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   3077 use strict;
  107         107  
  107         2399  
3 107     107   297 use warnings;
  107         94  
  107         2199  
4              
5 107     107   311 use Scalar::Util qw/weaken/;
  107         106  
  107         4270  
6 107     107   340 use Carp qw/confess croak longmess/;
  107         98  
  107         4342  
7 107     107   1971 use Test::Stream::Util qw/get_tid try pkg_to_file/;
  107         892  
  107         535  
8              
9 107     107   1835 use Test::Stream::Sync();
  107         118  
  107         1290  
10 107     107   330 use Test::Stream::DebugInfo();
  107         107  
  107         22452  
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 8 sub ON_INIT { shift; push @ON_INIT => @_ }
  3         6  
28 3     3 1 8 sub ON_RELEASE { shift; push @ON_RELEASE => @_ }
  3         11  
29              
30 107     107   7839 END { _do_end() }
31              
32             sub _do_end {
33 108     108   257 my $real = $?;
34 108         187 my $new = $real;
35              
36 108 100       344 my @unreleased = grep { $_ && $_->debug->pid == $$ } values %CONTEXTS;
  3         18  
37 108 100       427 if (@unreleased) {
38 1         1 $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         603 $? = $new;
45             }
46              
47 107     107   430 use Test::Stream::Exporter qw/import exports export/;
  107         117  
  107         499  
48             exports qw/context/;
49             export release => sub($;@) {
50 4     4   24 $_[0]->release;
51 4         3 shift; # Remove undef that used to be our $self reference.
52 4 100       16 return @_ > 1 ? @_ : $_[0];
53             };
54 107     107   453 no Test::Stream::Exporter;
  107         180  
  107         322  
55              
56             use Test::Stream::HashBase(
57 107         663 accessors => [qw/stack hub debug _on_release _depth _err _no_destroy_warning/],
58 107     107   424 );
  107         131  
59              
60             sub init {
61             confess "The 'debug' attribute is required"
62 887 100   887 0 1848 unless $_[0]->{+DEBUG};
63              
64             confess "The 'hub' attribute is required"
65 886 100       1529 unless $_[0]->{+HUB};
66              
67 885 100       1901 $_[0]->{+_DEPTH} = 0 unless defined $_[0]->{+_DEPTH};
68              
69 885         1664 $_[0]->{+_ERR} = $@;
70             }
71              
72 164     164 1 213 sub snapshot { bless {%{$_[0]}}, __PACKAGE__ }
  164         1111  
73              
74             sub release {
75 3773     3773 1 4181 my ($self) = @_;
76 3773 100       9687 return $_[0] = undef if Internals::SvREFCNT(%$self) != 2;
77              
78 3262         3751 my $hub = $self->{+HUB};
79 3262         3770 my $hid = $hub->{hid};
80              
81 3262 100 66     14003 if (!$CONTEXTS{$hid} || $self != $CONTEXTS{$hid}) {
82 1         1 $_[0] = undef;
83 1         93 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 3261         5195 delete $CONTEXTS{$hid};
90              
91 3261 100       5299 if (my $cbk = $self->{+_ON_RELEASE}) {
92 2         5 $_->($self) for reverse @$cbk;
93             }
94 3261 100       4878 if (my $hcbk = $hub->{_context_release}) {
95 32         45 $_->($self) for reverse @$hcbk;
96             }
97 3261         5264 $_->($self) for reverse @ON_RELEASE;
98              
99 3257         4926 return;
100             }
101              
102             sub DESTROY {
103 4318     4318   5112 my ($self) = @_;
104              
105 4318 100       7176 return unless $self->{+HUB};
106 4316         9521 my $hid = $self->{+HUB}->hid;
107              
108 4316 100 100     40218 return unless $CONTEXTS{$hid} && $CONTEXTS{$hid} == $self;
109 5 100       58 return unless "$@" eq "" . $self->{+_ERR};
110              
111 2   50     16 my $debug = $self->{+DEBUG} || return;
112 2         29 my $frame = $debug->frame;
113              
114 2         389 my $mess = longmess;
115              
116 2 100 66     240 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         23 delete $CONTEXTS{$hid};
129 2 50       12 if(my $cbk = $self->{+_ON_RELEASE}) {
130 0         0 $_->($self) for reverse @$cbk;
131             }
132 2 100       8 if (my $hcbk = $self->{+HUB}->{_context_release}) {
133 1         3 $_->($self) for reverse @$hcbk;
134             }
135 2         9 $_->($self) for reverse @ON_RELEASE;
136 2         165 return;
137             }
138              
139             sub do_in_context {
140 208     208 1 1203 my $self = shift;
141 208         328 my ($sub, @args) = @_;
142              
143 208         251 my $hub = $self->{+HUB};
144 208         458 my $hid = $hub->hid;
145              
146 208         553 my $old = $CONTEXTS{$hid};
147              
148 208         576 weaken($CONTEXTS{$hid} = $self);
149 208         520 my ($ok, $err) = &try($sub, @args);
150 207 100       411 if ($old) {
151 144         436 weaken($CONTEXTS{$hid} = $old);
152 144         159 $old = undef;
153             }
154             else {
155 63         120 delete $CONTEXTS{$hid};
156             }
157 207 100       724 die $err unless $ok;
158             }
159              
160             sub context {
161 3777     3777 1 10158 my %params = (level => 0, wrapped => 0, @_);
162              
163 3777 100       6572 croak "context() called, but return value is ignored"
164             unless defined wantarray;
165              
166 3776   33     8281 my $stack = $params{stack} || $STACK;
167 3776 100 100     13560 my $hub = $params{hub} || @$stack ? $stack->[-1] : $stack->top;
168 3776         4064 my $hid = $hub->{hid};
169 3776         3516 my $current = $CONTEXTS{$hid};
170              
171 3776         3893 my $level = 1 + $params{level};
172 3776         19053 my ($pkg, $file, $line, $sub) = caller($level);
173 3776 100       7095 unless ($pkg) {
174 2 100       171 confess "Could not find context at depth $level" unless $params{fudge};
175 1   66     62 ($pkg, $file, $line, $sub) = caller(--$level) while ($level >= 0 && !$pkg);
176             }
177              
178 3775         3314 my $depth = $level;
179 3775   100     115447 $depth++ while caller($depth + 1) && (!$current || $depth <= $current->{+_DEPTH} + $params{wrapped});
      66        
180 3775         3962 $depth -= $params{wrapped};
181              
182 3775 100 66     6023 if ($current && $params{on_release} && $current->{+_DEPTH} < $depth) {
      66        
183 1   50     3 $current->{+_ON_RELEASE} ||= [];
184 1         2 push @{$current->{+_ON_RELEASE}} => $params{on_release};
  1         1  
185             }
186              
187 3775 100 100     7861 return $current if $current && $current->{+_DEPTH} < $depth;
188              
189             # Handle error condition of bad level
190 3267 100       4515 $current->_depth_error([$pkg, $file, $line, $sub, $depth])
191             if $current;
192              
193 3267         11503 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 3267 100       13100 $params{on_release} ? (_ON_RELEASE() => [$params{on_release}]) : (),
211             },
212             __PACKAGE__
213             );
214              
215 3267         8427 weaken($CONTEXTS{$hid} = $current);
216              
217 3267         5642 $_->($current) for @ON_INIT;
218              
219 3267 100       5661 if (my $hcbk = $hub->{_context_init}) {
220 35         45 $_->($current) for @$hcbk;
221             }
222              
223 3267 100       4512 $params{on_init}->($current) if $params{on_init};
224              
225 3267         8006 return $current;
226             }
227              
228             sub _depth_error {
229 1     1   2 my $self = shift;
230 1         2 my ($details) = @_;
231 1         3 my ($pkg, $file, $line, $sub, $depth) = @$details;
232              
233 1         3 my $oldframe = $self->{+DEBUG}->frame;
234 1         4 my $olddepth = $self->{+_DEPTH};
235              
236 1         53 my $mess = longmess();
237              
238 1         89 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         7 my $hid = $self->{+HUB}->hid;
261 1         4 delete $CONTEXTS{$hid};
262 1         3 $self->release;
263             }
264              
265             sub throw {
266 4     4 1 10 my ($self, $msg) = @_;
267 4         12 $_[0]->release; # We have to act on $_[0] because it is aliased
268 4         11 $self->debug->throw($msg);
269             }
270              
271             sub alert {
272 2     2 1 6 my ($self, $msg) = @_;
273 2         8 $self->debug->alert($msg);
274             }
275              
276             sub send_event {
277 88     88 1 104 my $self = shift;
278 88         112 my $event = shift;
279 88         210 my %args = @_;
280              
281 88   33     324 my $pkg = $LOADED{$event} || $self->_parse_event($event);
282              
283             $self->{+HUB}->send(
284             $pkg->new(
285 88         370 debug => $self->{+DEBUG}->snapshot,
286             %args,
287             )
288             );
289             }
290              
291             sub build_event {
292 221     221 1 231 my $self = shift;
293 221         216 my $event = shift;
294 221         620 my %args = @_;
295              
296 221   66     612 my $pkg = $LOADED{$event} || $self->_parse_event($event);
297              
298             $pkg->new(
299 221         647 debug => $self->{+DEBUG}->snapshot,
300             %args,
301             );
302             }
303              
304             sub ok {
305 2984     2984 1 2848 my $self = shift;
306 2984         3325 my ($pass, $name, $diag) = @_;
307              
308             my $e = bless {
309 2984         2462 debug => bless( {%{$self->{+DEBUG}}}, 'Test::Stream::DebugInfo'),
  2984         17024  
310             pass => $pass,
311             name => $name,
312             }, 'Test::Stream::Event::Ok';
313 2984         7750 $e->init;
314              
315 2984 100       9241 return $self->{+HUB}->send($e) if $pass;
316              
317 180   100     362 $diag ||= [];
318 180         477 unshift @$diag => $e->default_diag;
319              
320 180         474 $e->set_diag($diag);
321              
322 180         863 $self->{+HUB}->send($e);
323             }
324              
325             sub note {
326 19     19 1 33 my $self = shift;
327 19         24 my ($message) = @_;
328 19         52 $self->send_event('Note', message => $message);
329             }
330              
331             sub diag {
332 23     23 1 38 my $self = shift;
333 23         31 my ($message) = @_;
334 23         58 $self->send_event('Diag', message => $message);
335             }
336              
337             sub plan {
338 30     30 1 68 my ($self, $max, $directive, $reason) = @_;
339 30 100 100     195 if ($directive && $directive =~ m/skip/i) {
340 16         43 $self->{+_NO_DESTROY_WARNING} = 1;
341 16         43 $self = $self->snapshot;
342 16         57 $_[0]->release;
343             }
344              
345 30         109 $self->send_event('Plan', max => $max, directive => $directive, reason => $reason);
346             }
347              
348             sub bail {
349 10     10 1 24 my ($self, $reason) = @_;
350 10         19 $self->{+_NO_DESTROY_WARNING} = 1;
351 10         21 $self = $self->snapshot;
352 10         29 $_[0]->release;
353 10         27 $self->send_event('Bail', reason => $reason);
354             }
355              
356             sub _parse_event {
357 30     30   48 my $self = shift;
358 30         43 my $event = shift;
359              
360 30         41 my $pkg;
361 30 100       94 if ($event =~ m/^\+(.*)/) {
362 2         4 $pkg = $1;
363             }
364             else {
365 28         83 $pkg = "Test::Stream::Event::$event";
366             }
367              
368 30 100       143 unless ($LOADED{$pkg}) {
369 28         118 my $file = pkg_to_file($pkg);
370 28     28   186 my ($ok, $err) = try { require $file };
  28         540  
371 28 100       140 $self->throw("Could not load event module '$pkg': $err")
372             unless $ok;
373              
374 27         71 $LOADED{$pkg} = $pkg;
375             }
376              
377 29 50       266 confess "'$pkg' is not a subclass of 'Test::Stream::Event'"
378             unless $pkg->isa('Test::Stream::Event');
379              
380 29         106 $LOADED{$event} = $pkg;
381              
382 29         107 return $pkg;
383             }
384              
385             1;
386              
387             __END__