line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::Mimic::Library; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
74337
|
use 5.006001; # for my $filehandle |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
124
|
|
4
|
1
|
|
|
1
|
|
7
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
37
|
|
5
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
155
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = 0.012_006; |
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
992
|
use Test::Mimic::Library::MonitorScalar; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
159
|
|
10
|
1
|
|
|
1
|
|
855
|
use Test::Mimic::Library::MonitorArray; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
34
|
|
11
|
1
|
|
|
1
|
|
1879
|
use Test::Mimic::Library::MonitorHash; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
34
|
|
12
|
1
|
|
|
1
|
|
1126
|
use Test::Mimic::Library::PlayScalar; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
31
|
|
13
|
1
|
|
|
1
|
|
1337
|
use Test::Mimic::Library::PlayArray; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
23
|
|
14
|
1
|
|
|
1
|
|
1570
|
use Test::Mimic::Library::PlayHash; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
36
|
|
15
|
1
|
|
|
1
|
|
741
|
use Test::Mimic::Library::MonitorTiedScalar; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
27
|
|
16
|
1
|
|
|
1
|
|
738
|
use Test::Mimic::Library::MonitorTiedArray; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
25
|
|
17
|
1
|
|
|
1
|
|
743
|
use Test::Mimic::Library::MonitorTiedHash; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
30
|
|
18
|
|
|
|
|
|
|
|
19
|
1
|
|
|
1
|
|
7
|
use Scalar::Util qw; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
728
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
#use Data::Dump::Streamer if possible, otherwise Data::Dumper and ad hoc replacements. |
22
|
|
|
|
|
|
|
BEGIN { |
23
|
1
|
50
|
|
1
|
|
3
|
if ( eval { require Data::Dump::Streamer; 1 } ) { |
|
1
|
|
|
|
|
463
|
|
|
0
|
|
|
|
|
0
|
|
24
|
0
|
|
|
|
|
0
|
Data::Dump::Streamer->import( qw<:undump Dump regex> ); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# Accepts a single argument. Returns true iff the argument is a regular expression created by qr. |
27
|
0
|
|
|
|
|
0
|
*_is_pattern = sub { return scalar regex( $_[0] ); }; |
|
0
|
|
|
|
|
0
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# Accepts a single argument. Returns a string form of this argument that can be inverted |
30
|
|
|
|
|
|
|
# (approximately) with _default_destringifier. |
31
|
|
|
|
|
|
|
*_default_stringifier = sub { |
32
|
0
|
|
|
|
|
0
|
return scalar Dump( $_[0] )->Names('TML_destringify_val')->KeyOrder('', 'lexical')->Out(); |
33
|
0
|
|
|
|
|
0
|
}; |
34
|
|
|
|
|
|
|
# The horrible name is my attempt to avoid collisions with variables from closures. Sadly, DDS doesn't |
35
|
|
|
|
|
|
|
# allow package scoped names. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Accepts a string returned by _default_stringifier. Returns an approximation to the original value. |
38
|
|
|
|
|
|
|
*_default_destringifier = sub { |
39
|
0
|
|
|
|
|
0
|
my $TML_destringify_val; |
40
|
0
|
0
|
|
|
|
0
|
eval( $_[0] . "; 1" ) |
41
|
|
|
|
|
|
|
or die "Unable to eval the string: $_[0]\nwith error: $@"; |
42
|
0
|
|
|
|
|
0
|
return $TML_destringify_val; |
43
|
0
|
|
|
|
|
0
|
}; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
else { |
46
|
1
|
|
|
|
|
1734
|
require Data::Dumper; |
47
|
1
|
|
|
|
|
12148
|
Data::Dumper->import(); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# Accepts a single argument. Returns true if the argument is a regular expression created by qr that |
50
|
|
|
|
|
|
|
# is not blessed. If it is blessed returns true iff the argument was blessed into the Regexp class. |
51
|
|
|
|
|
|
|
# Returns false in all other cases. In other words, this gives false positives for non qr refs |
52
|
|
|
|
|
|
|
# blessed into Regexp and false negatives for qr refs blessed into any other package. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# NOTE: This is a major problem if we need to store qr refs blessed into other packages. We will |
55
|
|
|
|
|
|
|
# attempt to dereference the qr object and tie the result. This will cause our code to die. False |
56
|
|
|
|
|
|
|
# positives will merely cause incomplete recording and punt the responsibility of preserving the |
57
|
|
|
|
|
|
|
# value to the stringifier. |
58
|
|
|
|
|
|
|
*_is_pattern = sub { |
59
|
17
|
|
|
17
|
|
34
|
my $type = ref( $_[0] ); |
60
|
17
|
50
|
|
|
|
32
|
if ( defined($type) ) { |
61
|
17
|
|
|
|
|
41
|
my $class = blessed( $_[0] ); |
62
|
17
|
100
|
|
|
|
27
|
if ( defined($class) ) { |
63
|
1
|
|
|
|
|
7
|
return $class eq 'Regexp'; |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
else { |
66
|
16
|
|
|
|
|
58
|
return $type eq 'Regexp'; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
else { |
70
|
0
|
|
|
|
|
0
|
return (); |
71
|
|
|
|
|
|
|
} |
72
|
1
|
|
|
|
|
9
|
}; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# Accepts a single argument. Returns a string form of this argument that can be inverted |
75
|
|
|
|
|
|
|
# (approximately) with _default_destringifier. |
76
|
1
|
|
|
6
|
|
4
|
*_default_stringifier = sub { return scalar Dumper( $_[0] ); }; |
|
6
|
|
|
|
|
24
|
|
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# Accepts a string returned by _default_stringifier. Returns an approximation to the original value. |
79
|
|
|
|
|
|
|
*_default_destringifier = sub { |
80
|
5
|
|
|
5
|
|
5
|
my $VAR1; |
81
|
5
|
50
|
|
|
|
366
|
eval( $_[0] . "; 1" ) |
82
|
|
|
|
|
|
|
or die "Unable to eval the string: $_[0]\nwith error: $@"; |
83
|
5
|
|
|
|
|
110
|
return $VAR1; |
84
|
1
|
|
|
|
|
160
|
}; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
require Exporter; |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
our @ISA = qw; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( |
93
|
|
|
|
|
|
|
'constants' => [ qw( |
94
|
|
|
|
|
|
|
SCALAR_CONTEXT |
95
|
|
|
|
|
|
|
LIST_CONTEXT |
96
|
|
|
|
|
|
|
VOID_CONTEXT |
97
|
|
|
|
|
|
|
STABLE |
98
|
|
|
|
|
|
|
VOLATILE |
99
|
|
|
|
|
|
|
NESTED |
100
|
|
|
|
|
|
|
RETURN |
101
|
|
|
|
|
|
|
EXCEPTION |
102
|
|
|
|
|
|
|
ARBITRARY |
103
|
|
|
|
|
|
|
CODE_E |
104
|
|
|
|
|
|
|
SCALAR_E |
105
|
|
|
|
|
|
|
ARRAY_E |
106
|
|
|
|
|
|
|
HASH_E |
107
|
|
|
|
|
|
|
ENCODE_TYPE |
108
|
|
|
|
|
|
|
DATA |
109
|
|
|
|
|
|
|
DATA_TYPE |
110
|
|
|
|
|
|
|
HISTORY |
111
|
|
|
|
|
|
|
CLASS |
112
|
|
|
|
|
|
|
) ], |
113
|
|
|
|
|
|
|
); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
our @EXPORT_OK = ( |
116
|
|
|
|
|
|
|
qw< |
117
|
|
|
|
|
|
|
encode |
118
|
|
|
|
|
|
|
decode |
119
|
|
|
|
|
|
|
monitor |
120
|
|
|
|
|
|
|
play |
121
|
|
|
|
|
|
|
monitor_args |
122
|
|
|
|
|
|
|
monitor_args_by |
123
|
|
|
|
|
|
|
play_args |
124
|
|
|
|
|
|
|
play_args_by |
125
|
|
|
|
|
|
|
gen_arg_key |
126
|
|
|
|
|
|
|
gen_arg_key_by |
127
|
|
|
|
|
|
|
stringify |
128
|
|
|
|
|
|
|
stringify_by |
129
|
|
|
|
|
|
|
destringify |
130
|
|
|
|
|
|
|
destringify_by |
131
|
|
|
|
|
|
|
init_records |
132
|
|
|
|
|
|
|
load_records |
133
|
|
|
|
|
|
|
write_records |
134
|
|
|
|
|
|
|
get_references |
135
|
|
|
|
|
|
|
execute |
136
|
|
|
|
|
|
|
descend |
137
|
|
|
|
|
|
|
load_preferences |
138
|
|
|
|
|
|
|
>, |
139
|
|
|
|
|
|
|
@{ $EXPORT_TAGS{'constants'} }, |
140
|
|
|
|
|
|
|
); |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
our @EXPORT = qw( |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
); |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
use constant { |
148
|
|
|
|
|
|
|
# Array indices for the three contexts |
149
|
1
|
|
|
|
|
5640
|
SCALAR_CONTEXT => 0, |
150
|
|
|
|
|
|
|
LIST_CONTEXT => 1, |
151
|
|
|
|
|
|
|
VOID_CONTEXT => 2, |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Description of encoded data |
154
|
|
|
|
|
|
|
STABLE => 200, |
155
|
|
|
|
|
|
|
VOLATILE => 201, |
156
|
|
|
|
|
|
|
NESTED => 202, |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# The two types of supported behavior |
159
|
|
|
|
|
|
|
RETURN => 300, |
160
|
|
|
|
|
|
|
EXCEPTION => 301, |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Convenience values |
163
|
|
|
|
|
|
|
ARBITRARY => 400, # For merely creating hash entries |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Event types. Should we deprecate this? |
166
|
|
|
|
|
|
|
CODE_E => 500, |
167
|
|
|
|
|
|
|
SCALAR_E => 501, |
168
|
|
|
|
|
|
|
ARRAY_E => 502, |
169
|
|
|
|
|
|
|
HASH_E => 503, |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# Encoded data fields, i.e. indices. |
172
|
|
|
|
|
|
|
ENCODE_TYPE => 0, |
173
|
|
|
|
|
|
|
DATA => 1, |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Reference table item fields, i.e. indices. |
176
|
|
|
|
|
|
|
DATA_TYPE => 0, |
177
|
|
|
|
|
|
|
HISTORY => 1, |
178
|
|
|
|
|
|
|
CLASS => 2, |
179
|
|
|
|
|
|
|
|
180
|
1
|
|
|
1
|
|
8
|
}; |
|
1
|
|
|
|
|
2
|
|
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
my $references; # A table containing recorded data for volatile references and objects. The index of a |
183
|
|
|
|
|
|
|
# given reference is simply the number of references |
184
|
|
|
|
|
|
|
# monitor saw before the reference under |
185
|
|
|
|
|
|
|
# consideration. |
186
|
|
|
|
|
|
|
my $address_to_index; # A hash ref mapping the address of a reference to its index in $references. |
187
|
|
|
|
|
|
|
my $is_alive; # A hash ref mapping the address of a reference to its current alive state. This will |
188
|
|
|
|
|
|
|
# be defined if the value stored at $address_to_index is current, undefined |
189
|
|
|
|
|
|
|
# otherwise. |
190
|
|
|
|
|
|
|
my $index_to_reference; # Almost, but not quite, the inverse of $address_to_index. Rather than mapping to the |
191
|
|
|
|
|
|
|
# address of the reference it maps to the reference itself. |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
# Preloaded methods go here. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub init_records { |
196
|
1
|
|
|
1
|
1
|
8
|
$references = []; |
197
|
1
|
|
|
|
|
3
|
$address_to_index = {}; |
198
|
1
|
|
|
|
|
3
|
$is_alive = {}; |
199
|
1
|
|
|
|
|
3
|
$index_to_reference = {}; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub load_records { |
203
|
0
|
|
|
0
|
1
|
0
|
my ($file_name) = @_; |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
0
|
init_records(); |
206
|
|
|
|
|
|
|
|
207
|
0
|
0
|
|
|
|
0
|
open( my $fh, '<', $file_name ) or die "Could not open file: $!"; |
208
|
|
|
|
|
|
|
|
209
|
0
|
|
|
|
|
0
|
my $recorded_data; |
210
|
|
|
|
|
|
|
{ |
211
|
0
|
|
|
|
|
0
|
local $/; |
|
0
|
|
|
|
|
0
|
|
212
|
0
|
|
|
|
|
0
|
undef $/; |
213
|
0
|
|
|
|
|
0
|
$recorded_data = <$fh>; |
214
|
|
|
|
|
|
|
} |
215
|
0
|
|
|
|
|
0
|
$references = destringify($recorded_data); |
216
|
|
|
|
|
|
|
|
217
|
0
|
0
|
|
|
|
0
|
close($fh) or die "Could not close file: $!"; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub get_references { |
221
|
1
|
|
|
1
|
1
|
58
|
return $references; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub write_records { |
225
|
0
|
|
|
0
|
1
|
0
|
my ($file_name) = @_; |
226
|
|
|
|
|
|
|
|
227
|
0
|
0
|
|
|
|
0
|
open( my $fh, '>', $file_name ) or die "Could not open file: $!"; |
228
|
0
|
|
|
|
|
0
|
print $fh stringify($references); |
229
|
0
|
0
|
|
|
|
0
|
close($fh) or die "Could not close file: $!"; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
sub load_preferences { |
233
|
0
|
|
|
0
|
0
|
0
|
my ($preferences) = @_; |
234
|
|
|
|
|
|
|
|
235
|
0
|
0
|
|
|
|
0
|
if ( defined( $preferences->{'string'} ) ) { |
236
|
0
|
|
|
|
|
0
|
stringify_by( $preferences->{'string' } ); |
237
|
|
|
|
|
|
|
} |
238
|
0
|
0
|
|
|
|
0
|
if ( defined( $preferences->{'destring'} ) ) { |
239
|
0
|
|
|
|
|
0
|
destringify_by( $preferences->{'destring'} ); |
240
|
|
|
|
|
|
|
} |
241
|
0
|
|
|
|
|
0
|
gen_arg_key_by($preferences); |
242
|
0
|
|
|
|
|
0
|
monitor_args_by($preferences); |
243
|
0
|
|
|
|
|
0
|
play_args_by($preferences); |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# Changes the current working directory to $dir. If $dir does not exist then it will be created. |
247
|
|
|
|
|
|
|
# If it exists, but it is not a directory or any other error occurs descend will die. |
248
|
|
|
|
|
|
|
sub descend { |
249
|
0
|
|
|
0
|
1
|
0
|
my ($dir) = @_; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Move to the $dir directory, creating if needed. |
252
|
0
|
0
|
|
|
|
0
|
if ( -e $dir ) { |
253
|
0
|
0
|
|
|
|
0
|
if ( ! ( -d $dir ) ) { |
254
|
0
|
|
|
|
|
0
|
die "$dir exists, but it is not a directory."; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
else { |
258
|
0
|
0
|
|
|
|
0
|
mkdir( $dir ) or die "Could not create directory: $!"; |
259
|
|
|
|
|
|
|
} |
260
|
0
|
0
|
|
|
|
0
|
chdir($dir) or die "Could not change the current working directory: $!"; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub execute { |
264
|
0
|
|
|
0
|
1
|
0
|
my ( $package, $subroutine, $behavior, $args ) = @_; |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Find proper behavior for these arguments. |
267
|
0
|
|
|
|
|
0
|
my $key = gen_arg_key( $package, $subroutine, $args ); |
268
|
|
|
|
|
|
|
|
269
|
0
|
0
|
|
|
|
0
|
if ( ! exists( $behavior->{$key} ) ) { |
270
|
0
|
|
|
|
|
0
|
die "No call recorded with corresponding arguments. Package: $package, Subroutine: $subroutine, Key: $key"; |
271
|
|
|
|
|
|
|
} |
272
|
0
|
|
|
|
|
0
|
my $context_to_result = $behavior->{$key}; |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# Find proper behavior for this context. |
275
|
0
|
|
|
|
|
0
|
my $index; |
276
|
0
|
0
|
|
|
|
0
|
if (wantarray) { |
|
|
0
|
|
|
|
|
|
277
|
0
|
|
|
|
|
0
|
$index = LIST_CONTEXT; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
elsif ( defined wantarray ) { |
280
|
0
|
|
|
|
|
0
|
$index = SCALAR_CONTEXT; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
else { |
283
|
0
|
|
|
|
|
0
|
$index = VOID_CONTEXT; |
284
|
|
|
|
|
|
|
} |
285
|
0
|
|
|
|
|
0
|
my $results = $context_to_result->[$index]; |
286
|
0
|
0
|
|
|
|
0
|
if ( ! defined( $results ) ) { |
287
|
0
|
|
|
|
|
0
|
die "No call recorded in context $index. Package: $package, Subroutine: $subroutine, Key: $key"; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# Obtain the results for this call. |
291
|
0
|
0
|
|
|
|
0
|
if ( @{$results} == 0 ) { |
|
0
|
|
|
|
|
0
|
|
292
|
0
|
|
|
|
|
0
|
die "Call history exhausted. Package: $package, Subroutine: $subroutine, Key: $key"; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
0
|
|
|
|
|
0
|
my ( $arg_signature, $stored_result ) = splice( @{$results}, 0, 2 ); |
|
0
|
|
|
|
|
0
|
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# Tie arguments making them behave as they were recorded behaving. |
298
|
0
|
|
|
|
|
0
|
play_args( $package, $subroutine, $args, $arg_signature ); |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# Perform appropriately |
301
|
0
|
|
|
|
|
0
|
my ( $result_type, $result ) = @{$stored_result}; |
|
0
|
|
|
|
|
0
|
|
302
|
0
|
0
|
|
|
|
0
|
if ( $result_type == EXCEPTION ) { |
|
|
0
|
|
|
|
|
|
303
|
0
|
|
|
|
|
0
|
die decode( $result ); |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
elsif ( $result_type == RETURN ) { |
306
|
0
|
0
|
|
|
|
0
|
if (wantarray) { |
|
|
0
|
|
|
|
|
|
307
|
0
|
|
|
|
|
0
|
return @{ decode($result) }; |
|
0
|
|
|
|
|
0
|
|
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
elsif ( defined wantarray ) { |
310
|
0
|
|
|
|
|
0
|
return decode($result); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
else { |
313
|
0
|
|
|
|
|
0
|
return; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
else { |
317
|
0
|
|
|
|
|
0
|
die "Bad result type <$result_type>. Package: $package, Subroutine: $subroutine, Key: $key"; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
{ |
322
|
|
|
|
|
|
|
my $key_gens = {}; |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
# The best way to think of the key generator is as a hint to the mimic system. A constant map to |
325
|
|
|
|
|
|
|
# 'the key' would work provided that all calls to a given subroutine occur in order. If a smarter |
326
|
|
|
|
|
|
|
# map is used then the mimic system will be more flexible. Call order only must be preserved in each set |
327
|
|
|
|
|
|
|
# of calls generated by the inverse map of each distinct key. Of course, if one call produces data that |
328
|
|
|
|
|
|
|
# another requires it doesn't really make sense to change the order (in either the playback _or_ record |
329
|
|
|
|
|
|
|
# stages). |
330
|
|
|
|
|
|
|
# |
331
|
|
|
|
|
|
|
# NOTE: The passed subroutine should probably not use the stored reference information. This is because |
332
|
|
|
|
|
|
|
# out of order calls could then break. Consider subroutines foo and bar. Both take a hash |
333
|
|
|
|
|
|
|
# reference. Suppose that in the recording stage foo is called first, bar second and that the same |
334
|
|
|
|
|
|
|
# reference is passed both times. If the reference is created by the user, i.e. not returned from a |
335
|
|
|
|
|
|
|
# mimicked subroutine or otherwise seen by the recorder, then foo will end up naming the reference. |
336
|
|
|
|
|
|
|
# foo's key generator will not be able to include the reference name and will perhaps instead perform |
337
|
|
|
|
|
|
|
# a straightforward stringification of the hash. bar's key generator on the other hand will be able to |
338
|
|
|
|
|
|
|
# use the fact that we are monitoring the reference and may instead create a key like '[ VOLATILE, 47 ]'. |
339
|
|
|
|
|
|
|
# Now suppose that in the playback stage the call order is reversed. The hash reference isn't named until |
340
|
|
|
|
|
|
|
# the call to foo, so there is no way bar can recognize it. |
341
|
|
|
|
|
|
|
# |
342
|
|
|
|
|
|
|
# NOTE: Or maybe SCRATCH ALL OF THAT. The above problem sucks, but the alternative is worse. Suppose we |
343
|
|
|
|
|
|
|
# do a _light_encode and then a stringification. If we played the object into existence then it is tied. |
344
|
|
|
|
|
|
|
# If it is tied and we examine it we will consume it's output. Even we added logic to halt the |
345
|
|
|
|
|
|
|
# consumption we don't have access to the most recent state of the object. Similarly, in the record phase |
346
|
|
|
|
|
|
|
# we don't know what the next access will be when gen_arg_key is called, so we can't approximate state |
347
|
|
|
|
|
|
|
# by considering the history information. We could allow gen_arg_key to cause history to build up like |
348
|
|
|
|
|
|
|
# it was a user call, but then we are enforcing call order on the set of subroutines that share |
349
|
|
|
|
|
|
|
# arguments. This is definitely a lesser of, err... 4 or 5, evils situation. |
350
|
|
|
|
|
|
|
# |
351
|
|
|
|
|
|
|
# NOTE: Additionally, you should avoid any calls to monitor, monitor_args or encode. These have the side |
352
|
|
|
|
|
|
|
# effect of naming passed values which will break the built in monitor_args/play_args paradigm. |
353
|
|
|
|
|
|
|
sub gen_arg_key_by { |
354
|
1
|
|
|
1
|
1
|
1415
|
$key_gens = $_[0]; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub gen_arg_key { |
358
|
4
|
|
|
4
|
1
|
1263
|
my ( $package, $subroutine, $args ) = @_; |
359
|
4
|
|
|
|
|
7
|
local $Test::Mimic::Recorder::SuspendRecording = 1; |
360
|
|
|
|
|
|
|
|
361
|
4
|
|
|
|
|
6
|
my $key_gen; |
362
|
4
|
100
|
100
|
|
|
47
|
if ( defined( $key_gen = $key_gens->{'packages'}->{$package}->{'subs'}->{$subroutine}->{'key'} ) |
|
|
|
100
|
|
|
|
|
363
|
|
|
|
|
|
|
|| defined( $key_gen = $key_gens->{'packages'}->{$package}->{'key'} ) |
364
|
|
|
|
|
|
|
|| defined( $key_gen = $key_gens->{'key'} ) ) { |
365
|
|
|
|
|
|
|
|
366
|
3
|
|
|
|
|
6
|
return &{$key_gen}($args); |
|
3
|
|
|
|
|
11
|
|
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
else { |
369
|
1
|
|
|
|
|
5
|
return stringify( _light_encode( $args, 2 ) ); |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
{ |
375
|
|
|
|
|
|
|
# Each of these helper subroutines takes ( $val, $at_level, $type ). |
376
|
|
|
|
|
|
|
my $scalar_action = sub { return [ 'SCALAR', _light_encode( ${ $_[0] }, $_[1] ) ]; }; |
377
|
|
|
|
|
|
|
my $simple_action = sub { return [ $_[2] ]; }; |
378
|
|
|
|
|
|
|
my %type_to_action = ( |
379
|
|
|
|
|
|
|
'REG_EXP' => $simple_action, |
380
|
|
|
|
|
|
|
'SCALAR' => $scalar_action, |
381
|
|
|
|
|
|
|
'REF' => $scalar_action, |
382
|
|
|
|
|
|
|
'LVALUE' => $scalar_action, |
383
|
|
|
|
|
|
|
'VSTRING' => $scalar_action, |
384
|
|
|
|
|
|
|
'ARRAY' => sub { |
385
|
|
|
|
|
|
|
my @temp = map( { _light_encode( $_, $_[1] ) } @{ $_[0] } ); |
386
|
|
|
|
|
|
|
return [ 'ARRAY', \@temp ]; |
387
|
|
|
|
|
|
|
}, |
388
|
|
|
|
|
|
|
'HASH' => sub { |
389
|
|
|
|
|
|
|
my %temp; |
390
|
|
|
|
|
|
|
@temp{ keys %{ $_[0] } } = map( { _light_encode( $_[0]->{$_}, $_[1] ) } keys %{ $_[0] } ); |
391
|
|
|
|
|
|
|
return [ 'HASH', \%temp]; |
392
|
|
|
|
|
|
|
}, |
393
|
|
|
|
|
|
|
'GLOB' => $simple_action, |
394
|
|
|
|
|
|
|
'IO' => $simple_action, |
395
|
|
|
|
|
|
|
'FORMAT' => $simple_action, |
396
|
|
|
|
|
|
|
'CODE' => $simple_action, |
397
|
|
|
|
|
|
|
); |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# RESULTS NOT SUITABLE FOR DECODE! |
400
|
|
|
|
|
|
|
sub _light_encode { |
401
|
23
|
|
|
23
|
|
767
|
my ( $val, $at_level ) = @_; |
402
|
|
|
|
|
|
|
|
403
|
23
|
|
|
|
|
57
|
my $type = reftype($val); |
404
|
23
|
100
|
|
|
|
59
|
if ( ! $type ) { # If the value is not a reference... |
|
|
50
|
|
|
|
|
|
405
|
10
|
|
|
|
|
41
|
return [ STABLE, $val ]; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
elsif ( exists( $type_to_action{$type} ) ) { |
408
|
13
|
|
|
|
|
29
|
my $address = refaddr($val); |
409
|
13
|
50
|
|
|
|
31
|
if ( defined( $is_alive->{$address} ) ) { |
410
|
0
|
|
|
|
|
0
|
return [ VOLATILE, $address_to_index->{$address} ]; |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
13
|
50
|
|
|
|
27
|
if ( _is_pattern($val) ) { # reftype doesn't recognize patterns, so set $type manually. |
414
|
0
|
|
|
|
|
0
|
$type = 'REG_EXP'; |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
13
|
100
|
|
|
|
46
|
if ( $at_level == 0 ) { # If we have reached the deepest requested layer... |
418
|
2
|
|
|
|
|
13
|
return [ NESTED, [ $type ] ]; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
else { |
421
|
11
|
|
|
|
|
14
|
$at_level--; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
11
|
|
|
|
|
13
|
my $coded = &{ $type_to_action{$type} }( $val, $at_level, $type ); |
|
11
|
|
|
|
|
35
|
|
425
|
11
|
|
|
|
|
47
|
return [ NESTED, $coded ]; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
else { |
428
|
0
|
|
|
|
|
0
|
die "Unknown reference type <$type> from <$val>. Unable to encode."; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
# So you want to build your own key generator? That's great. One rule: Never ever ever view the state of the |
434
|
|
|
|
|
|
|
# arguments you are mapping into keys. That won't be a problem will it? Didn't think so. Those of you |
435
|
|
|
|
|
|
|
# nonconformists that think state is important can use get_id. For each component of a passed value, i.e. |
436
|
|
|
|
|
|
|
# a single alias in the list, an array element of a dereferenced alias, an element of the array element |
437
|
|
|
|
|
|
|
# dereferenced as a hash etc., that you wish to examine _at all_ you must first call get_id on the component. |
438
|
|
|
|
|
|
|
# If it returns undef you can look at it, but if it is an aggregate you need to use get_id on it's components |
439
|
|
|
|
|
|
|
# as well. If undef is not returned, then you will be given an index corresponding to the reference. It is |
440
|
|
|
|
|
|
|
# guaranteed to be unique over the execution of the program and stable between the record and playback |
441
|
|
|
|
|
|
|
# phases. This is due to the fact that what you think are real variables in the playback phase are really |
442
|
|
|
|
|
|
|
# tied variables. They don't have any state and if you try to look at them you will just consume their fake |
443
|
|
|
|
|
|
|
# state. This will cause everything to crash and burn. In conclusion, use get_id. In the future we may store |
444
|
|
|
|
|
|
|
# state in the tied variables and allow you to look at them. Keep your fingers crossed. |
445
|
|
|
|
|
|
|
sub get_id { |
446
|
0
|
|
|
0
|
1
|
0
|
my ($val) = @_; |
447
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
0
|
my $address = refaddr($val); |
449
|
0
|
0
|
|
|
|
0
|
if ( defined( $is_alive->{$address} ) ) { |
450
|
0
|
|
|
|
|
0
|
return $address_to_index->{$address}; |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
else { |
453
|
0
|
|
|
|
|
0
|
return undef; |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
{ |
458
|
|
|
|
|
|
|
my $stringifier = \&_default_stringifier; |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub stringify_by { |
461
|
1
|
|
|
1
|
1
|
861
|
$stringifier = $_[0]; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
# Given an encoded element returns a string version. Should be suitable for use as a key in a hash as well as |
465
|
|
|
|
|
|
|
# being invertible with destringify. |
466
|
|
|
|
|
|
|
sub stringify { |
467
|
7
|
|
|
7
|
1
|
3105
|
return &{$stringifier}; |
|
7
|
|
|
|
|
98
|
|
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
{ |
472
|
|
|
|
|
|
|
my $destringifier = \&_default_destringifier; |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
sub destringify_by { |
475
|
1
|
|
|
1
|
1
|
584
|
$destringifier = $_[0]; |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
sub destringify { |
479
|
6
|
|
|
6
|
1
|
395
|
return &{$destringifier}; |
|
6
|
|
|
|
|
25
|
|
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
{ |
484
|
|
|
|
|
|
|
my $monitors = {}; |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
sub monitor_args_by { |
487
|
1
|
|
|
1
|
1
|
4
|
$monitors = $_[0]; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
# aliases act like references, but look like simple scalars. Because of this we have to be particularly |
491
|
|
|
|
|
|
|
# cautious where they could appear. Barring XS code and the sub{\@_} construction we only need to worry |
492
|
|
|
|
|
|
|
# about subroutine arguments, i.e. $_[i]. |
493
|
|
|
|
|
|
|
# |
494
|
|
|
|
|
|
|
# Accepts a reference to an array of aliases, |
495
|
|
|
|
|
|
|
# e.g. @_ from another subroutine. It will monitor each alias that is not read-only and return a tuple |
496
|
|
|
|
|
|
|
# consisting of the total number of aliases from the array reference as well as a hash reference that takes |
497
|
|
|
|
|
|
|
# an index of a mutable element in the array to the result of monitor being called on a reference to said |
498
|
|
|
|
|
|
|
# element. |
499
|
|
|
|
|
|
|
sub monitor_args { |
500
|
4
|
|
|
4
|
1
|
1880
|
my ( $package, $subroutine, $aliases ) = @_; |
501
|
|
|
|
|
|
|
|
502
|
4
|
|
|
|
|
5
|
my $arg_monitor; |
503
|
4
|
100
|
100
|
|
|
57
|
if ( defined( $arg_monitor = $monitors->{'packages'}->{$package}->{'subs'}->{$subroutine}->{'monitor_args'} ) |
|
|
|
100
|
|
|
|
|
504
|
|
|
|
|
|
|
|| defined( $arg_monitor = $monitors->{'packages'}->{$package}->{'monitor_args'} ) |
505
|
|
|
|
|
|
|
|| defined( $arg_monitor = $monitors->{'monitor_args'} ) ) { |
506
|
|
|
|
|
|
|
|
507
|
3
|
|
|
|
|
5
|
return &{$arg_monitor}($aliases); |
|
3
|
|
|
|
|
8
|
|
508
|
|
|
|
|
|
|
} |
509
|
|
|
|
|
|
|
else { |
510
|
1
|
|
|
|
|
3
|
my $num_aliases = @{$aliases}; |
|
1
|
|
|
|
|
2
|
|
511
|
1
|
|
|
|
|
3
|
my %mutable; |
512
|
1
|
|
|
|
|
8
|
for ( my $i = 0; $i < $num_aliases; $i++ ) { |
513
|
2
|
50
|
|
|
|
28
|
if ( ! readonly( $aliases->[$i] ) ) { |
514
|
2
|
|
|
|
|
8
|
$mutable{$i} = monitor( \$aliases->[$i] ); |
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
} |
517
|
1
|
|
|
|
|
11
|
return [ $num_aliases, \%mutable ]; |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
{ |
523
|
|
|
|
|
|
|
my $players = {}; |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
sub play_args_by { |
526
|
1
|
|
|
1
|
1
|
502
|
$players = $_[0]; |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
# Accepts an array of aliases and the tuple returned by monitor_args. |
530
|
|
|
|
|
|
|
# Attempts to match the aliases in the array reference with those in the tuple. If everything matches the |
531
|
|
|
|
|
|
|
# mutable passed aliases will be tied to behave as those monitored earlier, otherwise dies. The array and |
532
|
|
|
|
|
|
|
# the tuple representing the original array are said to match if the total number of elements are the same |
533
|
|
|
|
|
|
|
# and the mutable elements are the same, i.e. appear at the same indices. |
534
|
|
|
|
|
|
|
sub play_args { |
535
|
3
|
|
|
3
|
1
|
1365
|
my ( $package, $subroutine, $aliases, $coded_aliases ) = @_; |
536
|
|
|
|
|
|
|
|
537
|
3
|
|
|
|
|
5
|
my $arg_player; |
538
|
3
|
50
|
100
|
|
|
35
|
if ( defined( $arg_player = $players->{'packages'}->{$package}->{'subs'}->{$subroutine}->{'play_args'} ) |
|
|
|
66
|
|
|
|
|
539
|
|
|
|
|
|
|
|| defined( $arg_player = $players->{'packages'}->{$package}->{'play_args'} ) |
540
|
|
|
|
|
|
|
|| defined( $arg_player = $players->{'play_args'} ) ) { |
541
|
|
|
|
|
|
|
|
542
|
3
|
|
|
|
|
3
|
&{$arg_player}( $aliases, $coded_aliases ); |
|
3
|
|
|
|
|
9
|
|
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
else { |
545
|
0
|
|
|
|
|
0
|
my ( $orig_num_aliases, $mutable ) = @{$coded_aliases}; |
|
0
|
|
|
|
|
0
|
|
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
# Apply a primitive signature check, list length. |
548
|
0
|
|
|
|
|
0
|
my $cur_num_aliases = @{$aliases}; |
|
0
|
|
|
|
|
0
|
|
549
|
0
|
0
|
|
|
|
0
|
if ( $orig_num_aliases != $cur_num_aliases ) { |
550
|
0
|
|
|
|
|
0
|
die "Signatures do not match. Unable to play_args from <$coded_aliases> onto <$aliases>."; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
# Consider each alias, tie the mutable aliases if everything matches, else die. |
554
|
0
|
|
|
|
|
0
|
for ( my $i = 0; $i < $cur_num_aliases; $i++ ) { |
555
|
0
|
|
|
|
|
0
|
my $cur_read_only = readonly( $aliases->[$i] ); |
556
|
0
|
|
|
|
|
0
|
my $orig_read_only = ! exists( $mutable->{$i} ); |
557
|
|
|
|
|
|
|
|
558
|
0
|
0
|
0
|
|
|
0
|
if ( $cur_read_only && $orig_read_only ) { # If they are both read-only they match. |
|
|
0
|
0
|
|
|
|
|
559
|
0
|
|
|
|
|
0
|
next; # We shouldn't try to tie a read-only variable. :) |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
elsif ( ! $cur_read_only && ! $orig_read_only ) { # If they are both mutable... |
562
|
0
|
|
|
|
|
0
|
my $index = $mutable->{$i}->[DATA]; # See monitor. |
563
|
|
|
|
|
|
|
|
564
|
0
|
0
|
|
|
|
0
|
if ( defined( $index_to_reference->{$index} ) ) { # If we have already seen this value. |
565
|
0
|
|
|
|
|
0
|
next; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
#TODO: Assuming we maintain address_to_index and is_alive during playback too we can |
569
|
|
|
|
|
|
|
# check to see if $address_to_index{ refaddr( $index_to_reference{$index} ) } == $index. |
570
|
|
|
|
|
|
|
# If it doesn't we know that there is a problem. <---- that like something Or. |
571
|
|
|
|
|
|
|
|
572
|
0
|
|
|
|
|
0
|
my ( $type, $history, $old_class ) = @{ $references->[$index] }; |
|
0
|
|
|
|
|
0
|
|
573
|
0
|
|
|
|
|
0
|
tie( $aliases->[$i], 'Test::Mimic::Library::PlayScalar', $history ); |
574
|
0
|
|
|
|
|
0
|
$index_to_reference->{$index} = \( $aliases->[$i] ); |
575
|
0
|
|
|
|
|
0
|
weaken( $index_to_reference->{$index} ); # Don't prevent the val from being gced. |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
#NOTE: We need not bless the alias here. Either we produced it earlier, blessed it then and hit |
578
|
|
|
|
|
|
|
# next above or the alias was produced externally and if blessed at all was blessed |
579
|
|
|
|
|
|
|
# elsewhere. |
580
|
|
|
|
|
|
|
|
581
|
0
|
|
|
|
|
0
|
my $address = refaddr( \( $aliases->[$i] ) ); |
582
|
0
|
|
|
|
|
0
|
$address_to_index->{$address} = $index; |
583
|
0
|
|
|
|
|
0
|
$is_alive->{$address} = \( $aliases->[$i] ); |
584
|
0
|
|
|
|
|
0
|
weaken( $is_alive->{$address} ); |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
else { |
588
|
0
|
|
|
|
|
0
|
die "Mutable/immutable mismatch. Unable to play_args from <$coded_aliases> onto " |
589
|
|
|
|
|
|
|
. "<$aliases>."; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
sub _get_type { |
597
|
0
|
|
|
0
|
|
0
|
my ($val) = @_; |
598
|
|
|
|
|
|
|
|
599
|
0
|
0
|
|
|
|
0
|
if ( _is_pattern($val) ) { |
600
|
0
|
|
|
|
|
0
|
return 'REG_EXP'; |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
else { |
603
|
0
|
|
|
|
|
0
|
my $type = reftype($val); |
604
|
0
|
0
|
0
|
|
|
0
|
if ( $type eq 'REF' || $type eq 'LVALUE' || $type eq 'VSTRING' ) { |
|
|
|
0
|
|
|
|
|
605
|
0
|
|
|
|
|
0
|
return 'SCALAR'; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
else { |
608
|
0
|
|
|
|
|
0
|
return $type; |
609
|
|
|
|
|
|
|
} |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
{ |
614
|
|
|
|
|
|
|
# Each of these helper subroutines takes ( $val, $type ). |
615
|
|
|
|
|
|
|
my $scalar_action = sub { |
616
|
|
|
|
|
|
|
my $history = []; |
617
|
|
|
|
|
|
|
if ( defined( my $old_tie = tied( ${ $_[0] } ) ) ) { |
618
|
|
|
|
|
|
|
tie( ${ $_[0] }, 'Test::Mimic::Library::MonitorTiedScalar', $history, $old_tie ); |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
else { |
621
|
|
|
|
|
|
|
tie( ${ $_[0] }, 'Test::Mimic::Library::MonitorScalar', $history, $_[0] ); |
622
|
|
|
|
|
|
|
} |
623
|
|
|
|
|
|
|
return [ 'SCALAR', $history ]; |
624
|
|
|
|
|
|
|
}; |
625
|
|
|
|
|
|
|
my $simple_action = sub { return [ $_[1], $_[0] ]; }; |
626
|
|
|
|
|
|
|
my %type_to_action = ( |
627
|
|
|
|
|
|
|
'REG_EXP' => $simple_action, |
628
|
|
|
|
|
|
|
'SCALAR' => $scalar_action, |
629
|
|
|
|
|
|
|
'REF' => $scalar_action, |
630
|
|
|
|
|
|
|
'LVALUE' => $scalar_action, |
631
|
|
|
|
|
|
|
'VSTRING' => $scalar_action, |
632
|
|
|
|
|
|
|
'ARRAY' => sub { |
633
|
|
|
|
|
|
|
my $history = []; |
634
|
|
|
|
|
|
|
if ( defined( my $old_tie = tied( @{ $_[0] } ) ) ) { |
635
|
|
|
|
|
|
|
tie( @{ $_[0] }, 'Test::Mimic::Library::MonitorTiedArray', $history, $old_tie ); |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
else { |
638
|
|
|
|
|
|
|
tie ( @{ $_[0] }, 'Test::Mimic::Library::MonitorArray', $history, $_[0] ); |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
return [ 'ARRAY', $history ]; |
641
|
|
|
|
|
|
|
}, |
642
|
|
|
|
|
|
|
'HASH' => sub { |
643
|
|
|
|
|
|
|
my $history = []; |
644
|
|
|
|
|
|
|
if ( defined( my $old_tie = tied( %{ $_[0] } ) ) ) { |
645
|
|
|
|
|
|
|
tie( %{ $_[0] }, 'Test::Mimic::Library::MonitorTiedHash', $history, $old_tie ); |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
else { |
648
|
|
|
|
|
|
|
tie ( %{ $_[0] }, 'Test::Mimic::Library::MonitorHash', $history, $_[0] ); |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
return [ 'HASH', $history ]; |
651
|
|
|
|
|
|
|
}, |
652
|
|
|
|
|
|
|
'GLOB' => $simple_action, |
653
|
|
|
|
|
|
|
'IO' => $simple_action, |
654
|
|
|
|
|
|
|
'FORMAT' => $simple_action, |
655
|
|
|
|
|
|
|
'CODE' => $simple_action, |
656
|
|
|
|
|
|
|
); |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
# Monitor, i.e. tie the value and record its state, if possible (recursively as needed), otherwise merely |
659
|
|
|
|
|
|
|
# encapsulate the value as well as possible. In the second case proper storage and retrivial of the data |
660
|
|
|
|
|
|
|
# becomes the responsibility of Test::Mimic::Recorder::stringify. |
661
|
|
|
|
|
|
|
# |
662
|
|
|
|
|
|
|
# Objects are handled, but to a limited extent. The main restriction is that a reference (or rather the |
663
|
|
|
|
|
|
|
# 'object' behind the reference) can not change from being blessed to being unblessed anywhere that monitor |
664
|
|
|
|
|
|
|
# will notice. Purely internal modifications, i.e. those occurring in a wrapped subroutine, are okay. |
665
|
|
|
|
|
|
|
# Additionally, modifications occurring prior to the reference being monitored are okay. Also, it should be |
666
|
|
|
|
|
|
|
# noted that references blessed into a package that is not being recorded will have their state recorded |
667
|
|
|
|
|
|
|
# properly (including object info), but that object method calls on that reference will still not be |
668
|
|
|
|
|
|
|
# recorded. |
669
|
|
|
|
|
|
|
sub monitor { |
670
|
2
|
|
|
2
|
1
|
4
|
my ( $val ) = @_; |
671
|
|
|
|
|
|
|
|
672
|
2
|
|
|
|
|
7
|
my $type = reftype($val); |
673
|
2
|
50
|
|
|
|
7
|
if ( ! $type ) { # If this is not a reference... |
674
|
0
|
|
|
|
|
0
|
return [ STABLE, $val ]; |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
else { |
677
|
2
|
|
|
|
|
6
|
my $address = refaddr($val); |
678
|
2
|
|
|
|
|
4
|
my $index; |
679
|
|
|
|
|
|
|
|
680
|
2
|
50
|
|
|
|
7
|
if ( defined( $is_alive->{$address} ) ) { # If we are watching this reference... |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
# NOTE: We are using defined as opposed to exists because a given address can be used by multiple |
683
|
|
|
|
|
|
|
# references over the entire execution of the program. See the comment on weaken below. |
684
|
|
|
|
|
|
|
|
685
|
0
|
|
|
|
|
0
|
$index = $address_to_index->{$address}; |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
else { |
688
|
|
|
|
|
|
|
# Note that we are watching the reference. |
689
|
2
|
|
|
|
|
6
|
$is_alive->{$address} = $val; |
690
|
2
|
|
|
|
|
6
|
weaken( $is_alive->{$address} ); # This reference will be automatically set to undef when $$val is |
691
|
|
|
|
|
|
|
# garbage collected. |
692
|
|
|
|
|
|
|
|
693
|
2
|
50
|
|
|
|
6
|
if ( _is_pattern($val) ) { # reftype doesn't recognize patterns, so set $type manually. |
694
|
0
|
|
|
|
|
0
|
$type = 'REG_EXP'; |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
# Create a representation of the reference depending on its type. |
698
|
|
|
|
|
|
|
# Monitors recursively as necessary. |
699
|
2
|
|
|
|
|
4
|
my $reference; |
700
|
2
|
50
|
|
|
|
6
|
if ( exists( $type_to_action{$type} ) ) { |
701
|
2
|
|
|
|
|
4
|
$reference = &{ $type_to_action{$type} }( $val, $type ); |
|
2
|
|
|
|
|
8
|
|
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
else { |
704
|
0
|
|
|
|
|
0
|
die "Unknown reference type <$type> from <$val>. Unable to monitor."; |
705
|
|
|
|
|
|
|
} |
706
|
2
|
|
|
|
|
6
|
$reference->[2] = blessed($val); # Mark this as either an object or a plain reference. |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
# Store the representation of the reference into the references table. |
709
|
2
|
|
|
|
|
4
|
push( @{$references}, $reference ); |
|
2
|
|
|
|
|
4
|
|
710
|
2
|
|
|
|
|
11
|
$index = $address_to_index->{$address} = $#{$references}; |
|
2
|
|
|
|
|
7
|
|
711
|
|
|
|
|
|
|
} |
712
|
2
|
|
|
|
|
20
|
return [ VOLATILE, $index ]; |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
{ |
718
|
|
|
|
|
|
|
# Each of these helper subroutines takes ( $val, $at_level, $type ). |
719
|
|
|
|
|
|
|
my $scalar_action = sub { return [ 'SCALAR', encode( ${ $_[0] }, $_[1] ) ]; }; |
720
|
|
|
|
|
|
|
my $simple_action = sub { return [ $_[2], $_[0] ]; }; |
721
|
|
|
|
|
|
|
my %type_to_action = ( |
722
|
|
|
|
|
|
|
'REG_EXP' => $simple_action, |
723
|
|
|
|
|
|
|
'SCALAR' => $scalar_action, |
724
|
|
|
|
|
|
|
'REF' => $scalar_action, |
725
|
|
|
|
|
|
|
'LVALUE' => $scalar_action, |
726
|
|
|
|
|
|
|
'VSTRING' => $scalar_action, |
727
|
|
|
|
|
|
|
'ARRAY' => sub { |
728
|
|
|
|
|
|
|
my @temp = map( { encode( $_, $_[1] ) } @{ $_[0] } ); |
729
|
|
|
|
|
|
|
return [ 'ARRAY', \@temp ]; |
730
|
|
|
|
|
|
|
}, |
731
|
|
|
|
|
|
|
'HASH' => sub { |
732
|
|
|
|
|
|
|
my %temp; |
733
|
|
|
|
|
|
|
@temp{ keys %{ $_[0] } } = map( { encode( $_[0]->{$_}, $_[1] ) } keys %{ $_[0] } ); |
734
|
|
|
|
|
|
|
return [ 'HASH', \%temp]; |
735
|
|
|
|
|
|
|
}, |
736
|
|
|
|
|
|
|
'GLOB' => $simple_action, |
737
|
|
|
|
|
|
|
'IO' => $simple_action, |
738
|
|
|
|
|
|
|
'FORMAT' => $simple_action, |
739
|
|
|
|
|
|
|
'CODE' => $simple_action, |
740
|
|
|
|
|
|
|
); |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
# Performs an expansion wrap on the passed value until the given level then watches every component below. |
743
|
|
|
|
|
|
|
# Returns a structure analogous to the original except that each component is recursively wrapped. This should |
744
|
|
|
|
|
|
|
# only be used on static data. If circular references exist above the watch level or into the wrap level the |
745
|
|
|
|
|
|
|
# behavior is undefined. |
746
|
|
|
|
|
|
|
# |
747
|
|
|
|
|
|
|
# For example if _watch was passed an array it would perhaps return [ VOLATILE, 453 ]. |
748
|
|
|
|
|
|
|
# _wrap_then_watch would return [ NESTED, [ ARRAY, [ [ STABLE, 'foo' ], [ STABLE, 'bar' ] ] ] ] |
749
|
|
|
|
|
|
|
# |
750
|
|
|
|
|
|
|
# This is useful when the data currently in the array is important, but the array itself has no special |
751
|
|
|
|
|
|
|
# significance. |
752
|
|
|
|
|
|
|
# |
753
|
|
|
|
|
|
|
# Currently scalars et al., arrays, hashes, qr objects, code references are handled well. |
754
|
|
|
|
|
|
|
# Filehandles are not being tied, ideally they would be, but the filehandle tying mechanism is |
755
|
|
|
|
|
|
|
# not complete. |
756
|
|
|
|
|
|
|
# Formats are in a similar position, but they probably shouldn't ever be redefined. (Check this.) |
757
|
|
|
|
|
|
|
# Because of this that may not really be a problem. |
758
|
|
|
|
|
|
|
# The entries in globs can not be tied. A special glob tie could potentially remedy this, but |
759
|
|
|
|
|
|
|
# this does not currently exist. |
760
|
|
|
|
|
|
|
# |
761
|
|
|
|
|
|
|
# TODO: Handle circular references, also save space on DAGs. |
762
|
|
|
|
|
|
|
# Idea: Scan through structure. Record all references in a big hash. If we see duplicates note them. |
763
|
|
|
|
|
|
|
# The duplicates will exist as a special structure. |
764
|
|
|
|
|
|
|
# |
765
|
|
|
|
|
|
|
# [ CIRCULAR_NESTED, , [ ARRAY, blah... |
766
|
|
|
|
|
|
|
# We have one additional type: |
767
|
|
|
|
|
|
|
# [ DUP, ] |
768
|
|
|
|
|
|
|
sub encode { |
769
|
0
|
|
|
0
|
1
|
|
my ( $val, $at_level ) = @_; |
770
|
|
|
|
|
|
|
|
771
|
0
|
0
|
|
|
|
|
if ( $at_level == 0 ) { # If we have reached the volatile layer... |
772
|
0
|
|
|
|
|
|
return monitor( $val ); |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
else { |
775
|
0
|
|
|
|
|
|
$at_level--; |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
|
778
|
0
|
|
|
|
|
|
my $type = reftype($val); |
779
|
0
|
0
|
|
|
|
|
if ( ! $type ) { # If the value is not a reference... |
|
|
0
|
|
|
|
|
|
780
|
0
|
|
|
|
|
|
return [ STABLE, $val ]; |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
elsif ( exists( $type_to_action{$type} ) ) { |
783
|
0
|
0
|
|
|
|
|
if ( _is_pattern($val) ) { # reftype doesn't recognize patterns, so set $type manually. |
784
|
0
|
|
|
|
|
|
$type = 'REG_EXP'; |
785
|
|
|
|
|
|
|
} |
786
|
0
|
|
|
|
|
|
my $coded = &{ $type_to_action{$type} }( $val, $at_level, $type ); |
|
0
|
|
|
|
|
|
|
787
|
0
|
|
|
|
|
|
return [ NESTED, $coded ]; |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
else { |
790
|
0
|
|
|
|
|
|
die "Unknown reference type <$type> from <$val>. Unable to encode."; |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
{ |
796
|
|
|
|
|
|
|
# Each of these helper subroutines takes ( $val ). |
797
|
|
|
|
|
|
|
my $simple_action = sub { return $_[0]; }; |
798
|
|
|
|
|
|
|
my %type_to_action = ( |
799
|
|
|
|
|
|
|
'REG_EXP' => $simple_action, |
800
|
|
|
|
|
|
|
'SCALAR' => sub { |
801
|
|
|
|
|
|
|
my $temp = decode( $_[0] ); |
802
|
|
|
|
|
|
|
return \$temp; |
803
|
|
|
|
|
|
|
}, |
804
|
|
|
|
|
|
|
'ARRAY' => sub { |
805
|
|
|
|
|
|
|
my @temp = map( { decode( $_ ) } @{ $_[0] } ); |
806
|
|
|
|
|
|
|
return \@temp; |
807
|
|
|
|
|
|
|
}, |
808
|
|
|
|
|
|
|
'HASH' => sub { |
809
|
|
|
|
|
|
|
my %temp; |
810
|
|
|
|
|
|
|
@temp{ keys %{ $_[0] } } = map( { decode( $_[0]->[$_] ) } keys %{ $_[0] } ); |
811
|
|
|
|
|
|
|
return \%temp; |
812
|
|
|
|
|
|
|
}, |
813
|
|
|
|
|
|
|
'GLOB' => $simple_action, |
814
|
|
|
|
|
|
|
'IO' => $simple_action, |
815
|
|
|
|
|
|
|
'FORMAT' => $simple_action, |
816
|
|
|
|
|
|
|
'CODE' => $simple_action, |
817
|
|
|
|
|
|
|
); |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
sub decode { |
820
|
0
|
|
|
0
|
1
|
|
my ( $coded_val ) = @_; |
821
|
0
|
|
|
|
|
|
my ( $code_type, $data ) = @{$coded_val}; |
|
0
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
|
823
|
0
|
0
|
|
|
|
|
if ( $code_type == STABLE ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
824
|
0
|
|
|
|
|
|
return $data; |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
elsif ( $code_type == NESTED ) { |
827
|
0
|
|
|
|
|
|
my ( $ref_type, $val ) = @{$data}; |
|
0
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
|
829
|
0
|
0
|
|
|
|
|
if ( exists( $type_to_action{$ref_type} ) ) { |
830
|
0
|
|
|
|
|
|
return &{ $type_to_action{$ref_type} }( $val ); |
|
0
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
} |
832
|
|
|
|
|
|
|
else { |
833
|
0
|
|
|
|
|
|
die "Invalid reference type <$ref_type> from <$data> with value <$val>. Unable to decode."; |
834
|
|
|
|
|
|
|
} |
835
|
|
|
|
|
|
|
} |
836
|
|
|
|
|
|
|
elsif ( $code_type == VOLATILE ) { |
837
|
0
|
|
|
|
|
|
return play( $coded_val ); |
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
else { |
840
|
0
|
|
|
|
|
|
die "Invalid code type <$code_type> from <$coded_val> with data <$data>. Unable to decode."; |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
} |
843
|
|
|
|
|
|
|
} |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
{ |
846
|
|
|
|
|
|
|
# Each of these helper subroutines takes ( $history ). |
847
|
|
|
|
|
|
|
# This will be a single reference, i.e. not a true history, for types we do not tie. |
848
|
|
|
|
|
|
|
my $simple_action = sub { return $_[0]; }; |
849
|
|
|
|
|
|
|
my %type_to_action = ( |
850
|
|
|
|
|
|
|
'REG_EXP' => $simple_action, |
851
|
|
|
|
|
|
|
'SCALAR' => sub { |
852
|
|
|
|
|
|
|
my $temp; |
853
|
|
|
|
|
|
|
tie( $temp, 'Test::Mimic::Library::PlayScalar', $_[0] ); |
854
|
|
|
|
|
|
|
return \$temp; |
855
|
|
|
|
|
|
|
}, |
856
|
|
|
|
|
|
|
'ARRAY' => sub { |
857
|
|
|
|
|
|
|
my @temp; |
858
|
|
|
|
|
|
|
tie( @temp, 'Test::Mimic::Library::PlayArray', $_[0] ); |
859
|
|
|
|
|
|
|
return \@temp; |
860
|
|
|
|
|
|
|
}, |
861
|
|
|
|
|
|
|
'HASH' => sub { |
862
|
|
|
|
|
|
|
my %temp; |
863
|
|
|
|
|
|
|
tie( %temp, 'Test::Mimic::Library::PlayHash', $_[0] ); |
864
|
|
|
|
|
|
|
return \%temp; |
865
|
|
|
|
|
|
|
}, |
866
|
|
|
|
|
|
|
'GLOB' => $simple_action, |
867
|
|
|
|
|
|
|
'IO' => $simple_action, |
868
|
|
|
|
|
|
|
'FORMAT' => $simple_action, |
869
|
|
|
|
|
|
|
'CODE' => $simple_action, |
870
|
|
|
|
|
|
|
); |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
sub play { |
873
|
0
|
|
|
0
|
1
|
|
my ( $coded_val ) = @_; |
874
|
|
|
|
|
|
|
|
875
|
0
|
|
|
|
|
|
my ( $type, $data ) = @{$coded_val}; |
|
0
|
|
|
|
|
|
|
876
|
0
|
0
|
|
|
|
|
if ( $type == STABLE ) { |
|
|
0
|
|
|
|
|
|
877
|
0
|
|
|
|
|
|
return $data; |
878
|
|
|
|
|
|
|
} |
879
|
|
|
|
|
|
|
elsif ( $type == VOLATILE ) { |
880
|
0
|
0
|
|
|
|
|
if ( defined( $index_to_reference->{$data} ) ) { # We are using defined because the weak |
881
|
|
|
|
|
|
|
# references used in the hash will be set to |
882
|
|
|
|
|
|
|
# undef upon the destruction of the |
883
|
|
|
|
|
|
|
# corresponding values. |
884
|
0
|
|
|
|
|
|
return $index_to_reference->{$data}; |
885
|
|
|
|
|
|
|
} |
886
|
|
|
|
|
|
|
else { |
887
|
0
|
|
|
|
|
|
my ( $type, $history, $class_name ) = @{ $references->[$data] }; |
|
0
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
|
889
|
0
|
|
|
|
|
|
my $reference; |
890
|
0
|
0
|
|
|
|
|
if ( exists( $type_to_action{$type} ) ) { |
891
|
0
|
|
|
|
|
|
$reference = &{ $type_to_action{$type} }( $history ); |
|
0
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
} |
893
|
|
|
|
|
|
|
else { |
894
|
0
|
|
|
|
|
|
die "Unknown reference type <$type> at index <$data>. Unable to play."; |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
# If this reference is supposed to point at an object, bless it. |
898
|
|
|
|
|
|
|
# This will take place even if we didn't record the class. This may be a feature or a bug. |
899
|
0
|
0
|
|
|
|
|
if ( defined($class_name) ) { |
900
|
0
|
|
|
|
|
|
bless( $reference, $class_name ); |
901
|
|
|
|
|
|
|
} |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
# Note the creation of this reference, so we don't recreate it and are aware of what recorded |
904
|
|
|
|
|
|
|
# reference it corresponds to. |
905
|
0
|
|
|
|
|
|
my $address = refaddr($reference); |
906
|
0
|
|
|
|
|
|
$address_to_index->{$address} = $data; |
907
|
0
|
|
|
|
|
|
$is_alive->{$address} = $reference; |
908
|
0
|
|
|
|
|
|
weaken( $is_alive->{$address} ); |
909
|
0
|
|
|
|
|
|
$index_to_reference->{$data} = $reference; |
910
|
0
|
|
|
|
|
|
weaken( $index_to_reference->{$data} ); # But don't prevent it from being gced. If we |
911
|
|
|
|
|
|
|
# need to we can recreate it easily. ( Although the |
912
|
|
|
|
|
|
|
# address may well be different. ) |
913
|
|
|
|
|
|
|
|
914
|
0
|
|
|
|
|
|
return $reference; |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
else { |
918
|
0
|
|
|
|
|
|
die "Unrecognized type <$type>. Unable to play."; |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
} |
921
|
|
|
|
|
|
|
} |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
1; |
924
|
|
|
|
|
|
|
__END__ |