File Coverage

blib/lib/Test/StubGenerator.pm
Criterion Covered Total %
statement 155 159 97.4
branch 52 64 81.2
condition 10 12 83.3
subroutine 21 21 100.0
pod 2 2 100.0
total 240 258 93.0


line stmt bran cond sub pod time code
1             package Test::StubGenerator;
2              
3 11     11   330048 use strict;
  11         27  
  11         440  
4 11     11   62 use warnings;
  11         19  
  11         1606  
5              
6 11     11   578599 use PPI 1.118;
  11         2224560  
  11         366  
7 11     11   99204 use Perl::Tidy;
  11         2682568  
  11         1722  
8 11     11   108 use Carp;
  11         24  
  11         852  
9 11     11   11302 use English qw( -no_match_vars );
  11         40335  
  11         164  
10              
11 11     11   15633 use version; our $VERSION = qv('0.9.6');
  11         26786  
  11         93  
12              
13             my %DEFAULT_OPTIONS = ( file => undef,
14             source => undef,
15             output => undef,
16             out_dir => undef,
17             tidy => 1,
18             pertidyrc => '~/.perltidyrc', );
19              
20             sub new {
21 12     12 1 4316 my( $class, $arg_ref ) = @_;
22 12         55 my $self = {};
23              
24             # Collect the options passed in, defaulting to the, uh ... defaults.
25 11         87 my %option_args =
26             ref $arg_ref eq 'HASH'
27 12 100       107 ? ( %DEFAULT_OPTIONS, %{$arg_ref} )
28             : %DEFAULT_OPTIONS;
29 12         46 $self->{file} = $option_args{file};
30 12         32 $self->{source} = $option_args{source};
31 12         33 $self->{output} = $option_args{output};
32 12         29 $self->{out_dir} = $option_args{out_dir};
33 12         30 $self->{tidy} = $option_args{tidy};
34 12         35 $self->{perltidyrc} = $option_args{perltidyrc};
35 12         35 $self->{structure} = {};
36              
37             # Trim trailing slashes if present for easier interpolation later.
38 12 100       61 $self->{out_dir} =~ s{ / $ }{}xms if $self->{out_dir};
39              
40             # One or the other of these need to be non-false.
41 12   100     64 my $code = $self->{file} || $self->{source};
42 12 100       79 $code or die "No code provided to Test::StubGenerator\n"; # :)
43              
44             # Also, if we can't create a new PPI document from the file or
45             # code passed in, we're in trouble.
46 10 50       142 $self->{doc} = PPI::Document->new( $code, readonly => 1, )
47             or croak "Unable to initialize PPI document: $!";
48              
49 10         586368 return bless $self, $class;
50             }
51              
52             # Find something in the PPI doc that we're looking for.
53             sub _find {
54 22     22   60 my( $self, $sub_ref, $item_type ) = @_;
55 22         181 my $item_ref = $self->{doc}->find($sub_ref);
56 22 100       330 if($item_ref) {
57 17         134 return $item_ref;
58             }
59             else {
60 5         290 carp "No $item_type found";
61             }
62 5         3332 return;
63             }
64              
65             # In this case, we want things that are PPI::Subs and have names, but are
66             # not 'Scheduled' - i.e. BEGIN, CHECK, INIT, END blocks.
67             sub _find_subs {
68 11     11   62 my $self = shift;
69             my $subs_ref = $self->_find(
70             sub {
71 3810 100 100 3810   66410 $_[1]->isa('PPI::Statement::Sub')
72             && ! $_[1]->isa('PPI::Statement::Scheduled')
73             && $_[1]->name;
74             },
75 11         123 'subs', );
76 11         216 for my $sub ( @{$subs_ref} ) {
  11         48  
77 33         160 $self->_process_sub($sub);
78             }
79 11         34 return;
80             }
81              
82             # We're looking for package declarations. Not all code has a declared
83             # package, and that may or may not be a problem.
84             sub _find_package {
85 11     11   27 my $self = shift;
86             my $pkg_ref = $self->_find(
87             sub {
88 3810 100   3810   62007 $_[1]->isa('PPI::Statement::Package') && $_[1]->namespace;
89             },
90 11         108 'packages', );
91              
92 11         59 for my $pkg ( @{$pkg_ref} ) {
  11         45  
93 7         54 $self->{structure}->{package} = $pkg->namespace;
94             }
95 11         288 return;
96             }
97              
98             # We've been passed a named, non-scheduled PPI::Statement::Sub.
99             sub _process_sub {
100 33     33   64 my( $self, $sub ) = @_;
101              
102             # Let's examine the block defined for it.
103 33         116 my $block = $sub->block;
104 33         740 my @variables;
105              
106             # Keep track of all the variables passed into the subroutine.
107 33         267 for my $statement ( $block->children ) {
108 416 100       2397 if( $statement->isa('PPI::Statement::Variable') ) {
109 47         136 $self->_get_variables( $statement, \@variables );
110             }
111             }
112              
113             # Add the subroutine to the methods hash along with all associated variables
114             # that we were able to find.
115 33         206 $self->{structure}->{methods}->{ $sub->name } = [@variables];
116 33         860 return;
117             }
118              
119             sub _get_variables {
120 47     47   109 my( $self, $statement, $vars_ref ) = @_;
121              
122             # If any of the statements children contains "@_"...
123 47 100       181 if( scalar grep { $_->isa('PPI::Token::Magic') } $statement->children ) {
  415         2343  
124 14         63 push @{$vars_ref}, # keep all variables unpacked from @_.
  31         2206  
125 14         21 grep { $_ ne '$self' } $statement->variables; # other than $self
126             }
127              
128             # If any of the statements' children assigns using shift...
129 47 100       174 if( scalar grep { $_->content eq 'shift' } $statement->children ) {
  415         3913  
130 16         75 push @{$vars_ref}, # keep all variables shifted to.
  16         847  
131             # other than $self, $class, or $package
132 16         120 grep { $_ !~ /(?:\$self|\$class|\$package)/ } $statement->variables;
133             }
134 47         272 return;
135             }
136              
137             sub gen_testfile {
138 11     11 1 667 my $self = shift;
139              
140             # do a majority of the work here.
141 11         68 $self->_find_package();
142 11         79 $self->_find_subs();
143              
144             # start the testfile text.
145 11         43 my $test_file = _test_file_header();
146              
147 11         42 my $package = $self->{structure}->{package};
148              
149             # Add a little extra testing goodness if we're dealing with a package.
150 11         145 $test_file .= $self->_generate_preamble($package);
151              
152 11         32 my $declarations = q();
153 11         33 my $tests = q();
154 11         27 my @vars;
155 11         29 for my $sub ( sort keys %{ $self->{structure}->{methods} } ) {
  11         75  
156 26         63 my $vars_ref = $self->{structure}->{methods}->{$sub};
157 26         40 for my $var ( @{$vars_ref} ) {
  26         54  
158              
159             # Add handy testing variable declarations to the test file...
160 18 100       53 if( ! scalar grep { $_ eq $var } @vars ) {
  56         105  
161 16         21 my $arg_decl;
162 16 50       83 if( $var =~ /^\%/ ) {
    100          
163 0         0 $arg_decl = q{( '' => '', )};
164             }
165             elsif( $var =~ /^\@/ ) {
166 5         12 $arg_decl = q{( '', )};
167             }
168             else {
169 11         17 $arg_decl = q{''};
170             }
171 16         90 $declarations .= "my $var = " . sprintf "%s;\n", $arg_decl;
172              
173             # declare properly hash v. arr v. sclr
174             }
175              
176             # ... assuming we haven't run across them already.
177 18         55 push @vars, $var;
178             }
179              
180             # If we've got a package, precede all method calls with the object.
181 26 100       79 my $object_call = $package ? '$obj->' : q();
182             { # A little easier to interpolate the array directly.
183 26         44 local $LIST_SEPARATOR = ', ';
  26         40  
184 13         59 $tests .= "ok( $object_call$sub( @{ $vars_ref } ), "
  26         123  
185             . "'can call $object_call$sub()' );\n"
186 26 100       44 if @{$vars_ref};
187             }
188              
189             # add a test calling the subroutine without parameters.
190 26         121 $tests .= "ok( $object_call$sub(), "
191             . "'can call $object_call$sub() without params' );\n";
192 26         59 $tests .= "\n";
193             }
194              
195             # Put it all together.
196 11         60 $test_file .= _assemble_tests( $package, $declarations, $tests );
197              
198             # Tidy the output if desired
199 11 50       92 if( $self->{tidy} ) {
200 0         0 perltidy( source => \$test_file,
201             destination => \$test_file,
202             perltidyrc => $self->{perltidyrc} );
203             }
204 11         64 return $self->_handle_output($test_file);
205             }
206              
207             sub _generate_preamble {
208 13     13   44 my( $self, $package ) = @_;
209 13         28 my $test_file = q();
210              
211 13 100       64 if($package) {
212              
213             # Well packaged modules may not need the explicit 'use lib' statement.
214             # But in the off chance that `make test` doesn't set -I, the tests
215             # will still run.
216 7         32 my $pkg_hierarchy = $package =~ m/::/g;
217 7 100       30 if( $pkg_hierarchy > 0 ) {
218 2         8 my $use_lib = join q(/), q(..) x $pkg_hierarchy;
219 2         9 $test_file .= "use lib '$use_lib';\n\n";
220             }
221              
222             # Add the BEGIN block to the tests.
223 7         31 $test_file .= "BEGIN { use_ok( '$package' ); }\n\n";
224             }
225             else {
226              
227             # If it's not a package, chances are it should be required instead of used.
228 6 100       38 $test_file .= "BEGIN { require_ok( '$self->{ file }' ); }\n\n"
229             if $self->{file}; # If it's not a file, we really can't require it.
230             }
231              
232 13         32 my $constructor_found = 0;
233 13         32 for my $constructor (qw{ new instance }) {
234              
235             # If it's a package and has a constructor...
236 26 100 100     197 if( $package && defined $self->{structure}->{methods}->{$constructor} ) {
237 7         15 $constructor_found++; # controls whether or not we test the interface
238              
239             # Add tests for it.
240 7         43 $test_file .= 'ok( my $obj = ' . $package
241             . "->$constructor(), 'can create object $package' );\n";
242 7         25 $test_file .= "isa_ok( \$obj, '$package', 'object \$obj' );\n";
243              
244             # It seems that testing Test::StubGenerator->can( '$constructor' ); as an
245             # element of its interface makes less sense since by this
246             # point in the test file, we've aready used it. :)
247 7         31 delete $self->{structure}->{methods}->{$constructor};
248              
249             }
250             }
251              
252             # Add interface tests.
253 13 100       55 if($constructor_found) {
254 7         15 my @methods = sort keys %{ $self->{structure}->{methods} };
  7         66  
255 7 50       29 if( scalar @methods ) {
256              
257             # A little easier to interpolate the array directly.
258 7         21 local $LIST_SEPARATOR = q(', ');
259 7         48 $test_file .= "can_ok( \$obj, '@methods' );\n\n";
260             }
261             }
262 13         52 return $test_file;
263             }
264              
265             sub _handle_output {
266 11     11   35 my( $self, $test_file ) = @_;
267 11 100       54 if( defined $self->{output} ) {
268 2 100 33     49 if( ref $self->{output} eq 'GLOB' ) {
    50          
269              
270             # We've got a filehandle - print to it.
271 1 50       1 print { $self->{output} } $test_file
  1         21  
272             or croak "Can't write to file specified: $!";
273 1         15 return 1;
274             }
275             elsif( defined $self->{out_dir} && -d $self->{out_dir} ) {
276              
277             # We've got an existent directory for output.
278 1 0       5 return $self->_write_file($test_file)
279             or croak
280             "Can't write the test file to the directory as specified: $!";
281             }
282             else {
283 0         0 croak sprintf q(Can't write to file '%s' in directory '%s/'.),
284             $self->{output}, $self->{out_dir};
285             }
286             }
287             else {
288              
289             # Must be looking to have the text returned to them.
290 9         75 return $test_file;
291             }
292 0         0 return;
293             }
294              
295             sub _write_file {
296 1     1   14 my( $self, $test_file ) = @_;
297 1 50       6 my $filename = $self->{structure}->{package}
298             ? $self->{structure}->{package} # give preference to found package name
299             : $self->{output};
300 1         15 $filename =~ s{
301             ^ # Start of string
302             (?: # Don't capture this grouping
303             [\w/]* # zero or more word or slash chars
304             / # followed by a slash
305             )? # end (optional) grouping
306             (\w+) # one or more word characters
307             (?: # Don't capture this grouping
308             \.p[ml] # possibly with a pm or pl extension
309             )? # end (optional) grouping
310             $ # End of string;
311             }
312             {$1.t}x; # Give it a .t extension
313 1 50       141 open my $test_fh, '>', "$self->{out_dir}/$filename"
314             or croak "Can't open file for writing: $!";
315 1 50       3 print {$test_fh} $test_file or croak "Can't write to file: $!";
  1         21  
316 1 50       55 close $test_fh or carp "Can't close file: $!";
317 1         12 return 1;
318             }
319              
320             # A "theredoc", to keep it out of the other subroutines.
321             sub _assemble_tests {
322 12     12   39 my( $package, $declarations, $tests ) = @_;
323 12         26 my $assemblage;
324 12 100       67 $assemblage = <<"ASSEMBLED_TESTS" if $package;
325             # Create some variables with which to test the $package objects' methods
326             # Note: give these some reasonable values. Then try unreasonable values :)
327             ASSEMBLED_TESTS
328 12         71 $assemblage .= <<"ASSEMBLED_TESTS";
329             $declarations
330             # And now to test the methods/subroutines.
331             $tests
332             ASSEMBLED_TESTS
333 12         68 return $assemblage;
334             }
335              
336             # A "theredoc", to keep it out of the other subroutines.
337             sub _test_file_header {
338 12     12   39 return <<'TEST_FILE_HEADER';
339             #!/usr/bin/perl
340              
341             use strict;
342             use warnings;
343              
344             use Test::More qw/no_plan/;
345              
346             TEST_FILE_HEADER
347             }
348              
349             1;
350             __END__
351              
352             =head1 NAME
353              
354             Test::StubGenerator - A simple module that analyzes a given source file and
355             automatically generates t/*.t style tests for subroutines/methods
356             it encounters.
357              
358             =head1 SYNOPSIS
359              
360             use Test::StubGenerator;
361              
362             my $stub = Test::StubGenerator->new(
363             {
364             file => '/path/to/MyModule.pm',
365             tidy => 1,
366             }
367             );
368              
369             print $stub->gen_testfile;
370              
371             Or, from the command line (split for easier reading):
372              
373             $ perl -MTest::StubGenerator -e '
374             > my $stub = Test::StubGenerator->new({ file => "Module.pm" });
375             > print $stub->gen_testfile;' > Module.t
376              
377             =head1 DESCRIPTION
378              
379             Test::StubGenerator is a module that attempts to analyze a given source file
380             and automatically create testing stubs suitable for unit testing your code.
381              
382             Test::StubGenerator make use of PPI in order to parse your code, looking for
383             constructors and methods for modules (.pm), and subroutines for Perl script
384             files (.pl).
385              
386             Test::StubGenerator also runs the generated tests through Perl::Tidy before
387             returning the text of the tests to you, though this can be disabled.
388              
389             The idea for Test::StubGenerator grew out of a vim plugin I wrote that created
390             test stub files in a very similar fashion. However, the line-based nature of
391             vimscript quickly indicated that adding default parameters to the tests would
392             prove to be an exercise in futility. As this was a feature I very much wanted
393             to implement, I naturally turned to Perl, and L<PPI>.
394              
395             =head1 CONSTRUCTOR AND OPTIONS
396              
397             =head2 $stub = Test::StubGenerator->new( { file => 'MyModule.pm' } );
398              
399             Alternatively:
400              
401             my %options = (
402             file => '/path/to/Module.pm',
403             );
404              
405             my $stub - Test::StubGenerator->new( \%options );
406              
407             The full list of options:
408              
409             =head3 file
410              
411             Specify the path to the module or source code file for which you want to
412             generate test stubs.
413              
414             =head3 source
415              
416             Alternatively, if the code for which you want to create tests is already in a
417             scalar, pass a reference to that scalar as the named source argument.
418              
419             =head3 tidy
420              
421             Pass a true value to indicate that you'd like your generated tests run through
422             Perl::Tidy before being returned. This is the default. Specify a false
423             value to disable this feature. Note, this will by default use
424             your ~/.perltidyrc file for formatting.
425              
426             =head3 perltidyrc
427              
428             If you have a particular perltidyrc file, specify its location in this option.
429             Otherwise, the default is to use ~/.perltidyrc.
430              
431             =head3 output
432              
433             Pass a filename or an open filehandle to direct the output to. If this option
434             isn't specified, then gen_testfile() returns the textual data directly.
435              
436             =head3 out_dir
437              
438             Specify a directory for which to save your generated test file.
439              
440             =head1 METHODS
441              
442             =head2 $stub->gen_testfile()
443              
444             This is really the only method you need to know - after you've created a
445             Test::StubGenerator object, simply call $teststub->gen_testfile().
446              
447             =head1 DEPENDENCIES
448              
449             Requires L<PPI> and L<Perl::Tidy> to be installed.
450              
451             =head1 DIAGNOSTICS
452              
453             =head3 "No code provided to Test::StubGenerator"
454              
455             This means you've attempted to instantiate a new Test::StubGenerator object
456             without specifying a file for Test::StubGenerator to analyze. Either pass a
457             filename for Test::StubGenerator to analyze and create tests for, or a
458             reference to a scalar containing the source code you wish to analyze.
459              
460             =head3 "Unable to initialize PPI document"
461              
462             This means that the source you've passed to Test::StubGenerator has major
463             problems, and PPI is unable to parse it. At the very least, ensure your
464             code can pass `perl -Mstrict -wc <filename>` before attempting to generate
465             tests for it with Test::StubGenerator.
466              
467             =head3 "No [ packages | subs ] found"
468              
469             This is just a warning message indicating that Test::StubGenerator didn't
470             find any of the items of the specified type in your code. The functionality
471             that Test::StubGenerator supplies might be less than optimal if the code you're
472             analyzing doesn't contain any subroutines. :)
473              
474             =head3 "No output generated"
475              
476             This means that Test::StubGenerator wasn't able to produce output in the
477             desired format according to the options passed to the constructor. Possible
478             issues are: 1) a directory doesn't exist, 2) you don't have permission to
479             write to it, 3) the filesystem is full, 4) something is Very Broken.
480              
481             =head3 "Can't call method "gen_testfile"..."
482              
483             This probably means that you've trapped an exception with eval, but ignored
484             it by not checking if $@ ($EVAL_ERROR) has been set, and your code has
485             attempted to call gen_testfile() without ensuring that creating a
486             Test::StubGenerator object has been sucessfully created and initialized.
487              
488             =head3 "Can't open file for writing: Permission denied"
489              
490             You have passed an output directory (out_dir) that you don't have permission
491             to write to. Make sure you have the apropriate permission to the directory
492             you wish to create test files in.
493              
494             =head3 "Can't write to file 'filename' in directory 'directory'..."
495              
496             This means that you have passed an output directory that doesn't exist.
497             Please double check that any directory you specify in the named out_dir
498             parameter to new() exist and are writeable by your effective user id.
499              
500             =head1 SEE ALSO
501              
502             L<PPI>, L<Perl::Tidy>
503              
504             =head1 VERSION
505              
506             This documentation describes Test::StubGenerator version 0.9.6.
507              
508             =head1 AUTHOR
509              
510             Kent Cowgill, C<kent@c2group.net> L<http://www.kentcowgill.org/>
511              
512             =head1 REQUESTS & BUGS
513              
514             Please report any requests, suggestions, or bugs via the RT bug-tracking
515             system at http://rt.cpan.org/.
516              
517             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Test::StubGenerator> is the RT queue
518             for Test::StubGenerator. Please check to see if your bug has already been
519             reported.
520              
521             =head1 ACKNOWLEDGEMENTS
522              
523             Many thanks to the giants whose shoulders I stand upon, including Adam
524             Kennedy, and Steve Hancock.
525              
526             =head1 COPYRIGHT AND LICENSE
527              
528             Copyright (c) 2007-2009 by Kent Cowgill
529              
530             This library is free software; you can redistribute it and/or modify
531             it under the same terms as Perl itself.
532              
533             See L<http://www.perl.com/perl/misc/Artistic.html>
534              
535             =cut