File Coverage

blib/lib/Test/Inline/Script.pm
Criterion Covered Total %
statement 123 129 95.3
branch 39 54 72.2
condition 10 12 83.3
subroutine 25 26 96.1
pod 14 14 100.0
total 211 235 89.7


line stmt bran cond sub pod time code
1             package Test::Inline::Script;
2             # ABSTRACT: Generate the test file for a single source file
3              
4             #pod =pod
5             #pod
6             #pod =head1 DESCRIPTION
7             #pod
8             #pod This class is where the heavy lifting happens to actually generating a
9             #pod test file takes place. Given a source filename, this modules will load
10             #pod it, parse out the relavent bits, put them into order based on the tags,
11             #pod and then merge them into a test file.
12             #pod
13             #pod =head1 METHODS
14             #pod
15             #pod =cut
16              
17 12     12   93 use strict;
  12         23  
  12         410  
18 12     12   79 use List::Util ();
  12         26  
  12         264  
19 12     12   61 use Params::Util qw{_ARRAY _INSTANCE};
  12         23  
  12         742  
20 12     12   85 use Algorithm::Dependency::Item ();
  12         23  
  12         190  
21 12     12   52 use Algorithm::Dependency::Source ();
  12         22  
  12         241  
22 12     12   5977 use Algorithm::Dependency::Ordered ();
  12         5345  
  12         537  
23              
24             use overload 'bool' => sub () { 1 },
25 12     12   119 '""' => 'filename';
  12         25  
  12         81  
26              
27             our $VERSION = '2.214';
28             our @ISA = qw{
29             Algorithm::Dependency::Source
30             Algorithm::Dependency::Item
31             };
32              
33             # Special case, for when doing unit tests ONLY.
34             # Don't throw the missing files warning.
35 12     12   1340 use vars qw{$NO_MISSING_DEPENDENCIES_WARNING};
  12         28  
  12         555  
36             BEGIN {
37 12     12   18258 $NO_MISSING_DEPENDENCIES_WARNING = '';
38             }
39              
40              
41              
42              
43              
44             #####################################################################
45             # Constructor and Accessors
46              
47             #pod =pod
48             #pod
49             #pod =head2 new
50             #pod
51             #pod my $File = Test::Inline::Script->new( $class, \@sections, $check_count );
52             #pod
53             #pod The C constructor takes a class name, set of Section objects and
54             #pod an optional C flag.
55             #pod
56             #pod Returns a Test::Inline::Script object on success.
57             #pod Returns C on error.
58             #pod
59             #pod =cut
60              
61             sub new {
62 20     20 1 45 my $class = shift;
63 20 50       54 my $_class = defined $_[0] ? shift : return undef;
64 20 50       104 my $Sections = _ARRAY(shift) or return undef;
65 20   100     75 my $check_count = shift || 0;
66              
67             # Create the object
68             my $self = bless {
69             class => $_class,
70 49         153 setup => [ grep { $_->setup } @$Sections ],
71 20         54 sections => [ grep { ! $_->setup } @$Sections ],
  49         101  
72             filename => lc "$_class.t",
73             check_count => $check_count,
74             # tests => undef,
75             }, $class;
76 20         460 $self->{filename} =~ s/::/_/g;
77              
78             # Verify the uniqueness of the names
79 20 50       77 $self->_duplicate_names and return undef;
80              
81             # Warn if we have missing dependencies
82 20         128 my $missing = $self->missing_dependencies;
83 20 100       123 if ( $missing ) {
84 1         3 foreach ( @$missing ) {
85 1 50       4 next if $NO_MISSING_DEPENDENCIES_WARNING;
86 0         0 print "Warning: Missing dependency '$_' in $self->{class}\n";
87             }
88             }
89              
90             # Quickly predetermine if there will be an unknown number
91             # of unit tests in the file
92 20         44 my $unknown = grep { ! defined $_->tests } @$Sections;
  49         104  
93 20 50 66     68 unless ( $unknown or grep { $_->tests } @$Sections ) {
  27         52  
94 0         0 $unknown = 1;
95             }
96              
97             # Flag all sections that need count checking in advance
98 20 100       69 if ( $check_count ) {
99 19         50 foreach my $Section ( @$Sections ) {
100 44 100       118 next unless defined $Section->tests;
101 34 100 100     136 next unless $unknown or $check_count > 1;
102              
103             # Each count check is itself a test, so
104             # increment the number of tests for the section
105             # when we enable the check flag.
106 12         21 $Section->{check_count} = 1;
107 12         20 $Section->{tests}++;
108             }
109             }
110              
111 20         134 $self;
112             }
113              
114             #pod =pod
115             #pod
116             #pod =head2 class
117             #pod
118             #pod Returns the class that the test file will test
119             #pod
120             #pod =head2 filename
121             #pod
122             #pod my $filename = $File->filename;
123             #pod
124             #pod The C method returns the name of the output file that the tests
125             #pod should be written to. For example, the class C would have the
126             #pod filename value C.
127             #pod
128             #pod =head2 config
129             #pod
130             #pod my $config = $File->config;
131             #pod
132             #pod The C method returns the config object for the file, assuming that
133             #pod it has one. If more than one are found, the first will be used, and any
134             #pod additional config sections discarded.
135             #pod
136             #pod Returns a L object on success, or false if the
137             #pod file does not contain a config section.
138             #pod
139             #pod =head2 setup
140             #pod
141             #pod my @setup = $File->setup;
142             #pod
143             #pod The C method returns the setup sections from the file, in the same
144             #pod order as in the file.
145             #pod
146             #pod Returns a list of setup L objects, the null
147             #pod array C<()> if the file does not contain any setup objects.
148             #pod
149             #pod =head2 sections
150             #pod
151             #pod my @sections = $File->sections;
152             #pod
153             #pod The C method returns all normal sections from the file, in the
154             #pod same order as in the file. This may not be the order they will be written
155             #pod to the test file, for that you should see the C method.
156             #pod
157             #pod Returns a list of L objects, or the null array
158             #pod C<()> if the file does not contain any non-setup sections.
159             #pod
160             #pod =cut
161              
162 2     2 1 518 sub class { $_[0]->{class} }
163 42     42 1 2588 sub filename { $_[0]->{filename} }
164 0 0   0 1 0 sub config { $_[0]->{config} || '' }
165 65     65 1 89 sub setup { @{$_[0]->{setup}} }
  65         162  
166 243     243 1 334 sub sections { @{$_[0]->{sections}} }
  243         637  
167              
168              
169              
170              
171              
172             #####################################################################
173             # Main Methods
174              
175             #pod =pod
176             #pod
177             #pod =head2 sorted
178             #pod
179             #pod The C method returns all normal sections from the file, in an order
180             #pod that satisfies any dependencies in the sections.
181             #pod
182             #pod Returns a reference to an array of L objects,
183             #pod C<0> if the file does not contain any non-setup sections, or C on
184             #pod error.
185             #pod
186             #pod =cut
187              
188             sub sorted {
189 18     18 1 40 my $self = shift;
190 18 50       79 return $self->{sorted} if $self->{sorted};
191              
192             # Handle the simple case there there are no dependencies,
193             # so we don't have to load the dependency algorithm code.
194 18 100       88 unless ( map { $_->depends } $self->sections ) {
  37         137  
195 12         39 return $self->{sorted} = [ $self->setup, $self->sections ];
196             }
197              
198             # Create the dependency algorithm object
199 6 50       99 my $Algorithm = Algorithm::Dependency::Ordered->new(
200             source => $self,
201             ignore_orphans => 1, # Be lenient to non-existant dependencies
202             ) or return undef;
203              
204             # Pull the schedule from the algorithm. If we get an error back, it
205             # should be because there is a circular dependency.
206 6         218 my $schedule = $Algorithm->schedule_all;
207 6 50       118 unless ( $schedule ) {
208 0         0 warn " (Failed to build $self->{class} test schedule) ";
209 0         0 return undef;
210             }
211              
212             # Index the sections by name
213 6         20 my %hash = map { $_->name => $_ } grep { $_->name } $self->sections;
  18         36  
  24         51  
214              
215             # Merge together the setup, schedule, and anonymous parts into a
216             # single sorted list.
217             my @sorted = (
218             $self->setup,
219 18         37 ( map { $hash{$_} } @$schedule ),
220 6         35 ( grep { $_->anonymous } $self->sections )
  24         51  
221             );
222              
223 6         53 $self->{sorted} = \@sorted;
224             }
225              
226             #pod =pod
227             #pod
228             #pod =head2 tests
229             #pod
230             #pod If the number of tests for all of the sections within the file are known,
231             #pod then the number of tests for the entire file can also be determined.
232             #pod
233             #pod The C method determines if the number of tests can be known, and
234             #pod if so, calculates and returns the number of tests. Returns false if the
235             #pod number of tests is not known.
236             #pod
237             #pod =cut
238              
239             sub tests {
240 22     22 1 44 my $self = shift;
241 22 100       60 return $self->{tests} if exists $self->{tests};
242              
243             # Add up the tests
244 19         32 my $total = 0;
245 19         50 foreach my $Section ( $self->setup, $self->sections ) {
246             # Return undef if section has an unknown number of tests
247 37 100       93 return undef unless defined $Section->tests;
248 28         77 $total += $Section->tests;
249             }
250              
251             # If the total is zero, it's probably screwed, go with "unknown"
252 10   50     46 $self->{tests} = $total || undef;
253             }
254              
255             #pod =pod
256             #pod
257             #pod =head2 merged_content
258             #pod
259             #pod The C method generates and returns the merged contents of all
260             #pod the sections in the file, including the setup sections at the beginning. The
261             #pod method does not return the entire file, merely the part contained in the
262             #pod sections. For the full file contents, see the C method.
263             #pod
264             #pod Returns a string containing the merged section content on success, false
265             #pod if there is no content, despite the existance of sections ( which would
266             #pod have been empty ), or C on error.
267             #pod
268             #pod =cut
269              
270             sub merged_content {
271 22     22 1 41 my $self = shift;
272 22 100       81 return $self->{content} if exists $self->{content};
273              
274             # Get the sorted Test::Inline::Section objects
275 18 50       48 my $sorted = $self->sorted or return undef;
276              
277             # Prepare
278 18         44 $self->{_example_count} = 0;
279              
280             # Strip out empty sections
281 18         47 @$sorted = grep { $_->content =~ /\S/ } @$sorted;
  43         98  
282              
283             # Generate wrapped code chunks
284 18         49 my @content = map { $self->_wrap_content($_) } @$sorted;
  43         93  
285 18 50       54 return '' unless @content;
286              
287             # Merge to create the core testing code
288 18         100 $self->{content} = join "\n\n\n", @content;
289              
290             # Clean up and return
291 18         40 delete $self->{_example_count};
292 18         67 $self->{content};
293             }
294              
295             # Take a single generated section of code, and wrap it
296             # in the standard boilerplate.
297             sub _wrap_content {
298 43     43   62 my $self = shift;
299 43 50       285 my $Section = _INSTANCE(shift, 'Test::Inline::Section') or return undef;
300 43         139 my $code = $Section->content;
301              
302             # Wrap in compilation test code if an example
303 43 100       95 if ( $Section->example ) {
304 1         2 $self->{_example_count}++;
305 1         6 $code =~ s/^/ /mg;
306 1         6 $code = "eval q{\n"
307             . " my \$example = sub {\n"
308             . " local \$^W = 0;\n"
309             . $code
310             . " };\n"
311             . "};\n"
312             . "is(\$@, '', 'Example $self->{_example_count} compiles cleanly');\n";
313             }
314              
315             # Wrap in scope braces unless it is a setup section
316 43 100       132 unless ( $Section->setup ) {
317 37         106 $code = "{\n"
318             . $code
319             . "}\n";
320             }
321              
322             # Add the count-checking code if needed
323 43 100       123 if ( $Section->{check_count} ) {
324 12         25 my $increase = $Section->tests - 1;
325 12 100       31 my $were = $increase == 1 ? 'test was' : 'tests were';
326 12   100     59 my $section =
327             $code = "\$::__tc = Test::Builder->new->current_test;\n"
328             . $code
329             . "is( Test::Builder->new->current_test - \$::__tc, "
330             . ($increase || '0')
331             . ",\n"
332             . "\t'$increase $were run in the section' );\n";
333             }
334              
335             # Add the section header
336 43         144 $code = "# $Section->{begin}\n"
337             . $code;
338              
339             # Aaaaaaaand we're done
340 43         119 $code;
341             }
342              
343              
344              
345              
346              
347             #####################################################################
348             # Implement the Algorithm::Dependency::Source Interface
349             # This is used for section-level dependency.
350             # These methods, though public, are undocumented.
351              
352             # Our implementation of Algorithm::Dependency::Source->load is a no-op
353 21     21 1 505 sub load { 1 }
354              
355             # Pull a single item by name, section in the sections for it
356             sub item {
357 107     107 1 1224 my $self = shift;
358 107 50       211 my $name = shift or return undef;
359 107     310   345 List::Util::first { $_->name eq $name } $self->sections;
  310         626  
360             }
361              
362             # Return, in their original order, all the items ( named sections )
363 27     27 1 186 sub items { grep { $_->name } $_[0]->sections }
  67         133  
364              
365              
366              
367              
368              
369             #####################################################################
370             # Implement the Algorithm::Dependency::Item Interface
371             # This is used for class-level dependency.
372             # These methods, though public, are undocumented.
373              
374             sub id {
375 14     14 1 77 $_[0]->{class};
376             }
377              
378             sub depends {
379 28     28 1 144 my $self = shift;
380 0         0 my %depends = map { $_ => 1 }
381 28         58 map { $_->classes }
  46         95  
382             ($self->setup, $self->sections);
383 28         71 keys %depends;
384             }
385              
386              
387              
388              
389              
390             #####################################################################
391             # Utility Functions
392              
393             sub _duplicate_names(@) {
394 20     20   53 my $self = shift;
395 20         65 my %seen = ();
396 20         56 foreach ( map { $_->name } $self->sections ) {
  42         106  
397 42 100       107 next unless $_;
398 33 50       140 return 1 if $seen{$_}++;
399             }
400 20         90 undef;
401             }
402              
403             1;
404              
405             __END__