File Coverage

blib/lib/Test/Mimic/Generator.pm
Criterion Covered Total %
statement 106 110 96.3
branch 14 24 58.3
condition n/a
subroutine 18 18 100.0
pod 1 2 50.0
total 139 154 90.2


line stmt bran cond sub pod time code
1             package Test::Mimic::Generator;
2              
3 1     1   29916 use 5.006001; #for open( my $fh...
  1         5  
  1         43  
4 1     1   7 use strict;
  1         2  
  1         39  
5 1     1   5 use warnings;
  1         7  
  1         290  
6              
7             our $VERSION = 0.009_005;
8              
9             #Returns the name of the package that objects returned by new are blessed into. For encapuslation
10             #purposes this may not be Test::Mimic::Generator. Should be considered protected.
11             sub get_object_package {
12 1     1 0 4 my ($class) = @_;
13 1         9 return $class . '::Object';
14             }
15              
16             # See the POD documentation below.
17             sub new {
18 1     1 1 14 my ($class) = @_;
19 1         5 return bless( [], $class->get_object_package() );
20             }
21              
22             package Test::Mimic::Generator::_Implementation;
23              
24 1     1   1108 use Test::Mimic::Library qw< stringify stringify_by destringify DATA descend >;
  1         44077  
  1         399  
25 1     1   15 use Cwd qw;
  1         2  
  1         228  
26 1     1   1529 use File::Copy;
  1         3607  
  1         74  
27 1     1   8 use Data::Dumper;
  1         3  
  1         505  
28              
29             # Construct constants to access member variables.
30             BEGIN {
31 1     1   3 my $offset = 0;
32 1         3 for my $field ( qw< TYPEGLOBS EXTRA OPERATION_SEQUENCE READ_DIR > ) {
33 4     2   1300 eval("sub $field { return $offset; }");
  2     1   10  
  1     2   3  
  2     3   17  
  3         42  
34 4         1789 $offset++;
35             }
36             }
37              
38             # See the POD documentation below.
39             sub Test::Mimic::Generator::Object::load {
40 1     1   8 my ($self, $dir_name) = @_;
41              
42 1 50       52 open( my $fh, '<', $dir_name . '/additional_info.rec' ) or die "Could not open file: $!";
43            
44 1         3 my $recorded_data;
45             {
46 1         2 local $/;
  1         4  
47 1         3 undef $/;
48 1         136 $recorded_data = <$fh>;
49             }
50              
51 1 50       17 close($fh) or die "Could not close file: $!";
52 1         7 my $living_data = destringify($recorded_data);
53              
54 1         1502 $self->[TYPEGLOBS] = $living_data->[0]; #This could change later, so I'm listing all the assigns explicitly.
55 1         36 $self->[EXTRA] = $living_data->[1];
56 1         36 $self->[OPERATION_SEQUENCE] = $living_data->[2];
57 1         34 $self->[READ_DIR] = $dir_name;
58             }
59              
60             # See the POD documentation below.
61             sub Test::Mimic::Generator::Object::write {
62 1     1   8 my ( $self, $write_dir, @packages ) = @_;
63              
64             # Either select all recorded packages to write or verify that the requested packages were recorded.
65 1 50       6 if ( @packages == 0 ) { # If no packages were selected explicitly...
66 1         3 @packages = keys %{ $self->[TYPEGLOBS] };
  1         52  
67             }
68             else {
69 0         0 for my $package (@packages) {
70 0 0       0 if ( ! exists( $self->[TYPEGLOBS]->{$package} ) ) {
71 0         0 die "The $package package was not found in the loaded recording.";
72             }
73             }
74             }
75            
76 1         13 my $top_level = abs_path();
77            
78             # Move to the $write_dir/lib directory, creating dirs as needed.
79 1         7 descend($write_dir);
80 1         50 descend('lib');
81            
82             # Consider each package, construct and write the .pm file.
83 1         38 my $start_path = abs_path();
84 1         3 for my $package (@packages) {
85            
86             # Gets the name of the .pm file, descends to where it will be located.
87 1         16 my @dirs = split( /::/, $package );
88 1         4 my $filename = pop(@dirs) . '.pm';
89 1         3 for my $dir (@dirs ) {
90 0         0 descend($dir);
91             }
92            
93             # Open, write and close the .pm file.
94 1 50       133 open( my $fh, '>', $filename ) or die "Could not open file: $!";
95 1         32 _create($package, $self->[TYPEGLOBS]->{$package}, $self->[EXTRA]->{$package}, $fh );
96 1 50       53 close($fh) or die "Could not close file: $!";
97              
98             # Move to the top of our fake library hierarchy.
99 1 50       34 chdir($start_path) or die "Could not change the current working directory: $!";
100             }
101              
102             # Rename the history file so that the controller recognizes it.
103 1 50       18 chdir($top_level) or die "Could not change the current working directory: $!";
104 1 50       44 copy( $self->[READ_DIR] . '/history_from_recorder.rec', $write_dir . '/history_for_playback.rec' )
105             or die "Unable to copy file: $!";
106             # NOTE: In the future we may modify the contents of the file as well.
107             }
108              
109             {
110             # A few useful constant maps.
111             my %TYPE_TO_SIGIL = ( 'ARRAY' => '@', 'HASH' => '%', 'SCALAR' => '$' );
112             my %TYPE_TO_TIE = (
113             'ARRAY' => 'Test::Mimic::Library::PlayArray',
114             'HASH' => 'Test::Mimic::Library::PlayHash',
115             'SCALAR' => 'Test::Mimic::Library::PlayScalar',
116             );
117              
118             # Accepts a package name, the corresponding pseudo symbol table, the corresponding extra hash ref
119             # and a filehandle to write to. Assembles the code for the mock package and writes it to disk.
120             sub _create {
121 1     1   2 my ( $package, $pseudo_symbol_table, $extra, $fh ) = @_;
122              
123 1         10 my $header_code = join( "\n",
124             'package ' . $package . ';',
125             '',
126             'use strict;',
127             'use warnings;',
128             '',
129             'BEGIN {', #TODO: Check to see if Test::Mimic is loaded, allow requiring fake pack directly etc.
130             ' Test::Mimic::prepare_for_use();',
131             '}',
132             '',
133             'use Scalar::Util;',
134             '',
135             'use Test::Mimic::Library qw< execute get_references HISTORY decode destringify >;',
136             'use Test::Mimic::Library::PlayScalar;',
137             'use Test::Mimic::Library::PlayArray;',
138             'use Test::Mimic::Library::PlayHash;',
139             '',
140             '',
141             );
142 1         8 print $fh $header_code;
143              
144             # Create code to tie package variables.
145 1         4 my $package_var_code = join( "\n",
146             'BEGIN {',
147             ' my $references = get_references();',
148             '',
149             );
150 1         2 for my $typeglob ( keys %{$pseudo_symbol_table} ) {
  1         23  
151              
152             # Tie the current typeglob
153 18         21 my %slots = %{ $pseudo_symbol_table->{$typeglob} };
  18         55  
154 18         27 delete $slots{'CODE'};
155 18         20 delete $slots{'CONSTANT'};
156             # NOTE: You may (some day) need to delete other types too.
157 18         947 for my $type ( keys %slots ) {
158 4         30 $package_var_code .= "\n" . ' tie( '
159             . $TYPE_TO_SIGIL{$type} . $package . '::' . $typeglob # Full name including sigil
160             . ', q<' . $TYPE_TO_TIE{$type}
161             . '>, $references->['
162             . $slots{$type}->[DATA] # Index for the reference, ...->[ENCODE_TYPE]
163             # must be VOLATILE. Check?
164             . ']->[HISTORY] );';
165             }
166             }
167 1         3 $package_var_code .= "\n" . '}' . "\n\n";
168 1         3 print $fh $package_var_code;
169              
170             # Create code for generating constants.
171 1         2 my $constant_code = 'use constant {' . "\n";
172 1         3 for my $symbol ( keys %{$pseudo_symbol_table} ) {
  1         4  
173 18         24 my $typeglob = $pseudo_symbol_table->{$symbol};
174 18 100       41 if ( exists( $typeglob->{'CONSTANT'} ) ) {
175 2         9 $constant_code .= ' ' . _string_to_perl($symbol) . ' => decode( destringify( '
176             . _string_to_perl( stringify( $typeglob->{'CONSTANT'} ) ) . ' ) ),' . "\n";
177            
178             }
179             }
180 1         3 $constant_code .= '};' . "\n\n";
181 1         3 print $fh $constant_code;
182              
183 1         2 my @ancestors = %{ $extra->{'ISA'} };
  1         6  
184 1         14 my $isa_code = join( "\n",
185             '{',
186             ' my %ancestors = qw< ' . "@ancestors" . ' >;', # Interpolation is needed here.
187             '',
188             ' sub isa {',
189             ' my ( $self, $type ) = @_;',
190             '',
191             ' if ( Scalar::Util::reftype($self) ) {',
192             ' my $name = Scalar::Util::blessed($self);',
193             ' if ($name) {',
194             ' return exists( $ancestors{$name} );',
195             ' }',
196             ' else {',
197             ' return ();',
198             ' }',
199             ' }',
200             ' else {',
201             ' return exists( $ancestors{$self} );',
202             ' }',
203             ' }',
204             '}',
205             '',
206             '',
207             );
208             # TODO: Make this dependent on user options.
209 1         3 print $fh $isa_code;
210              
211             # Create code for user defined subroutines
212 1         2 my $prototypes = $extra->{'PROTOTYPES'};
213 1         2 for my $symbol ( keys %{$pseudo_symbol_table} ) {
  1         5  
214 18         26 my $typeglob = $pseudo_symbol_table->{$symbol};
215 18 100       43 if ( exists( $typeglob->{'CODE'} ) ) {
216 11         15 my $sub_code = '{' . "\n"; # Of course, I could say "{\n". I am being overly verbose in an
217             # attempt to very explicitly separate out strings that
218             # interpolate. This is a problem because the perl code that I am
219             # writing often uses scalars that could be accidentally
220             # interpolated. If I come back to this line and add a scalar (or
221             # array) I don't want it to bite me.
222              
223             # Create the code for the behavior hash.
224 11         30 my $behavior_code = stringify( $typeglob->{'CODE'} );
225 11         1525 $sub_code .= 'my $behavior = destringify( ' . _string_to_perl($behavior_code) . ' );' . "\n";
226            
227 11         23 my $prototype = $prototypes->{$symbol};
228 11 100       51 $sub_code .= join( "\n",
229             '',
230             ' sub ' . $symbol . ( defined($prototype) ? " ($prototype)" : '' ) . ' {',
231             ' return execute( q<' . $package . '>, q<' . $symbol . '>, $behavior, \@_ );',
232             ' }',
233             '}',
234             '',
235             '',
236             );
237              
238 11         250 print $fh $sub_code;
239             }
240             }
241             }
242             }
243              
244             # Given a string returns a Perl expression (as a string) that evaluates to the passed string.
245             sub _string_to_perl {
246 15     15   389 my ($string) = @_;
247              
248 15         38 my $code = Dumper($string);
249 15         1031 $code =~ s/^.*?= //;
250 15         85 $code =~ s/;.*?\n$//;
251              
252 15         96 return $code;
253             }
254              
255             1;
256             __END__