File Coverage

inc/Test/Class.pm
Criterion Covered Total %
statement 228 291 78.3
branch 60 144 41.6
condition 7 18 38.8
subroutine 45 55 81.8
pod 15 15 100.0
total 355 523 67.8


line stmt bran cond sub pod time code
1 2     2   2561 #line 1
  2         4  
  2         59  
2 2     2   8 use strict;
  2         4  
  2         39  
3 2     2   40 use warnings;
  2         5  
  2         79  
4             use 5.006;
5              
6             package Test::Class;
7 2     2   70158  
  2         11299  
  2         13  
8 2     2   60 use Attribute::Handlers;
  2         4  
  2         130  
9 2     2   1753 use Carp;
  2         6362  
  2         63  
10 2     2   51958 use MRO::Compat;
  2         8269  
  2         146  
11 2     2   1745 use Storable qw(dclone);
  2         6  
  2         61  
12 2     2   3423 use Test::Builder;
  2         1227  
  2         93  
13             use Test::Class::MethodInfo;
14              
15             our $VERSION = '0.36';
16              
17             my $Check_block_has_run;
18 2     2   14 {
  2         4  
  2         127  
19 2     2   54816 no warnings 'void';
20             CHECK { $Check_block_has_run = 1 };
21             }
22 2     2   10  
  2         4  
  2         164  
23 2     2   10 use constant NO_PLAN => "no_plan";
  2         4  
  2         79  
24 2     2   9 use constant SETUP => "setup";
  2         4  
  2         77  
25 2     2   10 use constant TEST => "test";
  2         4  
  2         110  
26 2     2   9 use constant TEARDOWN => "teardown";
  2         11  
  2         77  
27 2     2   9 use constant STARTUP => "startup";
  2         8  
  2         1350  
28             use constant SHUTDOWN => "shutdown";
29              
30              
31 273     273 1 530 our $Current_method = undef;
32             sub current_method { $Current_method };
33              
34              
35 0     0 1 0 my $Builder = Test::Builder->new;
36             sub builder { $Builder };
37              
38              
39             my $Tests = {};
40             my @Filters = ();
41              
42              
43             my %_Test; # inside-out object field indexed on $self
44              
45 2     2   5 sub DESTROY {
46 2         201 my $self = shift;
47             delete $_Test{ $self };
48             };
49              
50 249     249   355 sub _test_info {
51 249 100       1411 my $self = shift;
52             return ref($self) ? $_Test{$self} : $Tests;
53             };
54              
55 211     211   314 sub _method_info {
56 211         417 my ($self, $class, $method) = @_;
57             return( _test_info($self)->{$class}->{$method} );
58             };
59              
60 38     38   54 sub _methods_of_class {
61 38 50       64 my ( $self, $class ) = @_;
62             my $test_info = _test_info($self)
63             or die "Test::Class internals seem confused. Did you override "
64 38         45 . "new() in a sub-class or via multiple inheritence?\n";
  38         229  
65             return values %{ $test_info->{$class} };
66             };
67              
68 78   100 78   177 sub _parse_attribute_args {
69 78         79 my $args = shift || '';
70             my $num_tests;
71 78         131 my $type;
72 78         190 $args =~ s/\s+//sg;
73 69 50       249 foreach my $arg (split /=>/, $args) {
    0          
74 69         551 if (Test::Class::MethodInfo->is_num_tests($arg)) {
75             $num_tests = $arg;
76 0         0 } elsif (Test::Class::MethodInfo->is_method_type($arg)) {
77             $type = $arg;
78 0         0 } else {
79             die 'bad attribute args';
80             };
81 78         297 };
82             return( $type, $num_tests );
83             };
84              
85 78     78   93 sub _is_public_method {
86 78         85 my ($class, $name) = @_;
  78         362  
87 78         97 my @parents = @{mro::get_linear_isa($class)};
88 78         125 shift @parents;
89 78 50       785 foreach my $parent_class ( @parents ) {
90 0 0       0 return unless $parent_class->can( $name );
91             return if _method_info( $class, $parent_class, $name );
92 0         0 }
93             return 1;
94             }
95              
96 78     78 1 19125 sub Test : ATTR(CODE,RAWDATA) {
97 78 50       189 my ($class, $symbol, $code_ref, $attr, $args) = @_;
98 0         0 if ($symbol eq "ANON") {
99             warn "cannot test anonymous subs - you probably loaded a Test::Class too late (after the CHECK block was run). See 'A NOTE ON LOADING TEST CLASSES' in perldoc Test::Class for more details\n";
100 78         94 } else {
  78         133  
101 78 50       177 my $name = *{$symbol}{NAME};
102             warn "overriding public method $name with a test method in $class\n"
103 78 50       109 if _is_public_method( $class, $name );
  78         139  
104             eval { $class->add_testinfo($name, _parse_attribute_args($args)) }
105             || warn "bad test definition '$args' in $class->$name\n";
106 2     2   11 };
  2         3  
  2         18  
107             };
108              
109 9     9 1 2414 sub Tests : ATTR(CODE,RAWDATA) {
110 9   100     30 my ($class, $symbol, $code_ref, $attr, $args) = @_;
111 9         21 $args ||= 'no_plan';
112 2     2   884 Test( $class, $symbol, $code_ref, $attr, $args );
  2         2  
  2         8  
113             };
114              
115 78     78 1 119 sub add_testinfo {
116 78         229 my($class, $name, $type, $num_tests) = @_;
117             $Tests->{$class}->{$name} = Test::Class::MethodInfo->new(
118             name => $name,
119             num_tests => $num_tests,
120             type => $type,
121             );
122             }
123              
124 192     192   294 sub _class_of {
125 192 100       816 my $self = shift;
126             return ref $self ? ref $self : $self;
127             }
128              
129 2     2 1 6 sub new {
130 2         9 my $proto = shift;
131 2 50       10 my $class = _class_of( $proto );
132 2         11 $proto = {} unless ref($proto);
133 2         966 my $self = bless {%$proto, @_}, $class;
134 2         10 $_Test{$self} = dclone($Tests);
135             return($self);
136             };
137              
138 22     22   51 sub _get_methods {
139 22         44 my ( $self, @types ) = @_;
140             my $test_class = _class_of( $self );
141 22   50     107
142 22         28 my $test_method_regexp = $ENV{ TEST_METHOD } || '.*';
  22         154  
143 22 50       53 my $method_regexp = eval { qr/\A$test_method_regexp\z/ };
144             die "TEST_METHOD ($test_method_regexp) is not a valid regexp: $@" if $@;
145 22         39
146 22         24 my %methods = ();
  22         102  
147             foreach my $class ( @{mro::get_linear_isa( $test_class )} ) {
148 38         169 FILTER:
149 624         3892 foreach my $info ( _methods_of_class( $self, $class ) ) {
150             my $name = $info->name;
151 624 50       2740  
152             if ( $info->type eq TEST ) {
153             # determine if method is filtered, true if *any* filter
154 624         2804 # returns false.
155 0 0       0 foreach my $filter ( @Filters ) {
156             next FILTER unless $filter->( $class, $name );
157             }
158             }
159 624         934  
160 780 100       2542 foreach my $type ( @types ) {
161 156 50 33     2050 if ( $info->is_type( $type ) ) {
162             $methods{ $name } = 1
163             unless $type eq TEST && $name !~ $method_regexp;
164             }
165             };
166             };
167             };
168 22         160  
169 22         239 my @methods = sort keys %methods;
170             return @methods;
171             };
172              
173 4     4   5 sub _num_expected_tests {
174 4 50       11 my $self = shift;
175 0 0       0 if (my $reason = $self->SKIP_CLASS ) {
176             return $reason eq "1" ? 0 : 1;
177 4         11 };
178             my @startup_shutdown_methods =
179 4         10 _get_methods($self, STARTUP, SHUTDOWN);
180             my $num_startup_shutdown_methods =
181 4 50       14 _total_num_tests($self, @startup_shutdown_methods);
182 4         12 return(NO_PLAN) if $num_startup_shutdown_methods eq NO_PLAN;
183 4         12 my @fixture_methods = _get_methods($self, SETUP, TEARDOWN);
184 4 50       12 my $num_fixture_tests = _total_num_tests($self, @fixture_methods);
185 4         12 return(NO_PLAN) if $num_fixture_tests eq NO_PLAN;
186 4         15 my @test_methods = _get_methods($self, TEST);
187 4 100       16 my $num_tests = _total_num_tests($self, @test_methods);
188 3         13 return(NO_PLAN) if $num_tests eq NO_PLAN;
189             return($num_startup_shutdown_methods + $num_tests + @test_methods * $num_fixture_tests);
190             };
191              
192 2     2 1 5 sub expected_tests {
193 2         5 my $total = 0;
194 4 50 0     13 foreach my $test (@_) {
    0          
195 4         13 if ( _isa_class( __PACKAGE__, $test ) ) {
196 4 100       16 my $n = _num_expected_tests($test);
197 3         8 return NO_PLAN if $n eq NO_PLAN;
198             $total += $n;
199 0         0 } elsif ( defined $test && $test =~ m/^\d+$/ ) {
200             $total += $test;
201 0 0       0 } else {
202 0         0 $test = 'undef' unless defined $test;
203             croak "$test is not a Test::Class or an integer";
204             };
205 1         3 };
206             return $total;
207             };
208              
209 168     168   382 sub _total_num_tests {
210 168         352 my ($self, @methods) = @_;
211 168         272 my $class = _class_of( $self );
212 168         292 my $total_num_tests = 0;
213 211         227 foreach my $method (@methods) {
  211         1055  
214 211         483 foreach my $class (@{mro::get_linear_isa($class)}) {
215 211 50       560 my $info = _method_info($self, $class, $method);
216 211         812 next unless $info;
217 211 100       1557 my $num_tests = $info->num_tests;
218 206         345 return(NO_PLAN) if ($num_tests eq NO_PLAN);
219 206 50       835 $total_num_tests += $num_tests;
220             last unless $num_tests =~ m/^\+/
221             };
222 163         778 };
223             return($total_num_tests);
224             };
225              
226 78     78   135 sub _has_no_tests {
227 78         189 my ( $self, $method ) = @_;
228             return _total_num_tests( $self, $method ) eq '0';
229             }
230              
231 78     78   129 sub _all_ok_from {
232 78         250 my ($self, $start_test) = @_;
233 78 50       232 my $current_test = $Builder->current_test;
234 78         437 return(1) if $start_test == $current_test;
235 78 50       319 my @results = ($Builder->summary)[$start_test .. $current_test-1];
  294         635  
236 78         1173 foreach my $result (@results) { return(0) unless $result };
237             return(1);
238             };
239              
240 0     0   0 sub _exception_failure {
241 0         0 my ($self, $method, $exception, $tests) = @_;
242 0         0 local $Test::Builder::Level = 3;
243 0 0 0     0 my $message = $method;
244             $message .= " (for test method '$Current_method')"
245 0         0 if defined $Current_method && $method ne $Current_method;
246 0         0 _show_header($self, @$tests);
247             $Builder->ok(0, "$message died ($exception)");
248             };
249              
250 78     78   143 sub _run_method {
251 78         227 my ($self, $method, $tests) = @_;
252 78         109 my $num_start = $Builder->current_test;
253 78         152 my $skip_reason;
254 2     2   2820 my $original_ok = \&Test::Builder::ok;
  2         3  
  2         2892  
255             no warnings;
256 274     274   449 local *Test::Builder::ok = sub {
257 274         476 my ($builder, $test, $description) = @_;
258 274 100       669 local $Test::Builder::Level = $Test::Builder::Level+1;
259 273         949 unless ( defined($description) ) {
260 273         709 $description = $self->current_method;
261             $description =~ tr/_/ /;
262 274         898 };
263 274 50       583 my $is_ok = $original_ok->($builder, $test, $description);
264 0         0 unless ( $is_ok ) {
265 0         0 my $class = ref $self;
266             $Builder->diag( " (in $class->$method)" );
267 274         855 };
268 78         490 return $is_ok;
269 78         135 };
  78         430  
270 78 100       1584 $skip_reason = eval {$self->$method};
271 78         149 $skip_reason = $method unless $skip_reason;
272 78 50       200 my $exception = $@;
273 78         334 chomp($exception) if $exception;
274 78         286 my $num_done = $Builder->current_test - $num_start;
275 78 100       271 my $num_expected = _total_num_tests($self, $method);
276 78 100       219 $num_expected = $num_done if $num_expected eq NO_PLAN;
    50          
277 64 50       175 if ($num_done == $num_expected) {
278             _exception_failure($self, $method, $exception, $tests)
279             unless $exception eq '';
280 0         0 } elsif ($num_done > $num_expected) {
281 0         0 my $class = ref $self;
282             $Builder->diag("expected $num_expected test(s) in $class\::$method, $num_done completed\n");
283 14         58 } else {
284 20 50       57 until (($Builder->current_test - $num_start) >= $num_expected) {
285 0         0 if ($exception ne '') {
286 0         0 _exception_failure($self, $method, $exception, $tests);
287 0         0 $skip_reason = "$method died";
288             $exception = '';
289 20         92 } else {
290             $Builder->skip( $skip_reason );
291             };
292             };
293 78         230 };
294             return(_all_ok_from($self, $num_start));
295             };
296              
297 78     78   181 sub _show_header {
298 78 100       268 my ($self, @tests) = @_;
299 2         13 return if $Builder->has_plan;
300 2 100       11 my $num_tests = Test::Class->expected_tests(@tests);
301 1         6 if ($num_tests eq NO_PLAN) {
302             $Builder->no_plan;
303 1         6 } else {
304             $Builder->expected_tests($num_tests);
305             };
306             };
307              
308             my %SKIP_THIS_CLASS = ();
309              
310 6     6 1 13 sub SKIP_CLASS {
311 6 50       20 my $class = shift;
312 6         34 $SKIP_THIS_CLASS{ $class } = shift if @_;
313             return $SKIP_THIS_CLASS{ $class };
314             };
315              
316 6     6   15 sub _isa_class {
317 6 50       19 my ( $class, $object_or_class ) = @_;
318 6 50       18 return unless defined $object_or_class;
319 6         10 return if $object_or_class eq 'Contextual::Return::Value';
320 6 50       98 return eval {
321             $object_or_class->isa( $class ) and $object_or_class->can( 'runtests' )
322             };
323             }
324              
325 2     2   4 sub _test_classes {
326 2         3 my $class = shift;
  2         16  
327             return( @{mro::get_isarev($class)}, $class );
328             };
329              
330 2 50   2 1 446 sub runtests {
331             die "Test::Class was loaded too late (after the CHECK block was run). See 'A NOTE ON LOADING TEST CLASSES' in perldoc Test::Class for more details\n"
332 2         7 unless $Check_block_has_run;
333 2 50 33     24 my @tests = @_;
334 2         5 if (@tests == 1 && !ref($tests[0])) {
335 2         13 my $base_class = shift @tests;
336             @tests = _test_classes( $base_class );
337 2         6 };
338 2         7 my $all_passed = 1;
339             TEST_OBJECT: foreach my $t (@tests) {
340 2 50       13 # SHOULD ALSO ALLOW NO_PLAN
341 2 50       9 next if $t =~ m/^\d+$/;
342             croak "$t is not Test::Class or integer"
343 2 50       18 unless _isa_class( __PACKAGE__, $t );
344 0         0 if (my $reason = $t->SKIP_CLASS) {
345 0 0       0 _show_header($t, @tests);
346             $Builder->skip( $reason ) unless $reason eq "1";
347 2 50       30 } else {
348 2         9 $t = $t->new unless ref($t);
349 0 0       0 foreach my $method (_get_methods($t, STARTUP)) {
350 0         0 _show_header($t, @tests) unless _has_no_tests($t, $method);
351 0 0       0 my $method_passed = _run_method($t, $method, \@tests);
352 0 0       0 $all_passed = 0 unless $method_passed;
353             next TEST_OBJECT unless $method_passed;
354 2         8 };
355 2         8 my $class = ref($t);
356 2         9 my @setup = _get_methods($t, SETUP);
357 2         7 my @teardown = _get_methods($t, TEARDOWN);
358 78         177 foreach my $test (_get_methods($t, TEST)) {
359 78 50       295 local $Current_method = $test;
360 78         171 $Builder->diag("\n$class->$test") if $ENV{TEST_VERBOSE};
361 78 50       220 foreach my $method (@setup, $test, @teardown) {
362 78 50       267 _show_header($t, @tests) unless _has_no_tests($t, $method);
363             $all_passed = 0 unless _run_method($t, $method, \@tests);
364             };
365 2         29 };
366 0 0       0 foreach my $method (_get_methods($t, SHUTDOWN)) {
367 0 0       0 _show_header($t, @tests) unless _has_no_tests($t, $method);
368             $all_passed = 0 unless _run_method($t, $method, \@tests);
369             }
370             }
371 2         28 }
372             return($all_passed);
373             };
374              
375 0     0     sub _find_calling_test_class {
376 0           my $level = 0;
377 0 0         while (my $class = caller(++$level)) {
378 0 0         next if $class eq __PACKAGE__;
379             return $class if _isa_class( __PACKAGE__, $class );
380 0           };
381             return(undef);
382             };
383              
384 0     0 1   sub num_method_tests {
385 0 0         my ($self, $method, $n) = @_;
386             my $class = _find_calling_test_class( $self )
387 0 0         or croak "not called in a Test::Class";
388             my $info = _method_info($self, $class, $method)
389 0 0         or croak "$method is not a test method of class $class";
390 0           $info->num_tests($n) if defined($n);
391             return( $info->num_tests );
392             };
393              
394 0     0 1   sub num_tests {
395 0 0         my $self = shift;
396             croak "num_tests need to be called within a test method"
397 0           unless defined $Current_method;
398             return( $self->num_method_tests( $Current_method, @_ ) );
399             };
400              
401 0     0 1   sub BAILOUT {
402 0           my ($self, $reason) = @_;
403             $Builder->BAILOUT($reason);
404             };
405              
406 0 0   0     sub _last_test_if_exiting_immediately {
407             $Builder->expected_tests || $Builder->current_test+1
408             };
409              
410 0     0 1   sub FAIL_ALL {
411 0           my ($self, $reason) = @_;
412 0 0         my $last_test = _last_test_if_exiting_immediately();
413 0           $Builder->expected_tests( $last_test ) unless $Builder->has_plan;
414 0           $Builder->ok(0, $reason) until $Builder->current_test >= $last_test;
415 0 0         my $num_failed = grep( !$_, $Builder->summary );
416             exit( $num_failed < 254 ? $num_failed : 254 );
417             };
418              
419 0     0 1   sub SKIP_ALL {
420 0 0         my ($self, $reason) = @_;
421 0           $Builder->skip_all( $reason ) unless $Builder->has_plan;
422 0           my $last_test = _last_test_if_exiting_immediately();
423             $Builder->skip( $reason )
424 0           until $Builder->current_test >= $last_test;
425             exit(0);
426             }
427              
428 0     0 1   sub add_filter {
429             my ( $class, $cb ) = @_;
430 0 0          
431 0           if ( not ref $cb eq 'CODE' ) {
432             croak "Filter isn't a code-ref"
433             }
434 0            
435             push @Filters, $cb;
436             }
437              
438             1;
439              
440             __END__
441