File Coverage

blib/lib/Test/Dynamic.pm
Criterion Covered Total %
statement 168 214 78.5
branch 88 156 56.4
condition 28 37 75.6
subroutine 5 5 100.0
pod 1 1 100.0
total 290 413 70.2


line stmt bran cond sub pod time code
1             # -*-cperl-*-
2             #
3             # Copyright 2006-2007 Greg Sabino Mullane
4             #
5             # Test::Dynamic allows somewhat automatic counting of your tests for Test::More
6             #
7              
8             package Test::Dynamic;
9              
10 2     2   4477 use 5.008003;
  2         9  
  2         331  
11 2     2   2506 use utf8;
  2         25  
  2         13  
12 2     2   92 use strict;
  2         7  
  2         89  
13 2     2   11 use warnings;
  2         4  
  2         6937  
14              
15             our $VERSION = '1.3.3';
16              
17             sub count_tests {
18              
19             ## START_SKIP_TESTCOUNTING
20              
21             ## Test counting notes:
22             ## The script must have a __DATA__ line
23             ## Tests are counted up until the "end" of the script: exit, __DATA__, or __END__
24             ## Simple test commands are counted as a single item: is, pass, ok, etc.
25             ## Make sure that test subs use parens: pass("xxx") not pass "xxx"
26             ## We count how many tests each subroutine runs
27             ## Some lines are conditional on a global flag for a set of tests: foo(); ## TEST_COPY
28             ## Conditional blocks can be started and stopped, pure line-by-line basis only
29             ## Example line 10: foo(); ## START_TEST_COPY line 20: ## STOP_TEST_COPY
30             ## Can also use ENV variables with START_ENV_FOO and STOP_ENV_FOO
31             ## Some lines have multipliers: foobar() ## TESTCOUNT * 5
32             ## Some have both: foobar() ## TEST_COPY TESTCOUNT + 5
33             ## To skip entire blocks entirely, use ## START_SKIP_TESTCOUNTING, then ## STOP_SKIP_TESTCOUNTING
34              
35 6     6 1 4702 my $self = shift;
36 6         12 my $arg = shift;
37              
38 6 100       39 ref $arg eq 'HASH' or die qq{Argument must be a hashref\n};;
39              
40 4   100     18 my $fh = $arg->{filehandle} || die "Need a filehandle argument\n";
41              
42 3 50       16 my $verbose_count = exists $arg->{verbose} ? $arg->{verbose} : 1;
43              
44 3         16 my @testwords = qw(is isnt ok cmp pass fail is_deeply isa_ok can_ok like unlike);
45              
46             ## no critic
47 3 50       15 if (exists $arg->{local}) {
48 3 50       11 if (ref $arg->{local} eq 'ARRAY') {
    0          
49 3         6 push @testwords, $_ for @{$arg->{local}};
  3         13  
50             }
51             elsif (ref $arg->{local} eq 'HASH') {
52 0         0 push @testwords, $_ for keys %{$arg->{local}};
  0         0  
53             }
54             }
55             ## use critic
56 3         16 my $testwords = join '|' => @testwords;
57 3         100 $testwords = qr{$testwords};
58              
59 3         12 my @sublist;
60             my %substuff;
61 0         0 my %subcount;
62 0         0 my %linemod;
63 0         0 my %lineskip;
64 0         0 my %testgroup;
65 3         5 my $firstline = 0;
66 3         6 my $lastline = 0;
67 3         9 for my $pass (1..2) {
68 6         71 seek($fh,0,0);
69 6 50       32 if ($arg->{skipuseline}) {
70 6         250 1 while <$fh> !~ /^use Test::Dynamic/;
71             }
72 6   66     37 $firstline ||= $.;
73 6         11 my $line = $firstline;
74 6         11 my $currentsub = 'MAIN';
75 6         14 my $atend = 0;
76 6         8 my $skipcounting = 0;
77 6         9 my %skipgroup;
78 6         27 T: while (<$fh>) {
79 20730         23284 $line++;
80 20730         21832 chomp;
81 20730 50       35700 if ($skipcounting) {
82 0 0       0 if (/^\s*##\s*STOP_SKIP_TESTCOUNTING/o) {
83 0         0 $skipcounting=0;
84 0 0       0 $verbose_count >= 2 and warn "Found STOP_SKIP at line $line\n";
85             }
86 0         0 next T;
87             }
88 20730 50       39796 if (/^\s*##\s*START_SKIP_TESTCOUNTING/o) {
89 0 0       0 $verbose_count >= 2 and warn "Found START_SKIP at line $line\n";
90 0         0 $skipcounting=1;
91 0         0 next T;
92             }
93              
94 20730 100       37543 if (/^\s*exit;/) {
95 18         26 $atend = 1;
96 18   66     47 $lastline ||= $line;
97 18         89 next T;
98             }
99              
100 20712 100 66     87610 last T if /^__DATA__/ or /^__END__/;
101              
102 20706 50       45294 last T if /\#\#\s*TESTSTOP/;
103              
104 20706 100       45127 next T if $lineskip{$line};
105              
106             ## Special our lines for test groups
107 19432 100       47993 if (/^our \$(TEST_\w+)\s*=\s*(\d+)/) {
108 60         198 $testgroup{$1} = $2;
109             }
110              
111              
112 19432 50 100     43489 if (/^}[^;]/ and ! /##/ and @sublist) {
      66        
113 0         0 warn qq{May have a non-closed sub at line $line\n};
114             }
115              
116             ## Starting a new subroutine?
117 19432 100       42106 if (/^\s*sub\s+([\w:_]+)/) {
118 234         476 $currentsub = $1;
119 234 50       407 $verbose_count >= 3 and warn "Sub start: $currentsub\n";
120 234 100       530 if (!exists $subcount{$currentsub}) {
121 117         480 $subcount{$currentsub} = 0;
122 117         289 $substuff{$currentsub} = {};
123             }
124 234         507 push @sublist, $1;
125             }
126              
127             ## Ending a subroutine?
128 19432 100       56102 if (/##\s+end of (\S+)/o) {
    100          
129 234 50       453 $verbose_count >= 3 and warn "Sub stop: /$1\n";
130 234         348 pop @sublist;
131 234   100     912 $currentsub = $sublist[-1] || 'MAIN';
132             }
133             ## Skip commented-out lines
134             elsif (/^\s*#/) {
135 492         1525 $lineskip{$line} = 1;
136 492         2069 next T;
137             }
138              
139 18940 100       33285 if (1 == $pass) {
140              
141             ## Gather test group information
142 9861 100       22372 if (/##.*?((?:START_|STOP_)?(?:NO)?(?:TEST|ENV)_\S+.*)/o) {
143 66         170 my $extra = $1;
144 66 50       120 $verbose_count >= 2 and warn "Test group: $extra at line $line\n";
145 66         507 while ($extra =~ m{(START_|STOP_)?(NO)?(TEST|ENV)_(\S+)}g) {
146 66   100     609 my ($startstop,$reverse,$type,$name) = ($1||'',$2||0,$3,$4);
      50        
147 66         184 my $val;
148 66 100       122 if ('TEST' eq $type) {
149 63         105 $name = "TEST_$name";
150 63 50       337 exists $testgroup{$name} or die qq{Unknown test group "$name" at line $line!\n};
151 63         111 $val = $testgroup{$name};
152             }
153             else {
154 3   100     21 $val = $ENV{$name} || 0;
155             }
156 66 50       120 if ($reverse) {
157 0 0       0 $val = $val ? 0 : 1;
158             }
159 66 100       291 if ($startstop eq 'START_') {
160 30         65 $skipgroup{$name} = $val;
161             }
162 66 100       129 if ($startstop eq 'STOP_') {
163 30         61 delete $skipgroup{$name};
164             }
165 66 100       388 if (!$val) {
166 12         27 $lineskip{$line} = 1;
167 12         56 next T;
168             }
169             }
170             }
171              
172             ## Skip this line if we are in an active skip group
173 9849         22853 for my $group (keys %skipgroup) {
174 646 100       1833 if (!$skipgroup{$group}) {
175 97         175 $lineskip{$line} = 1;
176 97         317 next T;
177             }
178             }
179              
180             ## Gather any modifiers
181 9752 100       21087 if (/##.*TESTCOUNT\s*([\+\*\-\/])\s*(\d+)/o) {
182 28         160 $linemod{$line} = [$1,$2];
183             ## Quick test for no-op adjustments
184 28 100 66     140 if (/^\s*;\s*##/o and !$lineskip{$line}) {
185 1         3 my $testcount = 0;
186 1         2 my ($y,$z) = @{$linemod{$line}};
  1         5  
187 1 50       10 if ($y eq '*') { $testcount *= $z; }
  0 50       0  
    0          
188 1         4 elsif ($y eq '-') { $testcount -= $z; }
189 0         0 elsif ($y eq '/') { $testcount /= $z; }
190 0         0 else { $testcount += $z; }
191 1         3 $subcount{$currentsub} += $testcount;
192 1         4 delete $linemod{$line};
193             }
194             }
195              
196             ## Count up simple test functions and assign them to a sub
197 9752 100       22187 if (/^\s*$testwords\s*\(/o) {
198             ## Do nothing if in MAIN and the script has ended
199 673 50 66     1560 next T if $currentsub eq 'MAIN' and $atend;
200              
201 673         730 my $testcount = 1;
202 673 100       1268 if (exists $linemod{$line}) {
203 3         9 my ($y,$z) = @{$linemod{$line}};
  3         19  
204 3 50       17 if ($y eq '*') { $testcount *= $z; }
  0 50       0  
    0          
205 3         10 elsif ($y eq '-') { $testcount -= $z; }
206 0         0 elsif ($y eq '/') { $testcount /= $z; }
207 0         0 else { $testcount += $z; }
208             }
209 673 50       1169 $verbose_count >= 2 and warn "Simple count for $currentsub by $testcount\n";
210 673         1703 $subcount{$currentsub} += $testcount;
211 673         1856 $lineskip{$line} = 1;
212             }
213              
214 9752         31563 next T;
215             } ## end first pass
216              
217 9079 50       15907 if (2 == $pass) {
218             ## At this point, we know the names of our subroutines
219             ## We count up the dependencies for each sub
220 9079         30452 while ($_ =~ /\b([\w:_]+)\s*\(/g) {
221 3245         5319 my $sub = $1;
222 3245 100       11895 next if ! exists $subcount{$sub};
223 493 50       849 $sub eq $currentsub
224             and die qq{Recursive sub "$sub" at $line: perhaps you forgot "## end of $sub"?\n};
225 493 50       777 $verbose_count >= 3 and warn "Adding $sub to $currentsub at line $line\n";
226 493         3462 $substuff{$currentsub}{$sub}{$line} = 1;
227             }
228 9079         26640 next T;
229             }
230             }
231             } ## end two passes
232              
233             ## Only worry about the ones called by MAIN
234 3         15 my %subs = (MAIN => 0);
235              
236 3         11 my %subtrace = (0 => 'MAIN');
237 3         6 for my $sub (keys %{$substuff{MAIN}}) {
  3         41  
238 52         52 for my $line (keys %{$substuff{MAIN}{$sub}}) {
  52         171  
239 136         268 $subtrace{$line} = $sub;
240             }
241             }
242              
243 3         8 my %linecount;
244 3         8 my $loopy=0;
245             {
246             ## Get a final count for each sub
247 3         5 my $stilltodo = 0;
  24         25  
248 24 50       43 $verbose_count >= 3 and warn "==Entering loop\n";
249 24         274 for my $sub (sort keys %subs) {
250 559 50       1334 next if $subs{$sub};
251 559         960 my $oldscore = $subcount{$sub};
252 559 100       513 if (keys %{$substuff{$sub}}) {
  559         1396  
253 119         123 $stilltodo++;
254 119 50       211 $verbose_count >= 3 and warn "Need final score for $sub (currently $oldscore)\n";
255             }
256             else {
257 440 50       744 $verbose_count >= 3 and warn "Skipping $sub, has no dependencies\n";
258             }
259 559         699 for my $isub (keys %{$substuff{$sub}}) {
  559         1806  
260 321 100       1027 $subs{$isub} = 0 if !exists $subs{$isub};
261             ## Does this inner have a raw score?
262 321         285 my $subitems = keys %{$substuff{$isub}};
  321         734  
263 321 50       602 $verbose_count >= 3 and warn " Checking inner sub $isub ($subcount{$isub}) Items=$subitems\n";
264 321 100       796 next if $subitems;
265 146         151 for my $line (sort {$a<=>$b} keys %{$substuff{$sub}{$isub}}) {
  658         974  
  146         733  
266 463         1513 my $basescore = $subcount{$isub};
267 463         651 $linecount{$line} = $basescore;
268 463 100       1009 if (exists $linemod{$line}) {
269 24         25 my ($y,$z) = @{$linemod{$line}};
  24         65  
270 24 50       51 if ($y eq '*') { $basescore *= $z; }
  24 0       37  
    0          
271 0         0 elsif ($y eq '-') { $basescore -= $z; }
272 0         0 elsif ($y eq '/') { $basescore /= $z; }
273 0         0 else { $basescore += $z; }
274             }
275 463 100 100     1831 if ($sub ne 'MAIN' or ($line < $lastline)) {
276 460         1048 $subcount{$sub} += $basescore;
277 460         1807 $linecount{$line} = $basescore;
278 460 50       1084 $verbose_count >= 3 and warn " Boost count for $sub by $basescore due to line $line\n";
279             }
280             }
281             ## Remove from the list
282 146 50       349 $verbose_count >= 3 and warn " Finished with $isub, so removed from list for $sub\n";
283 146         792 delete $substuff{$sub}{$isub};
284             }
285 559 50 33     1628 $verbose_count >= 3 and $subcount{$sub} != $oldscore and warn "New final score for $sub: $subcount{$sub}\n";
286             } ## end each sub
287              
288 24 50       83 if ($loopy++ > 100) {
289 0         0 die "Too many loops while trying to figure out test counts";
290             }
291 24 100       54 redo if $stilltodo;
292             }
293              
294 3 50       11 if ($verbose_count >= 1) {
295 0         0 my ($maxline,$maxcount,$maxsub,$maxmod,$maxfinal) = (1,1,3,5,1);
296 0         0 my @niceline;
297 0         0 for my $line (sort {$a<=>$b} keys %subtrace) {
  0         0  
298 0         0 my $sub = $subtrace{$line};
299 0 0       0 my $mod = exists $linemod{$line} ? " $linemod{$line}[0] $linemod{$line}[1]" : '';
300 0 0       0 my $count = exists $linemod{$line} ? $linecount{$line} : '';
301 0 0       0 my $final = exists $linemod{$line} ? $linecount{$line} : $subcount{$sub};
302 0 0       0 $maxline = length($line) if length($line) > $maxline;
303 0 0       0 $maxcount = length($count) if length($count) > $maxcount;
304 0 0       0 $maxsub = length($sub) if length($sub) > $maxsub;
305 0 0       0 $maxmod = length($mod) if length($mod) > $maxmod;
306 0 0       0 $maxfinal = length($final) if length($final) > $maxfinal;
307 0         0 push @niceline, [$line,$count,$mod,$final,$sub];
308             }
309 0         0 my $total = -$niceline[0]->[3];
310 0         0 warn "TEST COUNT:\n";
311 0         0 for (@niceline) {## 20 * 4 = 80
312 0         0 $total += $_->[3];
313 0         0 splice @$_,4,0,$total;
314 0         0 my $msg = sprintf
315             "Line %${maxline}d: (%${maxcount}s%-${maxmod}s = %${maxfinal}d) [%${maxfinal}d] %-${maxsub}s\n", @$_;
316 0         0 warn $msg;
317             }
318             }
319              
320 3 50       9 $verbose_count >= 1 and warn "Total tests: $subcount{MAIN}\n";
321              
322 3         690 return $subcount{MAIN};
323              
324             ## STOP_SKIP_TESTCOUNTING
325              
326             } ## end of count_tests
327              
328             1;
329              
330             __END__