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   67078 use version; $VERSION = qv('0.3.4');
  27         2016  
  27         167  
4              
5 27     27   2179 use strict;
  27         128  
  27         533  
6 27     27   156 use warnings;
  27         57  
  27         787  
7 27     27   4153 use Data::Dump qw(dump);
  27         47579  
  27         1436  
8             BEGIN {
9 27     27   182 use Exporter ();
  27         46  
  27         2224  
10 27     27   122 *import = \&Exporter::import;
11 27         116 my @methods = qw( Next Exception ExceptionFunction Teardown Run TestAccessor TestFailure Prop DESTROY );
12 27         109 our @EXPORT_OK = (@methods);
13 27         872 our %EXPORT_TAGS = ( methods => \@methods );
14             }
15 27     27   182 use constant GOT_CARP_NOT => $] >= 5.008;
  27         68  
  27         2396  
16 27     27   185 use Carp qw(croak);
  27         54  
  27         13645  
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   108 my %Prop;
25 27         181 my $prefix = "$^T/$$/";
26 27         15828 my $counter;
27              
28             sub DESTROY {
29 338     338   13879 my $self = shift;
30 338   100     10220 delete $Prop{ $self->{' id '} || '' };
31             }
32              
33             sub Prop {
34 6231     6231 1 9126 my $self = shift;
35 6231         10239 my ($package) = @_;
36 6231 100       14491 $package = caller unless $package;
37 6231 100       15830 $self->{' id '} = $prefix . ++$counter unless $self->{' id '};
38 6231   100     33349 return $Prop{$self->{' id '}}{$package} ||= {};
39             }
40              
41 1920     1920 1 2782 sub Next { goto &{ pop @{$_[0]->Prop->{layers}} } }
  1920         2501  
  1920         3511  
42              
43 884     884 1 1801 sub Teardown { my $self = shift; push @{$self->Prop->{teardown}}, @_ }
  884         1287  
  884         1665  
44              
45 308     308 1 576 sub Run { my $self = shift; @_ = (); goto &{$self->Prop->{code}} }
  308         672  
  308         494  
  308         1059  
46              
47 1     1 1 4 sub TestAccessor { shift->Prop->{test_accessor} }
48              
49             sub TestFailure {
50 10     10 1 886 my $self = shift;
51 10 100       26 my $m = $self->Prop->{on_test_failure} or return;
52 5         26 $self->$m(@_);
53             }
54              
55             sub ExceptionFunction {
56 324     324 1 569 my $self = shift;
57 324   100     685 my $exception = $self->Prop->{exception} ||= [];
58             $self->Prop->{exception_function} ||= sub {
59 22     22   65 push @$exception, @_;
60 22         57 local *@;
61 22         38 eval {
62 27     27   233 no warnings 'exiting';
  27         61  
  27         3531  
63 22         190 last TEST_TRAP_BUILDER_INTERNAL_EXCEPTION;
64             };
65             # XXX: PANIC! We returned!?!
66 1         134 CORE::exit(8); # XXX: Is there a more appropriate exit value?
67 324   100     682 };
68 324         635 return $self->Prop->{exception_function};
69             }
70              
71             sub Exception {
72 10     10 1 135 my $self = shift;
73 10         34 $self->ExceptionFunction->(@_);
74             }
75             }
76              
77             # Utility functions and methods on the builder class/object:
78              
79             sub _carpnot_for (@) {
80 135     135   401 my %seen = ( __PACKAGE__, 1 );
81 135         339 my @pkg = grep { !$seen{$_}++ } @_;
  389         1287  
82 135         720 return @pkg;
83             }
84              
85 56     56 1 416 sub new { $builder }
86              
87             sub trap {
88 321     321 1 781 my $self = shift;
89 321         830 my ($trapper, $glob, $layers, $code) = @_;
90 321         1689 my $trap = bless { wantarray => (my $wantarray = wantarray) }, $trapper;
91             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
92 321         615 local *@;
  321         805  
93 321         869 local $trap->Prop->{code} = $code;
94 321         1204 $trap->Prop->{layers} = [@$layers];
95 321         766 $trap->Prop->{teardown} = [];
96             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
97 321 100       505 eval { $trap->Next; 1} or $trap->Exception("Rethrowing internal exception: $@");
  321         610  
  321         917  
  298         1330  
98             }
99 311         637 for (reverse @{$trap->Prop->{teardown}}) {
  311         1220  
100             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
101 855 100       1466 eval { $_->(); 1} or $trap->Exception("Rethrowing teardown exception: $@");
  855         1441  
  855         2782  
  847         4016  
102             }
103             }
104 311 100       558 last if @{$trap->Prop->{exception}||[]};
  311 100       862  
105 295         599 ${*$glob} = $trap;
  295         2177  
106 295         718 my @return = eval { @{$trap->return} };
  295         466  
  295         1110  
107 295 100       2491 return $wantarray ? @return : $return[0];
108             }
109 16         66 local( GOT_CARP_NOT ? @CARP_NOT : @ISA ) = _carpnot_for $trapper, scalar caller;
110 16         36 croak join"\n", @{$trap->Prop->{exception}};
  16         39  
111             }
112              
113 0         0 BEGIN { # The register (private) functions:
114 27     27   21126 my %register;
115             sub _register {
116 550     550   1078 my ($type, $package, $name, $val) = @_;
117 550         1642 $register{$type}{$package}{$name} = $val;
118             }
119             sub _register_packages {
120 523     523   932 my ($type) = @_;
121 523         738 return keys %{$register{$type}};
  523         1479  
122             }
123             sub _register_names {
124 542     542   942 my ($type, $package) = @_;
125 542         665 return keys %{$register{$type}{$package}};
  542         1757  
126             }
127             sub _register_value {
128 5531     5531   8934 my ($type, $package, $name) = @_;
129 5531         10010 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   118 my ($accessor, $test, $index, $trap, @arg);
136             my %argspec =
137 4         13 ( trap => sub { $trap },
138 86 100       190 element => sub { $accessor->{code}->( $trap, _need_index() ? $index = shift(@arg) : () ) },
139 25         65 entirety => sub { $accessor->{code}->( $trap ) },
140 107         305 predicate => sub { shift @arg },
141 130         361 name => sub { shift @arg },
142 27         247 );
143             # backwards compatibility -- don't use these:
144 27         144 @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         107 my %isname = ( $argspec{name} => 1 );
147 27         98 my %iselement = ( $argspec{element} => 1 );
148 27         136 my %takesarg = ( $argspec{predicate} => 1 );
149              
150 86 100   86   348 sub _need_index { $accessor->{is_array} && grep $iselement{$_}, @{$test->{argspec}} }
  5         48  
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         129 code => sub { require Test::More; goto &Test::More::pass },
  19         81  
157 27         171 pattern => '%s::did_%s',
158             builder => __PACKAGE__->new,
159             };
160              
161             my $basic_test = sub {
162 130         463 ($accessor, $test, $trap, @arg) = @_;
163 130         397 $index = '';
164 130         204 my @targs = map $_->(), @{$test->{argspec}};
  130         441  
165 130         227 my $ok;
166 130         400 local $trap->Prop->{test_accessor} = "$accessor->{name}($index)";
167 130         275 local $Test::Builder::Level = $Test::Builder::Level+1;
168              
169             # Work around perl5 bug #119683, as per Test-Trap bug #127112:
170 130         620 my @copy = ($!, $^E);
171 130         591 local ($!, $^E) = @copy;
172              
173 130 100       426 $ok = $test->{code}->(@targs) or $trap->TestFailure;
174 130         72287 $ok;
175 27         249 };
176              
177             my $wrong_leaveby = sub {
178 5         37 ($accessor, $test, $trap, @arg) = @_;
179 5         21 require Test::More;
180 5         28 my $Test = Test::More->builder;
181 5         36 my $test_name_index = 0;
182 5         10 for (@{$test->{argspec}}) {
  5         16  
183 11 100       32 last if $isname{$_};
184 6 100 100     33 $test_name_index++ if $takesarg{$_} or $accessor->{is_array} && $iselement{$_};
      100        
185             }
186 5         35 my $ok = $Test->ok('', $arg[$test_name_index]);
187 5         2115 my $got = $trap->leaveby;
188 5         27 $Test->diag(sprintf<{name}, $got, dump($trap->$got));
189             Expecting to %s(), but instead %s()ed with %s
190             DIAGNOSTIC
191 5         1454 $trap->TestFailure;
192 5         2034 $ok;
193 27         1839 };
194              
195             sub _accessor_test {
196 523     523   965 my ($apkgs, $anames, $tpkgs, $tnames) = @_;
197 523 100       1271 for my $apkg (@$apkgs ? @$apkgs : _register_packages 'accessor') {
198 531 100       1138 for my $aname (@$anames ? @$anames : _register_names accessor => $apkg) {
199 2739         5274 my $adef = _register_value accessor => $apkg => $aname;
200 2739 100       5440 for my $tpkg (@$tpkgs ? @$tpkgs : _register_packages 'test') {
201 2752 100       6899 my $mpkg = $apkg->isa($tpkg) ? $apkg
    100          
202             : $tpkg->isa($apkg) ? $tpkg
203             : next;
204 2749 100       4695 for my $tname (@$tnames ? @$tnames : _register_names test => $tpkg) {
205 2792         4156 my $tdef = _register_value test => $tpkg => $tname;
206 2792         7168 my $mname = sprintf $tdef->{pattern}, $mpkg, $aname;
207 27     27   247 no strict 'refs';
  27         58  
  27         8407  
208             *$mname = sub {
209 135     135   1286 my ($trap) = @_;
210 135         392 unshift @_, $adef, $tdef;
211 135 100 100     484 goto &$wrong_leaveby if $adef->{is_leaveby} and $trap->leaveby ne $adef->{name};
212 130         881 goto &$basic_test;
213 2792         24200 };
214             }
215             }
216             }
217             }
218             }
219              
220             sub test {
221 221     221 1 837 my $self = shift;
222 221         421 my ($tname, $targs, $code) = @_;
223 221         408 my $tpkg = caller;
224 221 100       1477 my @targs = map { $argspec{$_} || croak "Unrecognized identifier $_ in argspec" } $targs =~ /(\w+)/g;
  655         1930  
225 220         1368 _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         704 _accessor_test( [], [], [$tpkg], [$tname] );
233             }
234             }
235              
236 0         0 BEGIN { # Accessor registration:
237             my $export_accessor = sub {
238 303         676 my ($apkg, $aname, $par, $code) = @_;
239 27     27   221 no strict 'refs';
  27         64  
  27         9993  
240 303         410 *{"$apkg\::$aname"} = $code;
  303         1266  
241 303         1357 _register accessor => $apkg => $aname =>
242             { %$par,
243             code => $code,
244             name => $aname,
245             };
246             # make the test methods:
247 303         1042 _accessor_test( [$apkg], [$aname], [], [] );
248 27     27   189 };
249              
250             my %accessor_factory =
251             ( scalar => sub {
252 162         289 my $name = shift;
253 162     529   664 return sub { $_[0]{$name} };
  529         4486904  
254             },
255             array => sub {
256 56         111 my $name = shift;
257             return sub {
258 505     505   36749 my $trap = shift;
259 505 100       3165 return $trap->{$name} unless @_;
260 9 100       27 return @{$trap->{$name}}[@_] if wantarray;
  6         31  
261 3         13 return $trap->{$name}[shift];
262 56         318 };
263             },
264 27         2275 );
265              
266             sub accessor {
267 113     113 1 468 my $self = shift;
268 113         353 my %par = @_;
269 113         239 my $simple = delete $par{simple};
270 113         208 my $flexible = delete $par{flexible};
271 113         221 my $pkg = caller;
272 113 100       175 for my $name (keys %{$flexible||{}}) {
  113         644  
273 85         301 $export_accessor->($pkg, $name, \%par, $flexible->{$name});
274             }
275 113 100       484 my $factory = $accessor_factory{ $par{is_array} ? 'array' : 'scalar' };
276 113 100       239 for my $name (@{$simple||[]}) {
  113         476  
277 218         529 $export_accessor->($pkg, $name, \%par, $factory->($name));
278             }
279             }
280             }
281              
282 0         0 BEGIN { # Layer registration:
283             my $export_layer = sub {
284 352         723 my ($pkg, $name, $sub) = @_;
285 27     27   234 no strict 'refs';
  27         54  
  27         11129  
286 352         459 *{"$pkg\::layer:$name"} = $sub;
  352         2050  
287 27     27   6106 };
288              
289             sub layer {
290 244     244 1 489 my $self = shift;
291 244         459 my ($name, $sub) = @_;
292 244     516   1257 $export_layer->(scalar caller, $name, sub { my ($self, @arg) = @_; sub { shift->$sub(@arg) } });
  145         371  
  145         817  
  1292         4633  
293             }
294              
295             sub multi_layer {
296 55     55 1 151 my $self = shift;
297 55         115 my $name = shift;
298 55         120 my $callpkg = caller;
299 55         185 my @layer = $self->layer_implementation($callpkg, @_);
300 54     103   311 $export_layer->($callpkg, $name, sub { @layer });
  103         313  
301             }
302              
303             sub output_layer {
304 54     54 1 107 my $self = shift;
305 54         451 my ($name, $globref) = @_;
306             my $code = sub {
307 77     77   151 my $class = shift;
308 77         144 my ($arg) = @_;
309 77         214 my $strategy = $self->first_capture_strategy($arg);
310             return sub {
311 623     623   1229 my $trap = shift;
312 623         1495 $trap->{$name} = ''; # XXX: Encapsulation violation!
313 623         876 my $fileno;
314             # common stuff:
315 623 100 66     3321 unless (tied *$globref or defined($fileno = fileno *$globref)) {
316 16         81 return $trap->Next;
317             }
318 607         1127 my $m = $strategy; # placate Devel::Cover:
319 607 100       1408 $m = $trap->Prop->{capture_strategy} unless $m;
320 607 100       1536 $m = $self->capture_strategy('tempfile') unless $m;
321 607         2315 $trap->$m($name, $fileno, $globref);
322 76         504 };
323 54         233 };
324 54         165 $export_layer->(scalar caller, $name, $code);
325             }
326             }
327              
328 0         0 BEGIN {
329 27     27   106 my %strategy;
330             # Backwards compatibility aliases; don't use:
331 27         116 *output_layer_backend = \&capture_strategy;
332 27         6713 *first_output_layer_backend = \&first_capture_strategy;
333             sub capture_strategy {
334 509     509 1 1035 my $this = shift;
335 509         1092 my ($name, $strategy) = @_;
336 509 100       1410 $strategy{$name} = $strategy if $strategy;
337 509         35443 return $strategy{$name};
338             }
339             sub first_capture_strategy {
340 111     111 1 192 my $self = shift;
341 111         245 my ($arg) = @_;
342 111 100       295 return unless $arg;
343 55         223 my @strategy = split /[,;]/, $arg;
344 55         125 for (@strategy) {
345 58         134 my $strategy = $self->capture_strategy($_);
346 58 100       274 return $strategy if $strategy;
347             }
348 2         14 croak "No capture strategy found for " . dump(@strategy);
349             }
350             }
351              
352             sub layer_implementation {
353 119     119 1 240 my $self = shift;
354             # Directly querying layer implementation, we should know what we're doing:
355 119         437 local( GOT_CARP_NOT ? @CARP_NOT : @ISA ) = _carpnot_for caller;
356 119         339 my $trapper = shift;
357 119         189 my @r;
358 119         286 for (@_) {
359 335 100 100     862 if ( length ref and eval { exists &$_ } ) {
  8         39  
360 7         14 push @r, $_;
361 7         17 next;
362             }
363 328         1768 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       2755 my $meth = $trapper->can("layer:$name")
372             or croak qq[Unknown trap layer "$_"];
373 325         821 push @r, $trapper->$meth($arg);
374             }
375 115         497 return @r;
376             }
377              
378             1; # End of Test::Trap::Builder
379              
380             __END__