File Coverage

blib/lib/Wetware/Test/CreateTestSuite.pm
Criterion Covered Total %
statement 27 185 14.5
branch 0 38 0.0
condition 0 5 0.0
subroutine 9 38 23.6
pod 22 22 100.0
total 58 288 20.1


line stmt bran cond sub pod time code
1             #-------------------------------------------------------------------------------
2             # $URL$
3             # $Date$
4             # $Author$
5             # $Revision$
6             #-------------------------------------------------------------------------------
7             package Wetware::Test::CreateTestSuite;
8              
9 3     3   88784 use warnings;
  3         40  
  3         251  
10 3     3   18 use strict;
  3         5  
  3         184  
11              
12 3     3   3486 use Wetware::Test::Utilities qw(is_testsuite_module);
  3         15763  
  3         204  
13              
14 3     3   5709 use English qw( -no_match_vars ) ;
  3         25511  
  3         22  
15              
16 3     3   1741 use File::Basename qw(dirname);
  3         6  
  3         166  
17 3     3   17 use File::Find qw(find);
  3         6  
  3         202  
18 3     3   18 use File::Path;
  3         7  
  3         436  
19 3     3   16 use File::Spec;
  3         6  
  3         47  
20              
21 3     3   5992 use Readonly;
  3         12436  
  3         8459  
22              
23             #-------------------------------------------------------------------------------
24              
25             our $VERSION = 0.03;
26              
27             # we could 'use constant' here, but....
28             Readonly::Scalar my $FAILURE_EXIT => 1;
29             Readonly::Scalar my $NORMAL_EXIT => 0;
30              
31             Readonly::Hash my %t_dir_content_for => (
32             'pod-coverage.t' => sub { return content_for_pod_coverage_t(); },
33             '00_compile_pm.t' => sub { return content_for_compile_pm_t(); },
34             '01_test_classes.t' => sub { return content_for_test_class_t(); },
35             );
36              
37             #-------------------------------------------------------------------------------
38              
39             sub _init {
40 0     0     my ($self) = @_;
41 0 0         $self->{'SEARCH_DIR'} = './lib' unless $self->search_dir();
42 0           return $self;
43             }
44              
45             #-------------------------------------------------------------------------------
46             # The Content group - these simple return here to documents.
47             #-------------------------------------------------------------------------------
48              
49             sub content_for_head {
50 0     0 1   my $head_content =<<'EOX';
51             #-------------------------------------------------------------------------------
52             # $URL$
53             # $Date$
54             # $Author$
55             # $Revision$
56             #-------------------------------------------------------------------------------
57             # AUTO GENERATED by Wetware::Test::CreateTestSuite
58             EOX
59 0           return $head_content;
60             }
61              
62             sub content_for_compile_pm_t {
63 0     0 1   my $head_content = content_for_head();
64 0           my $body_content =<<'EOX';
65             use strict;
66             use warnings;
67             use Test::More;
68             use Test::Compile;
69              
70             all_non_testsuite_modules();
71              
72             #-----------------------------------------------------------------------------
73              
74             sub all_non_testsuite_modules {
75              
76             my @modules = grep { $_ !~ m{/TestSuite.pm$} } all_pm_files();
77             plan tests => scalar @modules;
78             foreach my $module ( @modules ) {
79             pm_file_ok
80             ($module);
81             }
82             return;
83             }
84             EOX
85 0           my $content = $head_content . $body_content;
86 0           return $content;
87             }
88              
89             sub content_for_pod_coverage_t {
90 0     0 1   my $head_content = content_for_head();
91 0           my $body_content =<<'EOX';
92             use strict;
93             use warnings;
94             use Test::More;
95             #-----------------------------------------------------------------------------
96             # Ensure a recent version of Test::Pod::Coverage
97             my $min_tpc = 1.08;
98             eval "use Test::Pod::Coverage $min_tpc";
99             plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
100             if $@;
101              
102             # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
103             # but older versions don't recognize some common documentation styles
104             my $min_pc = 0.18;
105             eval "use Pod::Coverage $min_pc";
106             plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
107             if $@;
108             #-----------------------------------------------------------------------------
109              
110             all_non_testsuite_pod();
111              
112             #-----------------------------------------------------------------------------
113              
114             sub all_non_testsuite_pod {
115              
116             my @modules = grep { $_ !~ m{::TestSuite$} } all_modules();
117             plan tests => scalar @modules;
118             foreach my $module ( @modules ) {
119             pod_coverage_ok($module);
120             }
121             return;
122             }
123             EOX
124 0           my $content = $head_content . $body_content;
125 0           return $content;
126             }
127              
128             sub content_for_test_class_t {
129 0     0 1   my $head_content = content_for_head();
130 0           my $body_content =<<'EOX';
131             use strict;
132             use warnings;
133             use FindBin qw($Bin);
134             use Wetware::Test::Class::Load "$Bin/../blib/lib/";
135             EOX
136              
137 0           my $content = $head_content . $body_content;
138 0           return $content;
139             }
140              
141             #-------------------------------------------------------------------------------
142             # back to normalish subs
143             #-------------------------------------------------------------------------------
144              
145             sub find_pm {
146 0     0 1   my ($self, $directory) = @_;
147 0           my @pm;
148            
149             my $wanted = sub {
150 0     0     my $file_name = $_;
151 0           my $full_path = $File::Find::name;
152 0 0         if ( $file_name =~ /\.pm$/ ) {
153 0           push @pm , $full_path ;
154             }
155             return
156 0           };
  0            
157            
158             # this will filter out the CVS and .svn directories
159             # and while we are at it, any of the . dirs
160             my $preprocess = sub {
161 0     0     my (@dirs_found) = @_;
162 0 0 0       my @dirs_to_use = grep {
163 0           $_ ne 'CVS' && $_ ne '.svn' && $_ !~ /^\./
164             } @dirs_found;
165 0           return @dirs_to_use;
166 0           };
167            
168 0           my $options = { 'wanted' => $wanted, 'preprocess' => $preprocess};
169 0           File::Find::find( $options, $directory );
170            
171 0           return @pm;
172             }
173              
174             sub _test_suite_dir_for {
175 0     0     my ($self, $file_path) = @_;
176            
177 0           (my $test_suite_dir = $file_path ) =~ s{.pm$}{};
178 0           return $test_suite_dir;
179             }
180             sub has_testsuite {
181 0     0 1   my ($self, $file_path) = @_;
182            
183 0           my $test_suite_dir = $self->_test_suite_dir_for($file_path);
184 0 0         return unless -d $test_suite_dir;
185            
186 0           my $test_suite_path = File::Spec->join($test_suite_dir, 'TestSuite.pm');
187 0           return $self->is_testsuite($test_suite_path);
188             }
189              
190             sub is_testsuite {
191 0     0 1   my ($self, $file_path) = @_;
192 0           return is_testsuite_module($file_path);
193             }
194              
195             #-------------------------------------------------------------------------------
196             # the ALL CAPS FOR PARAMS style - till I come up with a better way.
197             #
198             sub new {
199 0     0 1   my ($class, %params) = @_; # Normalize the keys of the params hash to ALL_CAPS.
200 0           my %uppercase_params = map { ( uc $_ => $params{$_} ) } keys %params;
  0            
201 0           my $self = bless \%uppercase_params, $class;
202 0           return $self->_init();
203             }
204              
205             sub overwrite_t_files {
206 0     0 1   my $self = shift;
207 0           return $self->{'OVERWRITE_T_FILES'};
208             }
209              
210             sub parse_pm_file {
211 0     0 1   my ($self, $pm_path) = @_;
212 0           my ($pkg_name,$sub_name, @sub_names);
213            
214 0           my @lines = $self->slurp($pm_path);
215            
216             LINE:
217 0           foreach my $line (@lines) {
218 0           chomp $line;
219 0           $line =~ s/#.*//g; # remove comments.
220 0 0         next LINE unless $line;
221 0 0         if ( my ($tmp_name) = ($line =~ m/package\s*([^\s;]+)/) ) {
222 0           $pkg_name = $tmp_name;
223 0           next LINE; # we found the package line
224             }
225 0 0         if ( ($sub_name) = ($line =~ m/sub\s*([^\s]+)/) ) {
226 0           push @sub_names , $sub_name;
227 0           next LINE; # we found the package line
228             }
229             }
230 0           return ($pkg_name, @sub_names);
231             }
232              
233              
234             sub pm_that_need_test_suite {
235 0     0 1   my ($self, $dir ) = @_;
236            
237 0           my @pm_paths = $self->find_pm($dir);
238            
239 0           my @pm_needing_test_suite;
240             PM:
241 0           foreach my $pm_path (@pm_paths) {
242 0 0         next PM if $self->is_testsuite($pm_path);
243 0 0         next PM if $self->has_testsuite($pm_path);
244 0           push @pm_needing_test_suite, $pm_path;
245             }
246            
247 0           return @pm_needing_test_suite;
248             }
249             sub preview {
250 0     0 1   my $self = shift;
251 0           return $self->{'PREVIEW'};
252             }
253              
254             sub run {
255 0     0 1   my ($self) = @_;
256            
257 0           my $search_dir = $self->search_dir();
258            
259 0           my @pm = $self->pm_that_need_test_suite($search_dir);
260            
261 0 0         if ( $self->preview() ){
262 0           print "The following PM files do not yet have a TestSuite.pm\n";
263 0           foreach my $pm_path (@pm) {
264 0           print "\t -- ${pm_path}\n";
265             }
266             # TODO: should we show the t/ files that could be set?
267 0           return $NORMAL_EXIT;
268             }
269 0           $self->write_testsuites_for(@pm);
270 0           $self->write_t_dir_files();
271 0           return $NORMAL_EXIT;
272             }
273              
274             sub search_dir {
275 0     0 1   my $self = shift;
276 0           return $self->{'SEARCH_DIR'};
277             }
278              
279             sub slurp {
280 0     0 1   my ($self, $file) = @_;
281            
282 0 0         open(my $fh, '<', $file)
283             || Carp::confess("unable to open '$file': $OS_ERROR");
284 0           local $INPUT_RECORD_SEPARATOR ;
285 0           my $content = <$fh>;
286 0           close $fh;
287 0           my @lines = split(/\n/, $content); # want them as lines.
288 0           return @lines;
289             }
290              
291             sub t_dir {
292 0     0 1   my $self = shift;
293              
294 0           my $search_dir = $self->search_dir();
295 0           my $up_dir = dirname($search_dir);
296 0           my $t_dir = File::Spec->join($up_dir, 't');
297              
298 0           return $t_dir;
299             }
300              
301             sub test_sub_for {
302 0     0 1   my ($self, $sub_name) = @_;
303            
304 0           my $test_sub =<<'EOX';
305             #-----------------------------------------------------------------------------
306              
307             sub test_SUB_NAME : Test(1) {
308             my $self = shift;
309             my $class = $self->class_under_test();
310             Test::More::can_ok($class, 'SUB_NAME');
311             return $self;
312             }
313             EOX
314 0           $test_sub =~ s/SUB_NAME/$sub_name/g;
315            
316 0           return $test_sub;
317             }
318              
319             sub usage_lines {
320 0     0 1   my $use_lines =<<'EOX';
321              
322             use strict;
323             use warnings;
324             use Wetware::Test::Suite;
325             use base q{Wetware::Test::Suite};
326              
327             use Test::More;
328              
329             EOX
330              
331 0           return $use_lines;
332             }
333              
334             sub useful_test_case_content {
335              
336 0     0 1   my $use_lines =<<'EOX';
337             #-----------------------------------------------------------------------------
338              
339             sub test_new : Test(1) {
340             my $self = shift;
341             my $object = $self->object_under_test();
342             my $expected_class = $self->class_under_test();
343             Test::More::isa_ok( $object, $expected_class );
344             return $self;
345             }
346              
347             #-----------------------------------------------------------------------------
348             # a template for the next start of a test of another method
349             # sub test_new_method_name : Test(1) {
350             # my $self = shift;
351             # my $class = $self->class_under_test();
352             # Test::More::can_ok($class, 'new_method_name');
353             # return $self;
354             # }
355              
356             EOX
357 0           return $use_lines;
358             }
359              
360             sub write_test_suite_file {
361 0     0 1   my ($self,$test_suite_dir, $pkg_name, @subs) = @_;
362              
363 0           my $test_suite_file_path = File::Spec->join($test_suite_dir, 'TestSuite.pm');
364            
365 0           my $head_content = $self->content_for_head();
366 0           $self->_content_to($head_content,$test_suite_file_path);
367            
368 0           my $package_line = "package ${pkg_name}::TestSuite;\n";
369 0           $self->_append_content_to($package_line,$test_suite_file_path);
370            
371 0           my $usage_lines = $self->usage_lines();
372 0           $self->_append_content_to($usage_lines,$test_suite_file_path);
373 0           my $comment_line = q{#-----------------------------------------------------------------------------};
374            
375 0           my $use_pkg_line = "use ${pkg_name};\n${comment_line}\nsub class_under_test { return \'${pkg_name}\'; }\n";
376 0           $self->_append_content_to($use_pkg_line,$test_suite_file_path);
377            
378            
379 0           my $useful_test_case_content = $self->useful_test_case_content();
380 0           $self->_append_content_to($useful_test_case_content,$test_suite_file_path);
381            
382             SUB_NAME:
383 0           foreach my $sub_name (@subs) {
384 0 0         next SUB_NAME if $sub_name eq 'new';
385            
386 0           my $test_sub = $self->test_sub_for($sub_name);
387 0           $self->_append_content_to($test_sub,$test_suite_file_path);
388             }
389            
390 0           $self->_append_content_to("\n${comment_line}\n1;\n",$test_suite_file_path);
391            
392 0           return;
393             }
394             sub write_testsuites_for {
395 0     0 1   my ($self, @pm_paths) = @_;
396            
397 0 0         return unless @pm_paths; # no work, no worry.
398            
399 0           foreach my $pm_path ( @pm_paths) {
400 0           my ($pkg_name, @subs) = $self->parse_pm_file($pm_path);
401 0           my $test_suite_dir = $self->_test_suite_dir_for($pm_path);
402 0 0         if ( ! -d $test_suite_dir ) {
403 0 0         File::Path::mkpath( $test_suite_dir )
404             || Carp::confess("Unable to make dir '$test_suite_dir':$OS_ERROR");
405             }
406 0           $self->write_test_suite_file($test_suite_dir, $pkg_name, @subs);
407             }
408 0           return $self;
409             }
410              
411             #-------------------------------------------------------------------------------
412              
413             sub _t_dir_files {
414 0     0     my @t_dir_files = keys %t_dir_content_for;
415 0           return @t_dir_files;
416             }
417              
418             sub write_t_dir_files {
419 0     0 1   my ($self) = @_;
420            
421 0           my $t_dir = $self->t_dir();
422             # will want a logging system at some point
423             # to note that this is not the directory....
424 0 0         return unless -d $t_dir;
425            
426 0           my @t_dir_files = $self->_t_dir_files();
427 0           my $over_write = $self->overwrite_t_files();
428              
429             T_FILE:
430 0           foreach my $t_file (@t_dir_files) {
431 0           my $t_file_path = File::Spec->join($t_dir, $t_file);
432            
433             # If the over_write flag is set, then we do
434             # not care if the file exists. Otherwise, we skip
435             # any of the files that exist.
436 0 0         if ( ! $over_write ) {
437 0 0         next T_FILE if -f $t_file_path;
438             }
439            
440 0           my $content = $t_dir_content_for{$t_file}->();
441 0           $self->_content_to( $content, $t_file_path );
442             }
443 0           return $self;
444             }
445              
446             sub _content_to {
447 0     0     my ($self, $content, $file_path, $mode) = @_;
448              
449 0   0       $mode ||= '>';
450 0 0         open(my $fh, $mode , $file_path)
451             or Carp::confess("unable to write '$file_path' : $OS_ERROR");
452            
453 0           print $fh $content;
454 0           close $fh;
455 0           return;
456             }
457              
458             sub _append_content_to {
459 0     0     my ($self, $content, $file_path) = @_;
460 0           my $mode = '>>';
461 0           return $self->_content_to($content, $file_path, $mode);
462             }
463              
464             #-------------------------------------------------------------------------------
465              
466             1;
467              
468             __END__
469              
470             =pod
471              
472             =head1 NAME
473              
474             Wetware::Test::CreateTestSuite - for creating TestSuite.pm
475              
476             =head1 SYNOPSIS
477              
478             use Wetware::Test::CreateTestSuite;
479              
480             my $test_stuite_creator = Wetware::Test::CreatTestSuite->new( %{$options} );
481              
482             $test_stuite_creator->run();
483              
484             =head1 DESCRIPTION
485              
486             The goal: automate the process of creating a Test::Class based TestSuite.pm
487             for each module Foo. So that there will be at least a Foo::TestSuite.pm frame
488             that will have a working test_new() method.
489              
490             It will also seek to install into the t/ of the "search_dir" the three
491             basic scripts.
492              
493             The new 01_test_class.t script requires that there is the
494             Wetware::Test::Class::Load that will skip over the CVS and .svn directories
495             that may get copied into blib/lib from lib by the basic Module::Build
496             process.
497              
498             =head1 METHODS
499              
500             =head2 new( %params)
501              
502             Constructs the immutable CreateTestSuite object.
503              
504             =head2 run()
505              
506             At present I do not see a good reason for any other public methods.
507              
508             Since it just needs to be constructed and run.
509              
510             It will exit with the normal exit value for unix of '0'.
511              
512             =head1 DemiPrivate METHODS
513              
514             This is the list of mostly private methods, but are listed so that IF anyone
515             comes up with a good case to subclass this...
516              
517             =head2 write_testsuites_for(@pm)
518              
519             Given a list of pm_paths, write the TestSuite.pm.
520              
521             =head2 slurp($file)
522              
523             Nice slurp the data in.
524              
525             =head2 preview()
526              
527             accessor to preview.
528              
529             =head2 pm_that_need_test_suite($dir)
530              
531             Calls C<find_pm()> to get a list of pm in the $dir.
532              
533             It then filgers out those that are TestSuite.pm, or have
534             a TestSuite.pm module.
535              
536             The list returned is those that will need to have one added.
537              
538             =head2 write_t_dir_files()
539              
540             Writes out the alternative C<.t> files into C<t/>. These files are:
541              
542             =over
543              
544             =item * 00_compile_pm.t
545              
546             =item * 01_test_class.t
547              
548             =item * pod-coverage.t
549              
550             =back
551              
552             Note, these will not overwrite existing files, so IF this is
553             run after module-starter has installed the default pod-coverage.t
554             that file should be removed first.
555              
556             TODO: what if we had a cli option for this? some sort of
557             I<--t_files_only>
558              
559             =head2 parse_pm_file()
560              
561             TBD
562              
563             =head2 is_testsuite($file_path)
564              
565             wrapper on Wetware::Test::Utilities::is_testsuite_module;
566              
567             =head2 has_testsuite($module_path)
568              
569             Constructs the path to the Foo::TestSuite, given
570             the path to the Foo Module. Then uses the file path
571             to the TestSuite to call C<is_testsuite()>.
572              
573             It will short circuit, if there is no Foo directory.
574              
575             =head2 find_pm($directory)
576              
577             A wrapper on File::Find:find() that has both a wanted()
578             and a preprocess() method passed in as an option.
579              
580             It will currently skip over any directory that is begins
581             with 'CVS', '.svn' and any that begins with a '.'.
582              
583              
584             =head2 search_dir()
585              
586             Accessor that returns the search directory.
587              
588             =head2 t_dir()
589              
590             returns the path to where the t/ is expected to be.
591              
592             =head2 overwrite_t_files()
593              
594             Accessor to named command line value.
595              
596             =head2 write_test_suite_file($test_dir, $pkg_name, @subnames)
597              
598             Given the directory where TestSuite.pm is to be written,
599             the pkg_name it will test, and a list of subnames,
600             write out the TestSuite.pm file itself.
601              
602             =head1 CONTENT METHODS
603              
604             There is a debate about whether or not we should have a
605             get_resources_from_INC() method that would find these
606             as template files..... instead of having these three
607             hereto document methods....
608              
609             =head2 content_for_head() - returns the 'svn' comment block
610              
611             =head2 content_for_compile_pm_t()
612              
613             =head2 content_for_pod_coverage_t()
614              
615             =head2 content_for_test_class_t()
616              
617             If we shift to a get_resources_from_INC() approach, then
618             these will be the access methods to that solution.
619              
620             =head2 test_sub_for($sub_name)
621              
622             Creates the content for the test_SUBNAME() sub.
623              
624             =head2 usage_lines()
625              
626             Returns a set of stock use lines.
627              
628             =head2 useful_test_case_content()
629              
630             Adds a test_new() and a commented out test_methodNameHere() method.
631              
632             =head1 CHANGE STOCK test files
633              
634             There are two semi standard .t test scripts that need to
635             be changed to work and play well with the TestSuite approach,
636             since if one attempts to do the simple Test::Compile of
637             any Module that inherits from a Test::Class based module
638             with the INIT block, they will fail becaue 'it is too
639             late for INIT' - this is also true of Pod Coverage.
640              
641             So the fix is to call a sub.
642              
643             Modify pod-coverage.t to use:
644              
645             =over
646              
647             all_non_testsuite_pod();
648              
649             #-------------------------------------------------------------
650              
651             sub all_non_testsuite_pod {
652              
653             my @modules = grep { $_ !~ m{::TestSuite$} } all_modules();
654             plan tests => scalar @modules;
655             foreach my $module ( @modules ) {
656             pod_coverage_ok($module);
657             }
658             return;
659             }
660              
661             =back
662              
663             And it will filter out the modules *::TestSuite.
664              
665             The compile_pm script is about the same, except that it runs with
666             module file names:
667              
668             =over
669              
670             all_non_testsuite_modules();
671              
672             #----------------------------------------------------------------
673              
674             sub all_non_testsuite_modules {
675              
676             my @modules = grep { $_ !~ m{/TestSuite.pm$} } all_pm_files();
677             plan tests => scalar @modules;
678             foreach my $module ( @modules ) {
679             pm_file_ok
680             ($module);
681             }
682             return;
683             }
684              
685             =back
686              
687             You can download this module from the CPAN and look at the
688             t/ directory for the specific test files.
689              
690             =head1 AUTHOR
691              
692             "drieux", C<< <"drieux [AT] at wetware.com"> >>
693              
694             =head1 BUGS
695              
696             Please report any bugs or feature requests
697             to C<bug-wetware-test-createtestsuite at rt.cpan.org>, or through
698             the web interface at
699             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Wetware-Test-CreateTestSuite>.
700             I will be notified, and then you'll
701             automatically be notified of progress on your bug as I make changes.
702              
703             =head1 SUPPORT
704              
705             You can find documentation for this module with the perldoc command.
706              
707             perldoc Wetware::Test::CreateTestSuite
708              
709              
710             You can also look for information at:
711              
712             =over 4
713              
714             =item * RT: CPAN's request tracker
715              
716             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Wetware-Test-CreateTestSuite>
717              
718             =item * AnnoCPAN: Annotated CPAN documentation
719              
720             L<http://annocpan.org/dist/Wetware-Test-CreateTestSuite>
721              
722             =item * CPAN Ratings
723              
724             L<http://cpanratings.perl.org/d/Wetware-Test-CreateTestSuite>
725              
726             =item * Search CPAN
727              
728             L<http://search.cpan.org/dist/Wetware-Test-CreateTestSuite/>
729              
730             =back
731              
732             =head1 COPYRIGHT & LICENSE
733              
734             Copyright 2009 "drieux", all rights reserved.
735              
736             This program is free software; you can redistribute it and/or modify it
737             under the same terms as Perl itself.
738              
739             =cut
740              
741             # End of Wetware::Test::CreateTestSuite