File Coverage

blib/lib/Test/Trap/Builder.pm
Criterion Covered Total %
statement 269 275 97.8
branch 68 68 100.0
condition 22 23 95.6
subroutine 52 52 100.0
pod 18 18 100.0
total 429 436 98.3


line stmt bran cond sub pod time code
1             package Test::Trap::Builder;
2              
3 27     27   107915 use version; $VERSION = qv('0.3.3');
  27         3116  
  27         193  
4              
5 27     27   2641 use strict;
  27         82  
  27         690  
6 27     27   167 use warnings;
  27         67  
  27         930  
7 27     27   2948 use Data::Dump qw(dump);
  27         50706  
  27         2085  
8             BEGIN {
9 27     27   254 use Exporter ();
  27         79  
  27         2963  
10 27     27   147 *import = \&Exporter::import;
11 27         135 my @methods = qw( Next Exception ExceptionFunction Teardown Run TestAccessor TestFailure Prop DESTROY );
12 27         354 our @EXPORT_OK = (@methods);
13 27         1040 our %EXPORT_TAGS = ( methods => \@methods );
14             }
15 27     27   204 use constant GOT_CARP_NOT => $] >= 5.008;
  27         61  
  27         2446  
16 27     27   204 use Carp qw(croak);
  27         92  
  27         14016  
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   114 my %Prop;
25 27         219 my $prefix = "$^T/$$/";
26 27         16369 my $counter;
27              
28             sub DESTROY {
29 337     337   18143 my $self = shift;
30 337   100     11506 delete $Prop{ $self->{' id '} || '' };
31             }
32              
33             sub Prop {
34 6213     6213 1 12612 my $self = shift;
35 6213         12519 my ($package) = @_;
36 6213 100       18262 $package = caller unless $package;
37 6213 100       20386 $self->{' id '} = $prefix . ++$counter unless $self->{' id '};
38 6213   100     44018 return $Prop{$self->{' id '}}{$package} ||= {};
39             }
40              
41 1914     1914 1 3933 sub Next { goto &{ pop @{$_[0]->Prop->{layers}} } }
  1914         3369  
  1914         4963  
42              
43 881     881 1 2151 sub Teardown { my $self = shift; push @{$self->Prop->{teardown}}, @_ }
  881         1686  
  881         2168  
44              
45 307     307 1 742 sub Run { my $self = shift; @_ = (); goto &{$self->Prop->{code}} }
  307         745  
  307         633  
  307         860  
46              
47 1     1 1 7 sub TestAccessor { shift->Prop->{test_accessor} }
48              
49             sub TestFailure {
50 10     10 1 1409 my $self = shift;
51 10 100       38 my $m = $self->Prop->{on_test_failure} or return;
52 5         57 $self->$m(@_);
53             }
54              
55             sub ExceptionFunction {
56 324     324 1 702 my $self = shift;
57 324   100     7430 my $exception = $self->Prop->{exception} ||= [];
58             $self->Prop->{exception_function} ||= sub {
59 22     22   79 push @$exception, @_;
60 22         87 local *@;
61 22         63 eval {
62 27     27   249 no warnings 'exiting';
  27         67  
  27         3644  
63 22         221 last TEST_TRAP_BUILDER_INTERNAL_EXCEPTION;
64             };
65             # XXX: PANIC! We returned!?!
66 1         58 CORE::exit(8); # XXX: Is there a more appropriate exit value?
67 324   100     896 };
68 324         826 return $self->Prop->{exception_function};
69             }
70              
71             sub Exception {
72 10     10 1 163 my $self = shift;
73 10         39 $self->ExceptionFunction->(@_);
74             }
75             }
76              
77             # Utility functions and methods on the builder class/object:
78              
79             sub _carpnot_for (@) {
80 135     135   513 my %seen = ( __PACKAGE__, 1 );
81 135         445 my @pkg = grep { !$seen{$_}++ } @_;
  389         1990  
82 135         854 return @pkg;
83             }
84              
85 56     56 1 490 sub new { $builder }
86              
87             sub trap {
88 320     320 1 939 my $self = shift;
89 320         1138 my ($trapper, $glob, $layers, $code) = @_;
90 320         2144 my $trap = bless { wantarray => (my $wantarray = wantarray) }, $trapper;
91             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
92 320         777 local *@;
  320         989  
93 320         1237 local $trap->Prop->{code} = $code;
94 320         1694 $trap->Prop->{layers} = [@$layers];
95 320         1080 $trap->Prop->{teardown} = [];
96             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
97 320 100       711 eval { $trap->Next; 1} or $trap->Exception("Rethrowing internal exception: $@");
  320         879  
  320         1278  
  297         1789  
98             }
99 310         787 for (reverse @{$trap->Prop->{teardown}}) {
  310         1407  
100             TEST_TRAP_BUILDER_INTERNAL_EXCEPTION: {
101 852 100       1745 eval { $_->(); 1} or $trap->Exception("Rethrowing teardown exception: $@");
  852         1732  
  852         3417  
  844         4517  
102             }
103             }
104 310 100       705 last if @{$trap->Prop->{exception}||[]};
  310 100       1209  
105 294         1088 ${*$glob} = $trap;
  294         2485  
106 294         1034 my @return = eval { @{$trap->return} };
  294         599  
  294         1367  
107 294 100       2653 return $wantarray ? @return : $return[0];
108             }
109 16         92 local( GOT_CARP_NOT ? @CARP_NOT : @ISA ) = _carpnot_for $trapper, scalar caller;
110 16         51 croak join"\n", @{$trap->Prop->{exception}};
  16         62  
111             }
112              
113 0         0 BEGIN { # The register (private) functions:
114 27     27   21871 my %register;
115             sub _register {
116 550     550   1472 my ($type, $package, $name, $val) = @_;
117 550         2016 $register{$type}{$package}{$name} = $val;
118             }
119             sub _register_packages {
120 523     523   1242 my ($type) = @_;
121 523         969 return keys %{$register{$type}};
  523         2116  
122             }
123             sub _register_names {
124 542     542   1527 my ($type, $package) = @_;
125 542         930 return keys %{$register{$type}{$package}};
  542         2445  
126             }
127             sub _register_value {
128 5531     5531   12748 my ($type, $package, $name) = @_;
129 5531         14841 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   158 my ($accessor, $test, $index, $trap, @arg);
136             my %argspec =
137 4         17 ( trap => sub { $trap },
138 85 100       251 element => sub { $accessor->{code}->( $trap, _need_index() ? $index = shift(@arg) : () ) },
139 25         90 entirety => sub { $accessor->{code}->( $trap ) },
140 106         377 predicate => sub { shift @arg },
141 129         468 name => sub { shift @arg },
142 27         428 );
143             # backwards compatibility -- don't use these:
144 27         166 @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         143 my %isname = ( $argspec{name} => 1 );
147 27         106 my %iselement = ( $argspec{element} => 1 );
148 27         114 my %takesarg = ( $argspec{predicate} => 1 );
149              
150 85 100   85   465 sub _need_index { $accessor->{is_array} && grep $iselement{$_}, @{$test->{argspec}} }
  5         63  
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         166 code => sub { require Test::More; goto &Test::More::pass },
  19         117  
157 27         190 pattern => '%s::did_%s',
158             builder => __PACKAGE__->new,
159             };
160              
161             my $basic_test = sub {
162 129         523 ($accessor, $test, $trap, @arg) = @_;
163 129         357 $index = '';
164 129         357 my @targs = map $_->(), @{$test->{argspec}};
  129         582  
165 129         285 my $ok;
166 129         549 local $trap->Prop->{test_accessor} = "$accessor->{name}($index)";
167 129         379 local $Test::Builder::Level = $Test::Builder::Level+1;
168 129         941 local($!, $^E) = ($!, $^E);
169 129 100       775 $ok = $test->{code}->(@targs) or $trap->TestFailure;
170 129         100489 $ok;
171 27         150 };
172              
173             my $wrong_leaveby = sub {
174 5         36 ($accessor, $test, $trap, @arg) = @_;
175 5         32 require Test::More;
176 5         45 my $Test = Test::More->builder;
177 5         58 my $test_name_index = 0;
178 5         14 for (@{$test->{argspec}}) {
  5         27  
179 11 100       55 last if $isname{$_};
180 6 100 100     77 $test_name_index++ if $takesarg{$_} or $accessor->{is_array} && $iselement{$_};
      100        
181             }
182 5         57 my $ok = $Test->ok('', $arg[$test_name_index]);
183 5         3946 my $got = $trap->leaveby;
184 5         39 $Test->diag(sprintf<{name}, $got, dump($trap->$got));
185             Expecting to %s(), but instead %s()ed with %s
186             DIAGNOSTIC
187 5         2189 $trap->TestFailure;
188 5         2991 $ok;
189 27         1818 };
190              
191             sub _accessor_test {
192 523     523   1428 my ($apkgs, $anames, $tpkgs, $tnames) = @_;
193 523 100       1672 for my $apkg (@$apkgs ? @$apkgs : _register_packages 'accessor') {
194 531 100       1605 for my $aname (@$anames ? @$anames : _register_names accessor => $apkg) {
195 2739         7852 my $adef = _register_value accessor => $apkg => $aname;
196 2739 100       8091 for my $tpkg (@$tpkgs ? @$tpkgs : _register_packages 'test') {
197 2752 100       10554 my $mpkg = $apkg->isa($tpkg) ? $apkg
    100          
198             : $tpkg->isa($apkg) ? $tpkg
199             : next;
200 2749 100       7141 for my $tname (@$tnames ? @$tnames : _register_names test => $tpkg) {
201 2792         6063 my $tdef = _register_value test => $tpkg => $tname;
202 2792         10587 my $mname = sprintf $tdef->{pattern}, $mpkg, $aname;
203 27     27   271 no strict 'refs';
  27         70  
  27         9249  
204             *$mname = sub {
205 134     134   1794 my ($trap) = @_;
206 134         566 unshift @_, $adef, $tdef;
207 134 100 100     641 goto &$wrong_leaveby if $adef->{is_leaveby} and $trap->leaveby ne $adef->{name};
208 129         740 goto &$basic_test;
209 2792         30953 };
210             }
211             }
212             }
213             }
214             }
215              
216             sub test {
217 221     221 1 1068 my $self = shift;
218 221         627 my ($tname, $targs, $code) = @_;
219 221         584 my $tpkg = caller;
220 221 100       2127 my @targs = map { $argspec{$_} || croak "Unrecognized identifier $_ in argspec" } $targs =~ /(\w+)/g;
  655         3382  
221 220         2102 _register test => $tpkg => $tname =>
222             { argspec => \@targs,
223             code => $code,
224             pattern => "%s::%s_$tname",
225             builder => $self,
226             };
227             # make the test methods:
228 220         983 _accessor_test( [], [], [$tpkg], [$tname] );
229             }
230             }
231              
232 0         0 BEGIN { # Accessor registration:
233             my $export_accessor = sub {
234 303         925 my ($apkg, $aname, $par, $code) = @_;
235 27     27   237 no strict 'refs';
  27         101  
  27         10300  
236 303         563 *{"$apkg\::$aname"} = $code;
  303         1851  
237 303         1747 _register accessor => $apkg => $aname =>
238             { %$par,
239             code => $code,
240             name => $aname,
241             };
242             # make the test methods:
243 303         1316 _accessor_test( [$apkg], [$aname], [], [] );
244 27     27   232 };
245              
246             my %accessor_factory =
247             ( scalar => sub {
248 162         377 my $name = shift;
249 162     527   879 return sub { $_[0]{$name} };
  527         6352742  
250             },
251             array => sub {
252 56         162 my $name = shift;
253             return sub {
254 504     504   54246 my $trap = shift;
255 504 100       4338 return $trap->{$name} unless @_;
256 9 100       33 return @{$trap->{$name}}[@_] if wantarray;
  6         40  
257 3         20 return $trap->{$name}[shift];
258 56         345 };
259             },
260 27         2447 );
261              
262             sub accessor {
263 113     113 1 531 my $self = shift;
264 113         460 my %par = @_;
265 113         333 my $simple = delete $par{simple};
266 113         291 my $flexible = delete $par{flexible};
267 113         319 my $pkg = caller;
268 113 100       219 for my $name (keys %{$flexible||{}}) {
  113         780  
269 85         376 $export_accessor->($pkg, $name, \%par, $flexible->{$name});
270             }
271 113 100       551 my $factory = $accessor_factory{ $par{is_array} ? 'array' : 'scalar' };
272 113 100       247 for my $name (@{$simple||[]}) {
  113         557  
273 218         727 $export_accessor->($pkg, $name, \%par, $factory->($name));
274             }
275             }
276             }
277              
278 0         0 BEGIN { # Layer registration:
279             my $export_layer = sub {
280 352         986 my ($pkg, $name, $sub) = @_;
281 27     27   268 no strict 'refs';
  27         71  
  27         11198  
282 352         634 *{"$pkg\::layer:$name"} = $sub;
  352         2864  
283 27     27   6424 };
284              
285             sub layer {
286 244     244 1 654 my $self = shift;
287 244         639 my ($name, $sub) = @_;
288 244     540   1591 $export_layer->(scalar caller, $name, sub { my ($self, @arg) = @_; sub { shift->$sub(@arg) } });
  145         487  
  145         927  
  1288         5956  
289             }
290              
291             sub multi_layer {
292 55     55 1 279 my $self = shift;
293 55         135 my $name = shift;
294 55         158 my $callpkg = caller;
295 55         303 my @layer = $self->layer_implementation($callpkg, @_);
296 54     103   586 $export_layer->($callpkg, $name, sub { @layer });
  103         466  
297             }
298              
299             sub output_layer {
300 54     54 1 156 my $self = shift;
301 54         193 my ($name, $globref) = @_;
302             my $code = sub {
303 77     77   199 my $class = shift;
304 77         206 my ($arg) = @_;
305 77         284 my $strategy = $self->first_capture_strategy($arg);
306             return sub {
307 621     621   1564 my $trap = shift;
308 621         1960 $trap->{$name} = ''; # XXX: Encapsulation violation!
309 621         1243 my $fileno;
310             # common stuff:
311 621 100 66     4073 unless (tied *$globref or defined($fileno = fileno *$globref)) {
312 16         72 return $trap->Next;
313             }
314 605         1405 my $m = $strategy; # placate Devel::Cover:
315 605 100       2016 $m = $trap->Prop->{capture_strategy} unless $m;
316 605 100       2219 $m = $self->capture_strategy('tempfile') unless $m;
317 605         2975 $trap->$m($name, $fileno, $globref);
318 76         566 };
319 54         310 };
320 54         241 $export_layer->(scalar caller, $name, $code);
321             }
322             }
323              
324 0         0 BEGIN {
325 27     27   124 my %strategy;
326             # Backwards compatibility aliases; don't use:
327 27         102 *output_layer_backend = \&capture_strategy;
328 27         7640 *first_output_layer_backend = \&first_capture_strategy;
329             sub capture_strategy {
330 507     507 1 1263 my $this = shift;
331 507         1329 my ($name, $strategy) = @_;
332 507 100       1952 $strategy{$name} = $strategy if $strategy;
333 507         41222 return $strategy{$name};
334             }
335             sub first_capture_strategy {
336 111     111 1 250 my $self = shift;
337 111         306 my ($arg) = @_;
338 111 100       397 return unless $arg;
339 55         281 my @strategy = split /[,;]/, $arg;
340 55         237 for (@strategy) {
341 58         204 my $strategy = $self->capture_strategy($_);
342 58 100       318 return $strategy if $strategy;
343             }
344 2         18 croak "No capture strategy found for " . dump(@strategy);
345             }
346             }
347              
348             sub layer_implementation {
349 119     119 1 348 my $self = shift;
350             # Directly querying layer implementation, we should know what we're doing:
351 119         648 local( GOT_CARP_NOT ? @CARP_NOT : @ISA ) = _carpnot_for caller;
352 119         382 my $trapper = shift;
353 119         274 my @r;
354 119         368 for (@_) {
355 335 100 100     1207 if ( length ref and eval { exists &$_ } ) {
  8         46  
356 7         17 push @r, $_;
357 7         22 next;
358             }
359 328         2449 my ($name, $arg) =
360             /^ ( [^\(]+ ) # layer name: anything but '('
361             (?: # begin optional group
362             \( # literal '('
363             ( [^\)]* ) # arg: anything but ')'
364             \) # literal ')'
365             )? # end optional group
366             \z/x;
367 328 100       3524 my $meth = $trapper->can("layer:$name")
368             or croak qq[Unknown trap layer "$_"];
369 325         1171 push @r, $trapper->$meth($arg);
370             }
371 115         684 return @r;
372             }
373              
374             1; # End of Test::Trap::Builder
375              
376             __END__