File Coverage

lib/Test/Classy/Base.pm
Criterion Covered Total %
statement 198 202 98.0
branch 64 72 88.8
condition 6 10 60.0
subroutine 35 36 97.2
pod 6 6 100.0
total 309 326 94.7


line stmt bran cond sub pod time code
1             package Test::Classy::Base;
2            
3 8     8   63053 use strict;
  8         21  
  8         257  
4 8     8   51 use warnings;
  8         17  
  8         272  
5 8     8   42 use base qw( Class::Data::Inheritable );
  8         18  
  8         7733  
6 8     8   3045 use Test::More ();
  8         17  
  8         139  
7 8     8   6930 use Data::Dump;
  8         60776  
  8         702  
8 8     8   1184 use Class::Inspector;
  8         4305  
  8         237  
9 8     8   8645 use Encode;
  8         102791  
  8         862  
10 8     8   6473 use Term::Encoding;
  8         5229  
  8         438  
11 8     8   435 use Test::Classy::Util;
  8         19  
  8         739  
12            
13             my $ENCODE = eval { find_encoding(Term::Encoding::get_encoding()) };
14            
15             sub import {
16 45     45   11689 my ($class, @flags) = @_;
17 45         97 my $caller = caller;
18            
19 45 100       189 if ( $class ne __PACKAGE__ ) {
20 25 100       272 return unless grep { $_ eq 'base' } @flags;
  2         10  
21             }
22            
23 8     8   45 no strict 'refs';
  8         16  
  8         5220  
24 22         37 push @{"$caller\::ISA"}, $class;
  22         413  
25            
26             # XXX: not sure why but $TODO refused to be exported well
27 22         51 *{"$caller\::TODO"} = \$Test::More::TODO;
  22         140  
28            
29 22         67 foreach my $export ( @Test::More::EXPORT ) {
30 616 100       1534 next if $export =~ /^\W/;
31 594         613 *{"$caller\::$export"} = \&{"Test::More\::$export"};
  594         2887  
  594         1524  
32             }
33            
34 22 100       77 if ( grep { $_ eq 'ignore' or $_ eq 'ignore_me' } @flags ) {
  4 100       32  
35 2         3 ${"$caller\::_ignore_me"} = 1;
  2         14  
36             }
37            
38 22 100       226 if ( $class eq __PACKAGE__ ) {
39 20         261 $caller->mk_classdata( _tests => {} );
40 20         558 $caller->mk_classdata( _plan => 0 );
41 20         425 $caller->mk_classdata( test_name => '' );
42             }
43             }
44            
45             sub MODIFY_CODE_ATTRIBUTES {
46 56     56   31026 my ($class, $code, @attrs) = @_;
47            
48 56         80 my %stash;
49 56         99 foreach my $attr ( @attrs ) {
50 76 100       472 if ( $attr eq 'Test' ) {
    100          
    100          
51 23         78 $stash{plan} = 1;
52             }
53             elsif ( my ($dummy, $plan) = $attr =~ /^Tests?\((['"]?)(\d+|no_plan)\1\)$/ ) {
54 29         102 $stash{plan} = $plan;
55             }
56             elsif ( my ($type, $dummy2, $reason) = $attr =~ /^(Skip|TODO)(?:\((['"]?)(.+)\)\2)?$/ ) {
57 19         60 $stash{$type} = $reason;
58             }
59             else {
60 5         20 $stash{$attr} = 1;
61             }
62             }
63 56 100       169 return unless $stash{plan};
64            
65 52 100       140 if ( $stash{plan} eq 'no_plan' ) {
66 3 100       11 Test::More::plan 'no_plan' unless Test::Classy::Util::_planned();
67 3         67 $stash{plan} = 0;
68             }
69            
70 52         211 $class->_plan( $class->_plan + $stash{plan} );
71            
72 52         740 $stash{code} = $code;
73            
74             # At this point, the name looks like CODE(...)
75             # we'll make it human-readable later, with class inspection
76 52         172 $class->_tests->{$code} = \%stash;
77            
78 52         500 return;
79             }
80            
81             sub _limit {
82 2     2   5 my ($class, @monikers) = @_;
83            
84 2         16 my $tests = $class->_tests;
85 2         14 my $reason = 'limited by attributes';
86            
87 2         6 LOOP:
88 2         3 foreach my $name ( keys %{ $tests } ) {
89 3         4 foreach my $moniker ( @monikers ) {
90 3 100       15 next LOOP if exists $tests->{$name}->{$moniker};
91             }
92 2         8 $tests->{$name}->{Skip} = $reason;
93             }
94             }
95            
96             sub _should_be_ignored {
97 18     18   29 my $class = shift;
98            
99 8     8   47 { no strict 'refs';
  8         15  
  8         4369  
  18         28  
100 18 100       24 if ( ${"$class\::_ignore_me"} ) {
  18         188  
101 2         11 SKIP: {
102 2         3 Test::More::skip 'a base class, not to test', $class->_plan;
103             }
104 2         694 return 1;
105             }
106             }
107             }
108            
109             sub _find_symbols {
110 16     16   26 my $class = shift;
111            
112             # to allow multibyte method names
113 16         79 local $Class::Inspector::RE_IDENTIFIER = qr/.+/s;
114            
115 16         100 my $methods = Class::Inspector->methods($class, 'expanded');
116            
117 16         17870 my %symbols;
118 16         28 foreach my $entry ( @{ $methods } ) {
  16         45  
119 1097         2442 $symbols{$entry->[3]} = $entry->[2]; # coderef to sub name
120             }
121 16         881 return %symbols;
122             }
123            
124             sub _run_tests {
125 18     18   41 my ($class, @args) = @_;
126            
127 18 100       117 return if $class->_should_be_ignored;
128            
129 16         115 my %sym = $class->_find_symbols;
130            
131 16         190 $class->test_name( undef );
132            
133 16         371 $class->initialize(@args);
134            
135 16         70 my $tests = $class->_tests;
136            
137 16         106 foreach my $name ( sort { $sym{$a} cmp $sym{$b} } grep { $sym{$_} } keys %{ $tests } ) {
  32         71  
  43         110  
  16         52  
138 37 50       566 next if $sym{$name} =~ /^(?:initialize|finalize)$/;
139            
140 37 100       177 if ( my $reason = $class->_should_skip_this_class ) {
141 6         9 SKIP: { Test::More::skip $class->message($reason), $tests->{$name}->{plan}; }
  6         27  
142 6         2228 next;
143             }
144            
145 31         150 $class->_run_test( $tests->{$name}, $sym{$name}, @args );
146             }
147            
148 16         142 $class->finalize(@args);
149             }
150            
151             sub _run_test {
152 31     31   64 my ($class, $test, $name, @args) = @_;
153            
154 31         170 $class->test_name( $name );
155 31         339 $class->_clear_skip_flag;
156            
157 31 100       136 if ( exists $test->{TODO} ) {
    100          
158 7 100       20 my $reason = defined $test->{TODO}
159             ? $test->{TODO}
160             : "$name is not implemented";
161            
162 7 100       21 if ( exists $test->{Skip} ) { # todo skip
163 1         3 TODO: {
164 1         2 Test::More::todo_skip $class->message($reason), $test->{plan};
165             }
166             }
167             else {
168 8         4018 TODO: {
169 8     8   51 no strict 'refs';
  8         16  
  6         8  
170 6         33 local ${"$class\::TODO"} = $class->message($reason); # perl 5.6.2 hates this
  6         22  
171            
172 6         24 $class->__run_test($test, @args);
173             }
174             }
175 7         574 return;
176             }
177             elsif ( exists $test->{Skip} ) {
178 5 100       18 my $reason = defined $test->{Skip}
179             ? $test->{Skip}
180             : "skipped $name";
181 5         6 SKIP: { Test::More::skip $class->message($reason), $test->{plan}; }
  5         19  
182 5         2232 return;
183             }
184            
185 19         91 $class->__run_test($test, @args);
186             }
187            
188             sub __run_test {
189 25     25   45 my ($class, $test, @args) = @_;
190            
191 25         119 my $current = Test::Classy::Util::_current_test();
192            
193 25         514 local $@;
194 25         40 eval { $test->{code}($class, @args); };
  25         108  
195 25 100       12029 if ( $@ ) {
196 3         9 my $done = Test::Classy::Util::_current_test() - $current;
197 3         29 my $rest = $test->{plan} - $done;
198 3 50       9 if ( $rest ) {
199 3 50       8 if ( exists $test->{TODO} ) {
200 3 50       8 my $reason = defined $test->{TODO}
201             ? $test->{TODO}
202             : 'not implemented';
203 3         18 TODO: {
204 3         5 Test::More::todo_skip( $class->message("$reason: $@"), $rest );
205             }
206             }
207             else {
208 0         0 for ( 1 .. $rest ) {
209 0         0 Test::More::ok( 0, $class->message($@) );
210             }
211             }
212             }
213             }
214            
215 25 100       1713 if ( my $reason = $class->_is_skipped ) {
216 3         7 my $done = Test::Classy::Util::_current_test() - $current;
217 3         25 my $rest = $test->{plan} - $done;
218 3 100       9 if ( $rest ) {
219 2         5 for ( 1 .. $rest ) {
220 2         7 Test::More->builder->skip( $class->message($reason) );
221             }
222             }
223             }
224             }
225            
226             sub skip_this_class {
227 4     4 1 40 my ($class, $reason) = @_;
228            
229 8     8   61 no strict 'refs';
  8         31  
  8         1114  
230 4   100     18 ${"$class\::_skip_this_class"} = $reason || 'for some reason';
  4         26  
231             }
232            
233             *skip_the_rest = \&skip_this_class;
234            
235             sub _should_skip_this_class {
236 37     37   57 my $class = shift;
237            
238 8     8   47 no strict 'refs';
  8         15  
  8         622  
239 37         42 return ${"$class\::_skip_this_class"};
  37         282  
240             }
241            
242             sub skip_this_test {
243 3     3 1 7 my ($class, $reason) = @_;
244            
245 8     8   53 no strict 'refs';
  8         16  
  8         716  
246 3   100     10 ${"$class\::_skip_this_test"} = $reason || 'for some reason';
  3         14  
247             }
248            
249             *abort_this_test = \&skip_this_test;
250            
251             sub _clear_skip_flag {
252 31     31   38 my $class = shift;
253            
254 8     8   46 no strict 'refs';
  8         15  
  8         537  
255 31         42 ${"$class\::_skip_this_test"} = '';
  31         149  
256             }
257            
258             sub _is_skipped {
259 25     25   40 my $class = shift;
260            
261 8     8   51 no strict 'refs';
  8         18  
  8         3462  
262 25         82 return ${"$class\::_skip_this_test"};
  25         229  
263             }
264            
265             sub dump {
266 0     0 1 0 my $class = shift;
267 0         0 Test::More::diag( Data::Dump::dump( @_ ) );
268             }
269            
270             sub message {
271 56     56 1 1378 my ($class, $message) = @_;
272            
273 56         179 $message = $class->_prepend_class_name( $class->_prepend_test_name( $message ) );
274            
275 56 50 33     445 $message = $ENCODE->encode($message) if $ENCODE && $INC{'utf8.pm'};
276            
277 56         224 return $message;
278             }
279            
280             sub _prepend_test_name {
281 56     56   75 my ($class, $message) = @_;
282            
283 56 100       127 $message = '' unless defined $message;
284            
285 56 100       155 if ( my $name = $class->test_name ) {
286 50 50 33     709 $name = decode_utf8($name) if $ENCODE && $INC{'utf8.pm'};
287 50 100       1902 $message = "$name: $message" unless $message =~ /\b$name\b/;
288             }
289            
290 56         295 return $message;
291             }
292            
293             sub _prepend_class_name {
294 56     56   87 my ($class, $message) = @_;
295            
296 56 50       168 $message = '' unless defined $message;
297            
298 56 50       409 if ( my ($name) = $class =~ /(\w+)$/ ) {
299 56 100       432 $message = "$name: $message" unless $message =~ /\b$name\b/;
300             }
301            
302 56         113 return $message;
303             }
304            
305 11     11 1 20 sub initialize {}
306 16     16 1 973 sub finalize {}
307            
308             1;
309            
310             __END__