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   4639 use strict;
  107         183  
  107         2688  
3 107     107   515 use warnings;
  107         176  
  107         2944  
4              
5 107     107   514 use Scalar::Util qw/weaken/;
  107         174  
  107         5519  
6 107     107   515 use Carp qw/confess croak longmess/;
  107         228  
  107         5914  
7 107     107   2925 use Test::Stream::Util qw/get_tid try pkg_to_file/;
  107         216  
  107         745  
8              
9 107     107   7261 use Test::Stream::Sync;
  107         271  
  107         2664  
10 107     107   537 use Test::Stream::DebugInfo;
  107         203  
  107         33948  
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 12 sub ON_INIT { shift; push @ON_INIT => @_ }
  3         10  
28 3     3 1 12 sub ON_RELEASE { shift; push @ON_RELEASE => @_ }
  3         14  
29              
30 107     107   12434 END { _do_end() }
31              
32             sub _do_end {
33 108     108   1659 my $real = $?;
34 108         301 my $new = $real;
35              
36 108 100       512 my @unreleased = grep { $_ && $_->debug->pid == $$ } values %CONTEXTS;
  3         16  
37 108 100       1414 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         1639 $? = $new;
45             }
46              
47 107     107   630 use Test::Stream::Exporter qw/import exports export/;
  107         186  
  107         753  
48             exports qw/context/;
49             export release => sub($;@) {
50 4     4   26 $_[0]->release;
51 4         6 shift; # Remove undef that used to be our $self reference.
52 4 100       16 return @_ > 1 ? @_ : $_[0];
53             };
54 107     107   645 no Test::Stream::Exporter;
  107         208  
  107         467  
55              
56             use Test::Stream::HashBase(
57 107         912 accessors => [qw/stack hub debug _on_release _depth _err _no_destroy_warning/],
58 107     107   560 );
  107         190  
59              
60             sub init {
61             confess "The 'debug' attribute is required"
62 885 100   885 0 2719 unless $_[0]->{+DEBUG};
63              
64             confess "The 'hub' attribute is required"
65 884 100       2344 unless $_[0]->{+HUB};
66              
67 883 100       3349 $_[0]->{+_DEPTH} = 0 unless defined $_[0]->{+_DEPTH};
68              
69 883         2715 $_[0]->{+_ERR} = $@;
70             }
71              
72 163     163 1 336 sub snapshot { bless {%{$_[0]}}, __PACKAGE__ }
  163         1566  
73              
74             sub release {
75 3741     3741 1 6171 my ($self) = @_;
76 3741 100       13758 return $_[0] = undef if Internals::SvREFCNT(%$self) != 2;
77              
78 3232         5624 my $hub = $self->{+HUB};
79 3232         5763 my $hid = $hub->{hid};
80              
81 3232 100 66     18319 if (!$CONTEXTS{$hid} || $self != $CONTEXTS{$hid}) {
82 1         3 $_[0] = undef;
83 1         113 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         7475 delete $CONTEXTS{$hid};
90              
91 3231 100       7989 if (my $cbk = $self->{+_ON_RELEASE}) {
92 2         9 $_->($self) for reverse @$cbk;
93             }
94 3231 100       7334 if (my $hcbk = $hub->{_context_release}) {
95 32         74 $_->($self) for reverse @$hcbk;
96             }
97 3231         7247 $_->($self) for reverse @ON_RELEASE;
98              
99 3227         8273 return;
100             }
101              
102             sub DESTROY {
103 4286     4286   8280 my ($self) = @_;
104              
105 4286 100       10496 return unless $self->{+HUB};
106 4284         14392 my $hid = $self->{+HUB}->hid;
107              
108 4284 100 100     54256 return unless $CONTEXTS{$hid} && $CONTEXTS{$hid} == $self;
109 6 100       77 return unless "$@" eq "" . $self->{+_ERR};
110              
111 2   50     12 my $debug = $self->{+DEBUG} || return;
112 2         39 my $frame = $debug->frame;
113              
114 2         608 my $mess = longmess;
115              
116 2 100 66     386 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         48 delete $CONTEXTS{$hid};
129 2 50       18 if(my $cbk = $self->{+_ON_RELEASE}) {
130 0         0 $_->($self) for reverse @$cbk;
131             }
132 2 100       26 if (my $hcbk = $self->{+HUB}->{_context_release}) {
133 1         3 $_->($self) for reverse @$hcbk;
134             }
135 2         8 $_->($self) for reverse @ON_RELEASE;
136 2         316 return;
137             }
138              
139             sub do_in_context {
140 207     207 1 1493 my $self = shift;
141 207         512 my ($sub, @args) = @_;
142              
143 207         380 my $hub = $self->{+HUB};
144 207         692 my $hid = $hub->hid;
145              
146 207         839 my $old = $CONTEXTS{$hid};
147              
148 207         745 weaken($CONTEXTS{$hid} = $self);
149 207         752 my ($ok, $err) = &try($sub, @args);
150 206 100       605 if ($old) {
151 143         582 weaken($CONTEXTS{$hid} = $old);
152 143         243 $old = undef;
153             }
154             else {
155 63         174 delete $CONTEXTS{$hid};
156             }
157 206 100       1013 die $err unless $ok;
158             }
159              
160             sub context {
161 3746     3746 1 15224 my %params = (level => 0, wrapped => 0, @_);
162              
163 3746 100       9383 croak "context() called, but return value is ignored"
164             unless defined wantarray;
165              
166 3745   33     11655 my $stack = $params{stack} || $STACK;
167 3745 100 100     19258 my $hub = $params{hub} || @$stack ? $stack->[-1] : $stack->top;
168 3745         6611 my $hid = $hub->{hid};
169 3745         5715 my $current = $CONTEXTS{$hid};
170              
171 3745         6099 my $level = 1 + $params{level};
172 3745         25820 my ($pkg, $file, $line, $sub) = caller($level);
173 3745 100       10986 unless ($pkg) {
174 2 100       261 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         4784 my $depth = $level;
179 3744   100     195310 $depth++ while caller($depth + 1) && (!$current || $depth <= $current->{+_DEPTH} + $params{wrapped});
      66        
180 3744         6018 $depth -= $params{wrapped};
181              
182 3744 100 66     8830 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         3  
185             }
186              
187 3744 100 100     11941 return $current if $current && $current->{+_DEPTH} < $depth;
188              
189             # Handle error condition of bad level
190 3238 100       6968 $current->_depth_error([$pkg, $file, $line, $sub, $depth])
191             if $current;
192              
193 3238         15923 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       20942 $params{on_release} ? (_ON_RELEASE() => [$params{on_release}]) : (),
211             },
212             __PACKAGE__
213             );
214              
215 3238         11557 weaken($CONTEXTS{$hid} = $current);
216              
217 3238         7552 $_->($current) for @ON_INIT;
218              
219 3238 100       8547 if (my $hcbk = $hub->{_context_init}) {
220 35         67 $_->($current) for @$hcbk;
221             }
222              
223 3238 100       7336 $params{on_init}->($current) if $params{on_init};
224              
225 3238         12511 return $current;
226             }
227              
228             sub _depth_error {
229 1     1   3 my $self = shift;
230 1         3 my ($details) = @_;
231 1         3 my ($pkg, $file, $line, $sub, $depth) = @$details;
232              
233 1         5 my $oldframe = $self->{+DEBUG}->frame;
234 1         5 my $olddepth = $self->{+_DEPTH};
235              
236 1         90 my $mess = longmess();
237              
238 1         134 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         10 my $hid = $self->{+HUB}->hid;
261 1         5 delete $CONTEXTS{$hid};
262 1         3 $self->release;
263             }
264              
265             sub throw {
266 4     4 1 10 my ($self, $msg) = @_;
267 4         14 $_[0]->release; # We have to act on $_[0] because it is aliased
268 4         78 $self->debug->throw($msg);
269             }
270              
271             sub alert {
272 2     2 1 9 my ($self, $msg) = @_;
273 2         11 $self->debug->alert($msg);
274             }
275              
276             sub send_event {
277 88     88 1 178 my $self = shift;
278 88         160 my $event = shift;
279 88         292 my %args = @_;
280              
281 88   33     377 my $pkg = $LOADED{$event} || $self->_parse_event($event);
282              
283             $self->{+HUB}->send(
284             $pkg->new(
285 88         490 debug => $self->{+DEBUG}->snapshot,
286             %args,
287             )
288             );
289             }
290              
291             sub build_event {
292 220     220 1 345 my $self = shift;
293 220         402 my $event = shift;
294 220         893 my %args = @_;
295              
296 220   66     795 my $pkg = $LOADED{$event} || $self->_parse_event($event);
297              
298             $pkg->new(
299 220         952 debug => $self->{+DEBUG}->snapshot,
300             %args,
301             );
302             }
303              
304             sub ok {
305 2955     2955 1 4476 my $self = shift;
306 2955         5675 my ($pass, $name, $diag) = @_;
307              
308             my $e = bless {
309 2955         3948 debug => bless( {%{$self->{+DEBUG}}}, 'Test::Stream::DebugInfo'),
  2955         24284  
310             pass => $pass,
311             name => $name,
312             }, 'Test::Stream::Event::Ok';
313 2955         11709 $e->init;
314              
315 2955 100       13106 return $self->{+HUB}->send($e) if $pass;
316              
317 172   100     482 $diag ||= [];
318 172         708 unshift @$diag => $e->default_diag;
319              
320 172         642 $e->set_diag($diag);
321              
322 172         1107 $self->{+HUB}->send($e);
323             }
324              
325             sub note {
326 19     19 1 35 my $self = shift;
327 19         33 my ($message) = @_;
328 19         62 $self->send_event('Note', message => $message);
329             }
330              
331             sub diag {
332 23     23 1 50 my $self = shift;
333 23         39 my ($message) = @_;
334 23         82 $self->send_event('Diag', message => $message);
335             }
336              
337             sub plan {
338 30     30 1 98 my ($self, $max, $directive, $reason) = @_;
339 30 100 100     336 if ($directive && $directive =~ m/skip/i) {
340 16         59 $self->{+_NO_DESTROY_WARNING} = 1;
341 16         69 $self = $self->snapshot;
342 16         74 $_[0]->release;
343             }
344              
345 30         137 $self->send_event('Plan', max => $max, directive => $directive, reason => $reason);
346             }
347              
348             sub bail {
349 10     10 1 31 my ($self, $reason) = @_;
350 10         28 $self->{+_NO_DESTROY_WARNING} = 1;
351 10         30 $self = $self->snapshot;
352 10         42 $_[0]->release;
353 10         31 $self->send_event('Bail', reason => $reason);
354             }
355              
356             sub _parse_event {
357 30     30   68 my $self = shift;
358 30         62 my $event = shift;
359              
360 30         61 my $pkg;
361 30 100       142 if ($event =~ m/^\+(.*)/) {
362 2         7 $pkg = $1;
363             }
364             else {
365 28         95 $pkg = "Test::Stream::Event::$event";
366             }
367              
368 30 100       139 unless ($LOADED{$pkg}) {
369 28         150 my $file = pkg_to_file($pkg);
370 28     28   220 my ($ok, $err) = try { require $file };
  28         887  
371 28 100       229 $self->throw("Could not load event module '$pkg': $err")
372             unless $ok;
373              
374 27         86 $LOADED{$pkg} = $pkg;
375             }
376              
377 29 50       395 confess "'$pkg' is not a subclass of 'Test::Stream::Event'"
378             unless $pkg->isa('Test::Stream::Event');
379              
380 29         152 $LOADED{$event} = $pkg;
381              
382 29         136 return $pkg;
383             }
384              
385             1;
386              
387             __END__