File Coverage

blib/lib/Test/Stream/Context.pm
Criterion Covered Total %
statement 188 189 99.4
branch 66 68 97.0
condition 37 51 72.5
subroutine 33 33 100.0
pod 15 16 93.7
total 339 357 94.9


line stmt bran cond sub pod time code
1             package Test::Stream::Context;
2 107     107   4595 use strict;
  107         184  
  107         2635  
3 107     107   510 use warnings;
  107         172  
  107         2973  
4              
5 107     107   524 use Scalar::Util qw/weaken/;
  107         167  
  107         5486  
6 107     107   509 use Carp qw/confess croak longmess/;
  107         169  
  107         5655  
7 107     107   3043 use Test::Stream::Util qw/get_tid try pkg_to_file/;
  107         179  
  107         833  
8              
9 107     107   7300 use Test::Stream::Sync;
  107         205  
  107         2617  
10 107     107   543 use Test::Stream::DebugInfo;
  107         201  
  107         33867  
11              
12             # Preload some key event types
13             my %LOADED = (
14             map {
15             require "Test/Stream/Event/$_.pm";
16             my $pkg = "Test::Stream::Event::$_";
17             ( $pkg => $pkg, $_ => $pkg )
18             } qw/Ok Diag Note Plan Bail Exception Waiting/
19             );
20              
21             # Stack is ok to cache.
22             our $STACK = Test::Stream::Sync->stack;
23             our @ON_INIT;
24             our @ON_RELEASE;
25             our %CONTEXTS;
26              
27 3     3 1 24 sub ON_INIT { shift; push @ON_INIT => @_ }
  3         12  
28 3     3 1 10 sub ON_RELEASE { shift; push @ON_RELEASE => @_ }
  3         16  
29              
30 107     107   12133 END { _do_end() }
31              
32             sub _do_end {
33 108     108   1754 my $real = $?;
34 108         340 my $new = $real;
35              
36 108 100       522 my @unreleased = grep { $_ && $_->debug->pid == $$ } values %CONTEXTS;
  3         16  
37 108 100       1227 if (@unreleased) {
38 1         2 $new = 255;
39              
40             $_->debug->alert("context object was never released! This means a testing tool is behaving very badly")
41 1         5 for @unreleased;
42             }
43              
44 108         1701 $? = $new;
45             }
46              
47 107     107   588 use Test::Stream::Exporter qw/import exports export/;
  107         195  
  107         803  
48             exports qw/context/;
49             export release => sub($;@) {
50 4     4   26 $_[0]->release;
51 4         7 shift; # Remove undef that used to be our $self reference.
52 4 100       17 return @_ > 1 ? @_ : $_[0];
53             };
54 107     107   594 no Test::Stream::Exporter;
  107         200  
  107         538  
55              
56             use Test::Stream::HashBase(
57 107         946 accessors => [qw/stack hub debug _on_release _depth _err _no_destroy_warning/],
58 107     107   579 );
  107         183  
59              
60             sub init {
61             confess "The 'debug' attribute is required"
62 885 100   885 0 2865 unless $_[0]->{+DEBUG};
63              
64             confess "The 'hub' attribute is required"
65 884 100       2470 unless $_[0]->{+HUB};
66              
67 883 100       3604 $_[0]->{+_DEPTH} = 0 unless defined $_[0]->{+_DEPTH};
68              
69 883         2933 $_[0]->{+_ERR} = $@;
70             }
71              
72 163     163 1 405 sub snapshot { bless {%{$_[0]}}, __PACKAGE__ }
  163         1494  
73              
74             sub release {
75 3741     3741 1 6369 my ($self) = @_;
76 3741 100       14355 return $_[0] = undef if Internals::SvREFCNT(%$self) != 2;
77              
78 3232         5844 my $hub = $self->{+HUB};
79 3232         5770 my $hid = $hub->{hid};
80              
81 3232 100 66     18819 if (!$CONTEXTS{$hid} || $self != $CONTEXTS{$hid}) {
82 1         2 $_[0] = undef;
83 1         109 croak "release() should not be called on a non-canonical context.";
84             }
85              
86             # Remove the weak reference, this will also prevent the destructor from
87             # having an issue.
88             # Remove the key itself to avoid a slow memory leak
89 3231         7697 delete $CONTEXTS{$hid};
90              
91 3231 100       8095 if (my $cbk = $self->{+_ON_RELEASE}) {
92 2         8 $_->($self) for reverse @$cbk;
93             }
94 3231 100       8329 if (my $hcbk = $hub->{_context_release}) {
95 32         119 $_->($self) for reverse @$hcbk;
96             }
97 3231         7135 $_->($self) for reverse @ON_RELEASE;
98              
99 3227         8451 return;
100             }
101              
102             sub DESTROY {
103 4286     4286   8770 my ($self) = @_;
104              
105 4286 100       10989 return unless $self->{+HUB};
106 4284         14659 my $hid = $self->{+HUB}->hid;
107              
108 4284 100 100     55590 return unless $CONTEXTS{$hid} && $CONTEXTS{$hid} == $self;
109 6 100       91 return unless "$@" eq "" . $self->{+_ERR};
110              
111 2   50     14 my $debug = $self->{+DEBUG} || return;
112 2         50 my $frame = $debug->frame;
113              
114 2         806 my $mess = longmess;
115              
116 2 100 66     585 warn <<" EOT" unless $self->{+_NO_DESTROY_WARNING} || $self->{+DEBUG}->pid != $$ || $self->{+DEBUG}->tid != get_tid;
      66        
117             Context was not released! Releasing at destruction.
118             Context creation details:
119             Package: $frame->[0]
120             File: $frame->[1]
121             Line: $frame->[2]
122             Tool: $frame->[3]
123              
124             Trace: $mess
125             EOT
126              
127             # Remove the key itself to avoid a slow memory leak
128 2         62 delete $CONTEXTS{$hid};
129 2 50       16 if(my $cbk = $self->{+_ON_RELEASE}) {
130 0         0 $_->($self) for reverse @$cbk;
131             }
132 2 100       34 if (my $hcbk = $self->{+HUB}->{_context_release}) {
133 1         4 $_->($self) for reverse @$hcbk;
134             }
135 2         17 $_->($self) for reverse @ON_RELEASE;
136 2         418 return;
137             }
138              
139             sub do_in_context {
140 207     207 1 1755 my $self = shift;
141 207         551 my ($sub, @args) = @_;
142              
143 207         403 my $hub = $self->{+HUB};
144 207         700 my $hid = $hub->hid;
145              
146 207         946 my $old = $CONTEXTS{$hid};
147              
148 207         783 weaken($CONTEXTS{$hid} = $self);
149 207         775 my ($ok, $err) = &try($sub, @args);
150 206 100       676 if ($old) {
151 143         680 weaken($CONTEXTS{$hid} = $old);
152 143         268 $old = undef;
153             }
154             else {
155 63         177 delete $CONTEXTS{$hid};
156             }
157 206 100       1183 die $err unless $ok;
158             }
159              
160             sub context {
161 3746     3746 1 15084 my %params = (level => 0, wrapped => 0, @_);
162              
163 3746 100       9676 croak "context() called, but return value is ignored"
164             unless defined wantarray;
165              
166 3745   33     11913 my $stack = $params{stack} || $STACK;
167 3745 100 100     19756 my $hub = $params{hub} || @$stack ? $stack->[-1] : $stack->top;
168 3745         6766 my $hid = $hub->{hid};
169 3745         5897 my $current = $CONTEXTS{$hid};
170              
171 3745         6174 my $level = 1 + $params{level};
172 3745         26744 my ($pkg, $file, $line, $sub) = caller($level);
173 3745 100       10892 unless ($pkg) {
174 2 100       292 confess "Could not find context at depth $level" unless $params{fudge};
175 1   66     63 ($pkg, $file, $line, $sub) = caller(--$level) while ($level >= 0 && !$pkg);
176             }
177              
178 3744         5007 my $depth = $level;
179 3744   100     200651 $depth++ while caller($depth + 1) && (!$current || $depth <= $current->{+_DEPTH} + $params{wrapped});
      66        
180 3744         6271 $depth -= $params{wrapped};
181              
182 3744 100 66     9139 if ($current && $params{on_release} && $current->{+_DEPTH} < $depth) {
      66        
183 1   50     4 $current->{+_ON_RELEASE} ||= [];
184 1         2 push @{$current->{+_ON_RELEASE}} => $params{on_release};
  1         2  
185             }
186              
187 3744 100 100     12408 return $current if $current && $current->{+_DEPTH} < $depth;
188              
189             # Handle error condition of bad level
190 3238 100       7141 $current->_depth_error([$pkg, $file, $line, $sub, $depth])
191             if $current;
192              
193 3238         16562 my $dbg = bless(
194             {
195             frame => [$pkg, $file, $line, $sub],
196             pid => $$,
197             tid => get_tid(),
198             $hub->debug_todo,
199             },
200             'Test::Stream::DebugInfo'
201             );
202              
203             $current = bless(
204             {
205             STACK() => $stack,
206             HUB() => $hub,
207             DEBUG() => $dbg,
208             _DEPTH() => $depth,
209             _ERR() => $@,
210 3238 100       20946 $params{on_release} ? (_ON_RELEASE() => [$params{on_release}]) : (),
211             },
212             __PACKAGE__
213             );
214              
215 3238         11740 weaken($CONTEXTS{$hid} = $current);
216              
217 3238         7794 $_->($current) for @ON_INIT;
218              
219 3238 100       8651 if (my $hcbk = $hub->{_context_init}) {
220 35         88 $_->($current) for @$hcbk;
221             }
222              
223 3238 100       7386 $params{on_init}->($current) if $params{on_init};
224              
225 3238         12444 return $current;
226             }
227              
228             sub _depth_error {
229 1     1   3 my $self = shift;
230 1         4 my ($details) = @_;
231 1         4 my ($pkg, $file, $line, $sub, $depth) = @$details;
232              
233 1         6 my $oldframe = $self->{+DEBUG}->frame;
234 1         5 my $olddepth = $self->{+_DEPTH};
235              
236 1         96 my $mess = longmess();
237              
238 1         138 warn <<" EOT";
239             context() was called to retrieve an existing context, however the existing
240             context was created in a stack frame at the same, or deeper level. This usually
241             means that a tool failed to release the context when it was finished.
242              
243             Old context details:
244             File: $oldframe->[1]
245             Line: $oldframe->[2]
246             Tool: $oldframe->[3]
247             Depth: $olddepth
248              
249             New context details:
250             File: $file
251             Line: $line
252             Tool: $sub
253             Depth: $depth
254              
255             Trace: $mess
256              
257             Removing the old context and creating a new one...
258             EOT
259              
260 1         11 my $hid = $self->{+HUB}->hid;
261 1         5 delete $CONTEXTS{$hid};
262 1         4 $self->release;
263             }
264              
265             sub throw {
266 4     4 1 14 my ($self, $msg) = @_;
267 4         15 $_[0]->release; # We have to act on $_[0] because it is aliased
268 4         18 $self->debug->throw($msg);
269             }
270              
271             sub alert {
272 2     2 1 8 my ($self, $msg) = @_;
273 2         10 $self->debug->alert($msg);
274             }
275              
276             sub send_event {
277 88     88 1 165 my $self = shift;
278 88         171 my $event = shift;
279 88         301 my %args = @_;
280              
281 88   33     382 my $pkg = $LOADED{$event} || $self->_parse_event($event);
282              
283             $self->{+HUB}->send(
284             $pkg->new(
285 88         491 debug => $self->{+DEBUG}->snapshot,
286             %args,
287             )
288             );
289             }
290              
291             sub build_event {
292 220     220 1 342 my $self = shift;
293 220         370 my $event = shift;
294 220         932 my %args = @_;
295              
296 220   66     840 my $pkg = $LOADED{$event} || $self->_parse_event($event);
297              
298             $pkg->new(
299 220         987 debug => $self->{+DEBUG}->snapshot,
300             %args,
301             );
302             }
303              
304             sub ok {
305 2955     2955 1 4511 my $self = shift;
306 2955         5718 my ($pass, $name, $diag) = @_;
307              
308             my $e = bless {
309 2955         3895 debug => bless( {%{$self->{+DEBUG}}}, 'Test::Stream::DebugInfo'),
  2955         24685  
310             pass => $pass,
311             name => $name,
312             }, 'Test::Stream::Event::Ok';
313 2955         11832 $e->init;
314              
315 2955 100       13474 return $self->{+HUB}->send($e) if $pass;
316              
317 172   100     474 $diag ||= [];
318 172         623 unshift @$diag => $e->default_diag;
319              
320 172         645 $e->set_diag($diag);
321              
322 172         1110 $self->{+HUB}->send($e);
323             }
324              
325             sub note {
326 19     19 1 39 my $self = shift;
327 19         36 my ($message) = @_;
328 19         57 $self->send_event('Note', message => $message);
329             }
330              
331             sub diag {
332 23     23 1 61 my $self = shift;
333 23         44 my ($message) = @_;
334 23         84 $self->send_event('Diag', message => $message);
335             }
336              
337             sub plan {
338 30     30 1 180 my ($self, $max, $directive, $reason) = @_;
339 30 100 100     236 if ($directive && $directive =~ m/skip/i) {
340 16         51 $self->{+_NO_DESTROY_WARNING} = 1;
341 16         54 $self = $self->snapshot;
342 16         68 $_[0]->release;
343             }
344              
345 30         134 $self->send_event('Plan', max => $max, directive => $directive, reason => $reason);
346             }
347              
348             sub bail {
349 10     10 1 29 my ($self, $reason) = @_;
350 10         26 $self->{+_NO_DESTROY_WARNING} = 1;
351 10         33 $self = $self->snapshot;
352 10         40 $_[0]->release;
353 10         34 $self->send_event('Bail', reason => $reason);
354             }
355              
356             sub _parse_event {
357 30     30   73 my $self = shift;
358 30         64 my $event = shift;
359              
360 30         58 my $pkg;
361 30 100       151 if ($event =~ m/^\+(.*)/) {
362 2         8 $pkg = $1;
363             }
364             else {
365 28         107 $pkg = "Test::Stream::Event::$event";
366             }
367              
368 30 100       142 unless ($LOADED{$pkg}) {
369 28         151 my $file = pkg_to_file($pkg);
370 28     28   211 my ($ok, $err) = try { require $file };
  28         965  
371 28 100       231 $self->throw("Could not load event module '$pkg': $err")
372             unless $ok;
373              
374 27         96 $LOADED{$pkg} = $pkg;
375             }
376              
377 29 50       363 confess "'$pkg' is not a subclass of 'Test::Stream::Event'"
378             unless $pkg->isa('Test::Stream::Event');
379              
380 29         180 $LOADED{$event} = $pkg;
381              
382 29         141 return $pkg;
383             }
384              
385             1;
386              
387             __END__