File Coverage

blib/lib/Test/Trap/Builder.pm
Criterion Covered Total %
statement 270 276 97.8
branch 68 68 100.0
condition 22 23 95.6
subroutine 52 52 100.0
pod 18 18 100.0
total 430 437 98.4


line stmt bran cond sub pod time code
1             package Test::Trap::Builder;
2              
3 27     27   53676 use version; $VERSION = qv('0.3.5');
  27         1619  
  27         143  
4              
5 27     27   1918 use strict;
  27         53  
  27         457  
6 27     27   110 use warnings;
  27         61  
  27         648  
7 27     27   3778 use Data::Dump qw(dump);
  27         39764  
  27         1404  
8             BEGIN {
9 27     27   159 use Exporter ();
  27         48  
  27         1752  
10 27     27   100 *import = \&Exporter::import;
11 27         97 my @methods = qw( Next Exception ExceptionFunction Teardown Run TestAccessor TestFailure Prop DESTROY );
12 27         78 our @EXPORT_OK = (@methods);
13 27         758 our %EXPORT_TAGS = ( methods => \@methods );
14             }
15 27     27   151 use constant GOT_CARP_NOT => $] >= 5.008;
  27         54  
  27         1990  
16 27     27   155 use Carp qw(croak);
  27         54  
  27         11701  
17             our (@CARP_NOT, @ISA);
18              
19             my $builder = bless {};
20              
21             # Methods on the trap object ... basically a trap object "base class":
22              
23 0         0 BEGIN {
24 27     27   85 my %Prop;
25 27         142 my $prefix = "$^T/$$/";
26 27         12794 my $counter;
27              
28             sub DESTROY {
29 338     338   11808 my $self = shift;
30 338   100     8822 delete $Prop{ $self->{' id '} || '' };
31             }
32              
33             sub Prop {
34 6231     6231 1 7245 my $self = shift;
35 6231         8161 my ($package) = @_;
36 6231 100       11754 $package = caller unless $package;
37 6231 100       13040 $self->{' id '} = $prefix . ++$counter unless $self->{' id '};
38 6231   100     27526 return $Prop{$self->{' id '}}{$package} ||= {};
39             }
40              
41 1920     1920 1 2654 sub Next { goto &{ pop @{$_[0]->Prop->{layers}} } }
  1920         2049  
  1920         3015  
42              
43 884     884 1 1375 sub Teardown { my $self = shift; push @{$self->Prop->{teardown}}, @_ }
  884         1055  
  884         1456  
44              
45 308     308 1 496 sub Run { my $self = shift; @_ = (); goto &{$self->Prop->{code}} }
  308         575  
  308         438  
  308         921  
46              
47 1     1 1 4 sub TestAccessor { shift->Prop->{test_accessor} }
48              
49             sub TestFailure {
50 10     10 1 717 my $self = shift;
51 10 100       17 my $m = $self->Prop->{on_test_failure} or return;
52 5         26 $self->$m(@_);
53             }
54              
55             sub ExceptionFunction {
56 324     324 1 497 my $self = shift;
57 324   100     562 my $exception = $self->Prop->{exception} ||= [];
58             $self->Prop->{exception_function} ||= sub {
59 22     22   50 push @$exception, @_;
60 22         46 local *@;
61 22         35 eval {
62 27     27   205 no warnings 'exiting';
  27         56  
  27         2987  
63 22         153 last TEST_TRAP_BUILDER_INTERNAL_EXCEPTION;
64             };
65             # XXX: PANIC! We returned!?!
66 1         115 CORE::exit(8); # XXX: Is there a more appropriate exit value?
67 324   100     621 };
68 324         558 return $self->Prop->{exception_function};
69             }
70              
71             sub Exception {
72 10     10 1 94 my $self = shift;
73 10         20 $self->ExceptionFunction->(@_);
74             }
75             }
76              
77             # Utility functions and methods on the builder class/object:
78              
79             sub _carpnot_for (@) {
80 135     135   316 my %seen = ( __PACKAGE__, 1 );
81 135         251 my @pkg = grep { !$seen{$_}++ } @_;
  389         1073  
82 135         501 return @pkg;
83             }
84              
85 56     56 1 419 sub new { $builder }
86              
87             sub trap {
88 321     321 1 603 my $self = shift;
89 321         704 my ($trapper, $glob, $layers, $code) = @_;
90 321         1351 my $trap = bless { wantarray => (my $wantarray = wantarray) }, $trapper;
91             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
92 321         503 local *@;
  321         678  
93 321         738 local $trap->Prop->{code} = $code;
94 321         1019 $trap->Prop->{layers} = [@$layers];
95 321         677 $trap->Prop->{teardown} = [];
96             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
97 321 100       458 eval { $trap->Next; 1} or $trap->Exception("Rethrowing internal exception: $@");
  321         536  
  321         746  
  298         1084  
98             }
99 311         537 for (reverse @{$trap->Prop->{teardown}}) {
  311         1130  
100             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
101 855 100       1281 eval { $_->(); 1} or $trap->Exception("Rethrowing teardown exception: $@");
  855         1215  
  855         2331  
  847         3260  
102             }
103             }
104 311 100       455 last if @{$trap->Prop->{exception}||[]};
  311 100       719  
105 295         486 ${*$glob} = $trap;
  295         1891  
106 295         583 my @return = eval { @{$trap->return} };
  295         397  
  295         932  
107 295 100       2027 return $wantarray ? @return : $return[0];
108             }
109 16         46 local( GOT_CARP_NOT ? @CARP_NOT : @ISA ) = _carpnot_for $trapper, scalar caller;
110 16         27 croak join"\n", @{$trap->Prop->{exception}};
  16         28  
111             }
112              
113 0         0 BEGIN { # The register (private) functions:
114 27     27   17695 my %register;
115             sub _register {
116 550     550   904 my ($type, $package, $name, $val) = @_;
117 550         1274 $register{$type}{$package}{$name} = $val;
118             }
119             sub _register_packages {
120 523     523   689 my ($type) = @_;
121 523         582 return keys %{$register{$type}};
  523         1202  
122             }
123             sub _register_names {
124 542     542   766 my ($type, $package) = @_;
125 542         573 return keys %{$register{$type}{$package}};
  542         1405  
126             }
127             sub _register_value {
128 5531     5531   7078 my ($type, $package, $name) = @_;
129 5531         7957 return $register{$type}{$package}{$name};
130             }
131             }
132              
133 0         0 BEGIN { # Test callback registration and test method generation:
134             # state for the closures in %argspec -- obviously not reentrant:
135 27     27   110 my ($accessor, $test, $index, $trap, @arg);
136             my %argspec =
137 4         11 ( trap => sub { $trap },
138 86 100       159 element => sub { $accessor->{code}->( $trap, _need_index() ? $index = shift(@arg) : () ) },
139 25         50 entirety => sub { $accessor->{code}->( $trap ) },
140 107         231 predicate => sub { shift @arg },
141 130         648 name => sub { shift @arg },
142 27         286 );
143             # backwards compatibility -- don't use these:
144 27         131 @argspec{ qw( object all indexed ) } = @argspec{ qw( trap entirety element ) };
145             # stringifying the CODE refs, that we may easily check if we have a specific one:
146 27         93 my %isname = ( $argspec{name} => 1 );
147 27         72 my %iselement = ( $argspec{element} => 1 );
148 27         63 my %takesarg = ( $argspec{predicate} => 1 );
149              
150 86 100   86   274 sub _need_index { $accessor->{is_array} && grep $iselement{$_}, @{$test->{argspec}} }
  5         40  
151              
152             # a single universal test -- the leaveby test:
153             # (don't worry -- the UNIVERSAL package is not actually touched)
154             _register test => UNIVERSAL => did =>
155             { argspec => [ $argspec{name} ],
156 19         97 code => sub { require Test::More; goto &Test::More::pass },
  19         61  
157 27         157 pattern => '%s::did_%s',
158             builder => __PACKAGE__->new,
159             };
160              
161             my $basic_test = sub {
162 130         344 ($accessor, $test, $trap, @arg) = @_;
163 130         233 $index = '';
164 130         174 my @targs = map $_->(), @{$test->{argspec}};
  130         335  
165 130         193 my $ok;
166 130         322 local $trap->Prop->{test_accessor} = "$accessor->{name}($index)";
167 130         218 local $Test::Builder::Level = $Test::Builder::Level+1;
168              
169             # Work around perl5 bug #119683, as per Test-Trap bug #127112:
170 130         532 my @copy = ($!, $^E);
171 130         478 local ($!, $^E) = @copy;
172              
173 130 100       330 $ok = $test->{code}->(@targs) or $trap->TestFailure;
174 130         61124 $ok;
175 27         163 };
176              
177             my $wrong_leaveby = sub {
178 5         17 ($accessor, $test, $trap, @arg) = @_;
179 5         15 require Test::More;
180 5         22 my $Test = Test::More->builder;
181 5         28 my $test_name_index = 0;
182 5         17 for (@{$test->{argspec}}) {
  5         14  
183 11 100       26 last if $isname{$_};
184 6 100 100     24 $test_name_index++ if $takesarg{$_} or $accessor->{is_array} && $iselement{$_};
      100        
185             }
186 5         25 my $ok = $Test->ok('', $arg[$test_name_index]);
187 5         1681 my $got = $trap->leaveby;
188 5         18 $Test->diag(sprintf<{name}, $got, dump($trap->$got));
189             Expecting to %s(), but instead %s()ed with %s
190             DIAGNOSTIC
191 5         1165 $trap->TestFailure;
192 5         1651 $ok;
193 27         1416 };
194              
195             sub _accessor_test {
196 523     523   767 my ($apkgs, $anames, $tpkgs, $tnames) = @_;
197 523 100       1006 for my $apkg (@$apkgs ? @$apkgs : _register_packages 'accessor') {
198 531 100       858 for my $aname (@$anames ? @$anames : _register_names accessor => $apkg) {
199 2739         4313 my $adef = _register_value accessor => $apkg => $aname;
200 2739 100       4492 for my $tpkg (@$tpkgs ? @$tpkgs : _register_packages 'test') {
201 2752 100       5382 my $mpkg = $apkg->isa($tpkg) ? $apkg
    100          
202             : $tpkg->isa($apkg) ? $tpkg
203             : next;
204 2749 100       3958 for my $tname (@$tnames ? @$tnames : _register_names test => $tpkg) {
205 2792         3419 my $tdef = _register_value test => $tpkg => $tname;
206 2792         5866 my $mname = sprintf $tdef->{pattern}, $mpkg, $aname;
207 27     27   204 no strict 'refs';
  27         50  
  27         7218  
208             *$mname = sub {
209 135     135   1043 my ($trap) = @_;
210 135         352 unshift @_, $adef, $tdef;
211 135 100 100     379 goto &$wrong_leaveby if $adef->{is_leaveby} and $trap->leaveby ne $adef->{name};
212 130         374 goto &$basic_test;
213 2792         20081 };
214             }
215             }
216             }
217             }
218             }
219              
220             sub test {
221 221     221 1 694 my $self = shift;
222 221         343 my ($tname, $targs, $code) = @_;
223 221         303 my $tpkg = caller;
224 221 100       1280 my @targs = map { $argspec{$_} || croak "Unrecognized identifier $_ in argspec" } $targs =~ /(\w+)/g;
  655         1654  
225 220         1056 _register test => $tpkg => $tname =>
226             { argspec => \@targs,
227             code => $code,
228             pattern => "%s::%s_$tname",
229             builder => $self,
230             };
231             # make the test methods:
232 220         588 _accessor_test( [], [], [$tpkg], [$tname] );
233             }
234             }
235              
236 0         0 BEGIN { # Accessor registration:
237             my $export_accessor = sub {
238 303         537 my ($apkg, $aname, $par, $code) = @_;
239 27     27   203 no strict 'refs';
  27         43  
  27         8561  
240 303         346 *{"$apkg\::$aname"} = $code;
  303         1010  
241 303         1076 _register accessor => $apkg => $aname =>
242             { %$par,
243             code => $code,
244             name => $aname,
245             };
246             # make the test methods:
247 303         928 _accessor_test( [$apkg], [$aname], [], [] );
248 27     27   195 };
249              
250             my %accessor_factory =
251             ( scalar => sub {
252 162         217 my $name = shift;
253 162     529   560 return sub { $_[0]{$name} };
  529         4035951  
254             },
255             array => sub {
256 56         88 my $name = shift;
257             return sub {
258 505     505   34824 my $trap = shift;
259 505 100       2770 return $trap->{$name} unless @_;
260 9 100       26 return @{$trap->{$name}}[@_] if wantarray;
  6         27  
261 3         11 return $trap->{$name}[shift];
262 56         237 };
263             },
264 27         1949 );
265              
266             sub accessor {
267 113     113 1 357 my $self = shift;
268 113         307 my %par = @_;
269 113         189 my $simple = delete $par{simple};
270 113         168 my $flexible = delete $par{flexible};
271 113         182 my $pkg = caller;
272 113 100       138 for my $name (keys %{$flexible||{}}) {
  113         712  
273 85         257 $export_accessor->($pkg, $name, \%par, $flexible->{$name});
274             }
275 113 100       371 my $factory = $accessor_factory{ $par{is_array} ? 'array' : 'scalar' };
276 113 100       171 for my $name (@{$simple||[]}) {
  113         410  
277 218         436 $export_accessor->($pkg, $name, \%par, $factory->($name));
278             }
279             }
280             }
281              
282 0         0 BEGIN { # Layer registration:
283             my $export_layer = sub {
284 352         571 my ($pkg, $name, $sub) = @_;
285 27     27   212 no strict 'refs';
  27         47  
  27         8739  
286 352         375 *{"$pkg\::layer:$name"} = $sub;
  352         1767  
287 27     27   5206 };
288              
289             sub layer {
290 244     244 1 384 my $self = shift;
291 244         360 my ($name, $sub) = @_;
292 244     523   900 $export_layer->(scalar caller, $name, sub { my ($self, @arg) = @_; sub { shift->$sub(@arg) } });
  145         277  
  145         550  
  1292         3655  
293             }
294              
295             sub multi_layer {
296 55     55 1 103 my $self = shift;
297 55         71 my $name = shift;
298 55         106 my $callpkg = caller;
299 55         171 my @layer = $self->layer_implementation($callpkg, @_);
300 54     103   183 $export_layer->($callpkg, $name, sub { @layer });
  103         269  
301             }
302              
303             sub output_layer {
304 54     54 1 91 my $self = shift;
305 54         93 my ($name, $globref) = @_;
306             my $code = sub {
307 77     77   114 my $class = shift;
308 77         124 my ($arg) = @_;
309 77         157 my $strategy = $self->first_capture_strategy($arg);
310             return sub {
311 623     623   1005 my $trap = shift;
312 623         1253 $trap->{$name} = ''; # XXX: Encapsulation violation!
313 623         753 my $fileno;
314             # common stuff:
315 623 100 66     2811 unless (tied *$globref or defined($fileno = fileno *$globref)) {
316 16         88 return $trap->Next;
317             }
318 607         883 my $m = $strategy; # placate Devel::Cover:
319 607 100       1218 $m = $trap->Prop->{capture_strategy} unless $m;
320 607 100       1454 $m = $self->capture_strategy('tempfile') unless $m;
321 607         1994 $trap->$m($name, $fileno, $globref);
322 76         435 };
323 54         212 };
324 54         174 $export_layer->(scalar caller, $name, $code);
325             }
326             }
327              
328 0         0 BEGIN {
329 27     27   90 my %strategy;
330             # Backwards compatibility aliases; don't use:
331 27         79 *output_layer_backend = \&capture_strategy;
332 27         5705 *first_output_layer_backend = \&first_capture_strategy;
333             sub capture_strategy {
334 509     509 1 750 my $this = shift;
335 509         903 my ($name, $strategy) = @_;
336 509 100       1192 $strategy{$name} = $strategy if $strategy;
337 509         29335 return $strategy{$name};
338             }
339             sub first_capture_strategy {
340 111     111 1 158 my $self = shift;
341 111         163 my ($arg) = @_;
342 111 100       233 return unless $arg;
343 55         180 my @strategy = split /[,;]/, $arg;
344 55         101 for (@strategy) {
345 58         110 my $strategy = $self->capture_strategy($_);
346 58 100       194 return $strategy if $strategy;
347             }
348 2         12 croak "No capture strategy found for " . dump(@strategy);
349             }
350             }
351              
352             sub layer_implementation {
353 119     119 1 177 my $self = shift;
354             # Directly querying layer implementation, we should know what we're doing:
355 119         380 local( GOT_CARP_NOT ? @CARP_NOT : @ISA ) = _carpnot_for caller;
356 119         218 my $trapper = shift;
357 119         161 my @r;
358 119         240 for (@_) {
359 335 100 100     722 if ( length ref and eval { exists &$_ } ) {
  8         32  
360 7         12 push @r, $_;
361 7         15 next;
362             }
363 328         1389 my ($name, $arg) =
364             /^ ( [^\(]+ ) # layer name: anything but '('
365             (?: # begin optional group
366             \( # literal '('
367             ( [^\)]* ) # arg: anything but ')'
368             \) # literal ')'
369             )? # end optional group
370             \z/x;
371 328 100       2127 my $meth = $trapper->can("layer:$name")
372             or croak qq[Unknown trap layer "$_"];
373 325         701 push @r, $trapper->$meth($arg);
374             }
375 115         415 return @r;
376             }
377              
378             1; # End of Test::Trap::Builder
379              
380             __END__