File Coverage

blib/lib/Basset/Test.pm
Criterion Covered Total %
statement 21 219 9.5
branch 0 84 0.0
condition 0 16 0.0
subroutine 7 20 35.0
pod 1 9 11.1
total 29 348 8.3


line stmt bran cond sub pod time code
1             package Basset::Test;
2              
3             #Basset::Test, copyright and (c) 2004, 2005, 2006 James A Thomason III
4              
5             =pod
6              
7             Inline testing for Basset modules. Or anyone else that wants it, for that matter. Built off of Test::More.
8              
9             =cut
10              
11             $VERSION = '1.01';
12              
13 3     3   24074 use Basset::Object;
  3         8  
  3         186  
14             our @ISA = Basset::Object->pkg_for_type('object');
15              
16 3     3   22 use Test::Builder;
  3         6  
  3         62  
17              
18 3     3   13 use strict;
  3         5  
  3         98  
19 3     3   14 use warnings;
  3         5  
  3         7846  
20              
21             __PACKAGE__->add_class_attr('singleton');
22              
23             =pod
24              
25             =head1 METHODS
26              
27             =over
28              
29             =item test
30              
31             ->test is the bread and butter of Basset testing. Takes one, or optionally two arguments.
32             The first argument is always the class to test. The second argument is an optional boolean flag.
33             If true, then the test will also test all super classes B.
34              
35             For more information on tests, read up on Test::More and Test::Builder.
36              
37             To embed a test:
38              
39             =pod
40            
41             =begin btest(NAMEOFTEST)
42            
43             $test->ok('true value', "Testing for true value");
44             $test->is(1, 1, "1 = 1");
45             #etc.
46            
47             =end btest(NAMEOFTEST)
48            
49             =cut
50              
51             Note that you must specify the same NAMEOFTEST on both the begin and end lines (otherwise it's a pod formatting bug), and
52             it cannot contain any spaces.
53              
54             If you append "__only" to the name of the test, then it will only be run within the test that contains it, never in a superclass.
55             For example, this is useful if you have a dummy method in a super class that always dies with the intention of having the subclass
56             populate it. You never want to run the superclass's tests from a subclass.
57              
58             You do have access to Test::More style SKIP: blocks and the $TODO variable (declare it as our $TODO up at the
59             top if you use todos).
60              
61             =end btest
62              
63             =cut
64              
65             __PACKAGE__->add_class_attr('_plan', 0);
66              
67             sub plan {
68 0     0 0   my $self = shift;
69 0 0         if (@_ == 1) {
70 0           $self->_plan(shift);
71             };
72 0           return $self->_plan;
73             }
74              
75             __PACKAGE__->add_class_attr('_output', []);
76              
77             __PACKAGE__->add_class_attr('silently');
78              
79             sub test {
80              
81 0     0 1   my $self = shift;
82 0 0         my $class = shift or return $self->error("Cannot test w/o class", "XXX");
83 0   0       my $superclasses = shift || 0;
84            
85 0           $self->singleton(Test::Builder->new);
86            
87 0           $self->_output([]);
88              
89 0           $self->singleton->no_header(1);
90 0           $self->singleton->no_ending(1);
91            
92 0 0         my @t = $self->get_all_tests($class, $superclasses) or return;
93              
94 0           my $tested = {};
95              
96 0           $self->announce('1..' . $self->plan . "\n");
97              
98 0           while (@t) {
99 0           my $n = shift @t;
100 0   0       my $t = '{' . (shift(@t) || '') . '};';
101 0           $t = "package $class;\n$t";
102 0           $t =~ s/\$test\b/$self/g;
103              
104 0           my @num = ($t =~ /$self->/g);
105 0           $t =~ s/__PACKAGE__/$class/g;
106              
107 0 0         next if $tested->{$n};
108              
109 0 0         if ($n =~ s/__only//) {
110 0 0         next if $tested->{$n}++;
111             };
112              
113              
114 0           my $num = @num;
115              
116 0 0         if ($num) {
117 0           $self->announce("# 1..$num testing $n\n");
118 0           local $@ = undef;
119 0           eval $t;
120 0 0         if ($@) {
121 0           $self->singleton->diag("failure ($@) during test suite :\n----\n$t\n----\n");
122             }
123              
124             } else {
125 0           $self->singleton->diag("no tests for $n\n");
126             };
127             };
128            
129 0           my @results = $self->singleton->summary;
130 0           my ($successes, $failures) = (0,0);
131 0           foreach my $r (@results) {
132 0 0         $r ? $successes++ : $failures++;
133             }
134            
135 0           my $total = $successes + $failures;
136            
137 0           $self->announce("# \n");
138 0           $self->announce("# " . $successes . " tests passed\n");
139 0           $self->announce("# " . $failures . " tests failed\n");
140            
141 0 0         $self->singleton->diag("Looks like you failed $failures tests of $total")
142             if $failures;
143              
144 0 0         if ($total > $self->plan) {
    0          
145 0           $self->singleton->diag("Looks like you planned " . $self->plan
146             . " tests but ran " . ($total - $self->plan) . " extra.");
147             }
148             elsif ($total < $self->plan) {
149 0           $self->singleton->diag("Looks like you planned " . $self->plan
150             . " tests but only ran $total.");
151             }
152              
153 0           my $laststream = undef;
154            
155 0 0         return 1 if $self->silently;
156            
157 0 0         if ($self->proving) {
158 0           print <<" eTESTOUTPUT";
159             select((select(\\*STDOUT), \$| = 1)[0]);
160             select((select(\\*STDERR), \$| = 1)[0]);
161             use Test::Builder;
162             my \$builder = Test::Builder->new();
163             \$builder->plan('tests' => $total);
164            
165             eTESTOUTPUT
166             }
167            
168 0           my $test_idx = 0;
169            
170 0           foreach my $item (@{$self->_output}) {
  0            
171 0           my ($stream, $msg) = @$item;
172 0 0         $test_idx++ if $msg =~ /^(not )?ok/;
173 0 0 0       if (defined $laststream && $laststream eq $stream) {
174 0           print $msg;
175             } else {
176 0 0         if ($self->proving) {
177 0 0         if ($laststream) {
178 0           print "eTESTOUTPUT\n";
179             }
180 0 0         if ($stream eq 'error' ) {
181 0           print "\$builder->current_test($test_idx);\n";
182 0           print "print STDERR <<'eTESTOUTPUT';\n";
183             } else {
184 0           print "print STDOUT <<'eTESTOUTPUT';\n";
185             }
186             }
187 0           print $msg;
188 0           $laststream = $stream;
189             }
190             }
191            
192 0 0 0       if ($laststream && $self->proving) {
193 0           print "eTESTOUTPUT\n";
194             }
195              
196 0           return 1;
197             }
198              
199             sub get_all_tests {
200              
201 0     0 0   my $self = shift;
202 0           my $class = shift;
203 0   0       my $superclasses = shift || 0;
204 0           local $@ = undef;
205              
206 0           eval "use $class";
207 0 0         if ($@) {
208 0           return $self->error("Catastrophe - could not use $class : $@", "BT-01");
209             };
210            
211 0 0         $class->exceptions(0) if $class->can('exceptions');
212            
213 0 0         my $classes = $superclasses ? Basset::Object::isa_path($class) : [$class];
214              
215 0           my @modules = ();
216 0           foreach my $class (@$classes) {
217 0           local $@ = undef;
218 0           eval "use $class";
219 0 0         if ($@) {
220 0           return $self->error("Catastrophe - could not use $class : $@", "BT-02");
221             };
222 0           my $module = $self->module_for_class($class);
223 0           push @modules, $module;
224             };
225              
226 0           my @t = ();
227            
228 0           foreach my $module (reverse @modules) {
229 0           push @t, $self->extract_tests($INC{$module});
230             };
231            
232 0           my $handleclass = 'Basset::Test::_Handle';
233            
234 0           tie *OUT, $handleclass, $self, 'output';
235 0           tie *ERR, $handleclass, $self, 'error';
236 0           tie *TODO, $handleclass, $self, 'todo';
237            
238 0           my $numtests = $self->count_tests(@t);
239            
240 0           $self->singleton->output(\*OUT);
241 0           $self->singleton->failure_output(\*ERR);
242 0           $self->singleton->todo_output(\*TODO);
243              
244 0           $self->singleton->exported_to($self->pkg);
245              
246 0 0         unless ($self->plan) {
247 0           $self->singleton->plan('tests' => $numtests);
248 0           $self->plan($numtests);
249             }
250            
251 0 0         return @t ? @t : ($class);
252             }
253              
254              
255             sub count_tests {
256 0     0 0   my $class = shift;
257 0           my @tests = @_;
258 0           my @num = ();
259            
260 0           my $skips = {};
261            
262 0           my $test_name = '';
263            
264 0           foreach my $test (@tests) {
265              
266 0 0         if ($test =~ /^\s*\$test->(?!announce)/m) {
267 0 0         next if $skips->{$test_name};
268 0           push @num, $test =~ /^\s*\$test->(?!announce)/gm;
269            
270 0 0         if ($test_name =~ s/__only$//) {
271 0           $skips->{$test_name}++;
272             }
273             } else {
274 0           $test_name = $test;
275              
276             }
277            
278             }
279              
280 0           return scalar @num;
281             }
282              
283             sub extract_tests {
284 0     0 0   my $self = shift;
285 0           my $class = $self->pkg;
286 0           my $file = shift;
287            
288 0           my $data = undef;
289            
290 0           open (my $fh, $file);
291             {
292 0           local $/ = undef;
  0            
293 0           $data = <$fh>;
294             };
295 0           close $file;
296            
297 0           $self->line_counter(1);
298              
299 0 0         if ($data =~ /^\s*\$test->plan\(([^)]+)\);\s*\n/m) {
300 0           my @plan = ();
301 0           my $plan = $1;
302 0 0         if ($1 =~ /,/) {
    0          
303 0           @plan = split(/,/, $plan);
304             } elsif ($1 =~ /=>/) {
305 0           @plan = split(/\s*=>\s*/, $plan);
306             } else {
307 0           @plan = ($plan);
308             }
309 0 0         $plan[0] =~ s/['"\s]//g if defined $plan[0];
310 0 0         $plan[1] =~ s/['"\s]//g if defined $plan[1];
311              
312 0           $plan[1] += 2;
313              
314 0           $self->singleton->plan(@plan);
315 0           $self->plan($plan[1]);
316              
317             } else {
318 0           $self->plan(0);
319             }
320              
321 0           my @tests = ();
322 0 0         if ($data =~ /^\s*=begin btest\(/m) {
323 0           $data =~ s/(\n|^\s*=begin btest\(([^)]+)\)\s*\n)/$self->numberer($1, $2)/gem;
  0            
324 0           @tests = $data =~ /^\s*=begin btest\(([^)]+)\)\s*\n(.+?)^\s*=end btest\(\1\)\s*\n/sgm;
325             } else {
326 0           $data =~ s/(\n|^\s*=begin btest( +([^\n]+))?\n)/$self->numberer($1, $2)/gem;
  0            
327 0           @tests = $data =~ /^\s*=begin btest( +[^\n]+)?\n(.+?)^\s*=end btest\s*\n/sgm;
328             }
329              
330 0           $self->test_for_strict($class, $file, \$data, \@tests);
331              
332 0           return @tests;
333             };
334              
335             __PACKAGE__->add_class_attr('line_counter');
336             __PACKAGE__->add_class_attr('proving');
337              
338             sub numberer {
339 0     0 0   my $self = shift;
340 0           my $val = shift;
341              
342 0           $self->line_counter($self->line_counter + 1);
343 0 0         if ($val ne "\n") {
344 0   0       my $name = shift || '';
345 0           $val .= "\n#line " . $self->line_counter . " $name\n";
346             }
347 0           return $val;
348             };
349              
350             sub test_for_strict {
351 0     0 0   my $self = shift;
352 0           my $class = shift;
353 0           my $file = shift;
354 0           my $data = shift;
355 0           my $tests = shift;
356            
357 0 0         my $uses_strict = $$data =~ /^\s*use\s*strict\s*;/m ? 1 : 0;
358 0 0         my $uses_warnings = $$data =~ /^\s*use\s*warnings\s*;/m ? 1 : 0;
359            
360 0           unshift @$tests, ("strict and warnings checks", <<" eoT");
361             \$test->ok($uses_strict, "uses strict");
362             \$test->ok($uses_warnings, "uses warnings");
363             eoT
364            
365 0           return 1;
366             }
367              
368             sub skip {
369 0     0 0   my $self = shift;
370 0 0         my $reason = shift or return $self->error("Cannot skip w/o reason", "XXX");
371 0   0       my $num = shift || 1;
372            
373 0           foreach (1..$num) {
374 0           $self->singleton->skip($reason);
375             }
376            
377             #cheat and bail out of the loop.
378 3     3   26 no warnings;
  3         7  
  3         481  
379            
380 0           last SKIP;
381             }
382              
383             __PACKAGE__->add_attr('todo');
384              
385             sub AUTOLOAD {
386 0     0     my $self = shift;
387 0           (my $method = $Basset::Test::AUTOLOAD) =~ s/^(.+):://;
388            
389 0           my $method_map = {
390             $method => $method,
391             'is' => 'is_eq',
392             'isnt' => 'isnt_eq',
393             };
394            
395 0           my $imethod = $method_map->{$method};
396            
397 0 0         if ($method ne 'DESTROY') {
398              
399 0 0         if (defined $self->singleton) {
400 3     3   15 no strict 'refs';
  3         7  
  3         854  
401 0           my $pkg = $self->pkg;
402            
403 0           *{$pkg . "::$method"} = sub {
404 0     0     my $self = shift;
405 0 0         if (my $singleton = $self->singleton) {
406 0           $self->singleton->$imethod(@_);
407             } else {
408 0           return $self->error("Cannot call method ($method) : no singleton", "XXX");
409             }
410 0           };
411              
412 0           return $self->$method(@_);
413             } else {
414 0           return $self->error("Cannot do anything without singleton", "XXX");
415             };
416             }
417             };
418              
419             sub announce {
420 0     0 0   my $self = shift;
421 0           my @msgs = @_;
422            
423 0           foreach my $msg (@msgs) {
424 0           push @{$self->_output}, ['output', $msg];
  0            
425             };
426             }
427              
428             1;
429              
430             package Basset::Test::_Handle;
431              
432 3     3   28 use Basset::Test;
  3         6  
  3         669  
433             our @ISA = qw(Basset::Test);
434              
435             sub PRINT {
436 0     0     my $self = shift;
437 0           my ($test, $stream) = @$self;
438 0           my @args = @_;
439              
440 0           foreach my $arg (@args) {
441 0           push @{$test->_output}, [$stream, $arg];
  0            
442             };
443             }
444              
445             sub TIEHANDLE {
446 0     0     my $class = shift;
447 0           my $test = shift;
448 0           my $stream = shift;
449              
450 0           return bless [$test, $stream], $class;
451             }
452              
453             1;