File Coverage

inc/Test2/Hub.pm
Criterion Covered Total %
statement 133 263 50.5
branch 47 126 37.3
condition 39 116 33.6
subroutine 20 35 57.1
pod 20 27 74.0
total 259 567 45.6


line stmt bran cond sub pod time code
1             #line 1
2 6     6   38 package Test2::Hub;
  6         13  
  6         163  
3 6     6   27 use strict;
  6         12  
  6         276  
4             use warnings;
5              
6             our $VERSION = '1.302073';
7              
8 6     6   31  
  6         24  
  6         327  
9 6     6   45 use Carp qw/carp croak confess/;
  6         15  
  6         298  
10             use Test2::Util qw/get_tid ipc_separator/;
11 6     6   36  
  6         12  
  6         316  
12             use Scalar::Util qw/weaken/;
13 6     6   2588  
  6         16  
  6         517  
14 6         73 use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/;
15             use Test2::Util::HashBase qw{
16             pid tid hid ipc
17             no_ending
18             _filters
19             _pre_filters
20             _listeners
21             _follow_ups
22             _formatter
23             _context_acquire
24             _context_init
25             _context_release
26              
27             active
28             count
29             failed
30             ended
31             bailed_out
32             _passing
33             _plan
34 6     6   43 skip_reason
  6         12  
35             };
36              
37             my $ID_POSTFIX = 1;
38 15     15 0 34 sub init {
39             my $self = shift;
40 15         84  
41 15         37 $self->{+PID} = $$;
42 15         106 $self->{+TID} = get_tid();
43             $self->{+HID} = join ipc_separator, $self->{+PID}, $self->{+TID}, $ID_POSTFIX++;
44 15         38  
45 15         39 $self->{+COUNT} = 0;
46 15         34 $self->{+FAILED} = 0;
47             $self->{+_PASSING} = 1;
48 15 50       54  
49 0         0 if (my $formatter = delete $self->{formatter}) {
50             $self->format($formatter);
51             }
52 15 50       63  
53 0         0 if (my $ipc = $self->{+IPC}) {
54             $ipc->add_hub($self->{+HID});
55             }
56             }
57 6     6 0 37  
58             sub is_subtest { 0 }
59              
60 0     0 1 0 sub reset_state {
61             my $self = shift;
62 0         0  
63 0         0 $self->{+COUNT} = 0;
64 0         0 $self->{+FAILED} = 0;
65             $self->{+_PASSING} = 1;
66 0         0  
67 0         0 delete $self->{+_PLAN};
68 0         0 delete $self->{+ENDED};
69 0         0 delete $self->{+BAILED_OUT};
70             delete $self->{+SKIP_REASON};
71             }
72              
73 9     9 0 37 sub inherit {
74 9         25 my $self = shift;
75             my ($from, %params) = @_;
76              
77 9 50 33     76 $self->{+_FORMATTER} = $from->{+_FORMATTER}
78             unless $self->{+_FORMATTER} || exists($params{formatter});
79 9 0 33     49  
      33        
80 0         0 if ($from->{+IPC} && !$self->{+IPC} && !exists($params{ipc})) {
81 0         0 my $ipc = $from->{+IPC};
82 0         0 $self->{+IPC} = $ipc;
83             $ipc->add_hub($self->{+HID});
84             }
85 9 50       45  
86 0         0 if (my $ls = $from->{+_LISTENERS}) {
  0         0  
  0         0  
87             push @{$self->{+_LISTENERS}} => grep { $_->{inherit} } @$ls;
88             }
89 9 50       30  
90 9         17 if (my $pfs = $from->{+_PRE_FILTERS}) {
  9         36  
  9         31  
91             push @{$self->{+_PRE_FILTERS}} => grep { $_->{inherit} } @$pfs;
92             }
93 9 50       47  
94 0         0 if (my $fs = $from->{+_FILTERS}) {
  0         0  
  0         0  
95             push @{$self->{+_FILTERS}} => grep { $_->{inherit} } @$fs;
96             }
97             }
98              
99 45     45 1 83 sub format {
100             my $self = shift;
101 45         88  
102 45 100       119 my $old = $self->{+_FORMATTER};
103             ($self->{+_FORMATTER}) = @_ if @_;
104 45         230  
105             return $old;
106             }
107              
108 0     0 0 0 sub is_local {
109             my $self = shift;
110 0   0     0 return $$ == $self->{+PID}
111             && get_tid() == $self->{+TID};
112             }
113              
114 9     9 1 18 sub listen {
115 9         26 my $self = shift;
116             my ($sub, %params) = @_;
117              
118 9 50 33     63 carp "Useless addition of a listener in a child process or thread!"
119             if $$ != $self->{+PID} || get_tid() != $self->{+TID};
120 9 50 33     55  
121             croak "listen only takes coderefs for arguments, got '$sub'"
122             unless ref $sub && ref $sub eq 'CODE';
123 9         20  
  9         40  
124             push @{$self->{+_LISTENERS}} => { %params, code => $sub };
125 9         21  
126             $sub; # Intentional return.
127             }
128              
129 0     0 1 0 sub unlisten {
130             my $self = shift;
131              
132 0 0 0     0 carp "Useless removal of a listener in a child process or thread!"
133             if $$ != $self->{+PID} || get_tid() != $self->{+TID};
134 0         0  
  0         0  
135             my %subs = map {$_ => $_} @_;
136 0         0  
  0         0  
  0         0  
  0         0  
137             @{$self->{+_LISTENERS}} = grep { !$subs{$_->{code}} } @{$self->{+_LISTENERS}};
138             }
139              
140 0     0 1 0 sub filter {
141 0         0 my $self = shift;
142             my ($sub, %params) = @_;
143              
144 0 0 0     0 carp "Useless addition of a filter in a child process or thread!"
145             if $$ != $self->{+PID} || get_tid() != $self->{+TID};
146 0 0 0     0  
147             croak "filter only takes coderefs for arguments, got '$sub'"
148             unless ref $sub && ref $sub eq 'CODE';
149 0         0  
  0         0  
150             push @{$self->{+_FILTERS}} => { %params, code => $sub };
151 0         0  
152             $sub; # Intentional Return
153             }
154              
155 0     0 1 0 sub unfilter {
156             my $self = shift;
157 0 0 0     0 carp "Useless removal of a filter in a child process or thread!"
158 0         0 if $$ != $self->{+PID} || get_tid() != $self->{+TID};
  0         0  
159 0         0 my %subs = map {$_ => $_} @_;
  0         0  
  0         0  
  0         0  
160             @{$self->{+_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_FILTERS}};
161             }
162              
163 15     15 1 113 sub pre_filter {
164 15         85 my $self = shift;
165             my ($sub, %params) = @_;
166 15 50 33     191  
167             croak "pre_filter only takes coderefs for arguments, got '$sub'"
168             unless ref $sub && ref $sub eq 'CODE';
169 15         36  
  15         118  
170             push @{$self->{+_PRE_FILTERS}} => { %params, code => $sub };
171 15         58  
172             $sub; # Intentional Return
173             }
174              
175 0     0 1 0 sub pre_unfilter {
176 0         0 my $self = shift;
  0         0  
177 0         0 my %subs = map {$_ => $_} @_;
  0         0  
  0         0  
  0         0  
178             @{$self->{+_PRE_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_PRE_FILTERS}};
179             }
180              
181 0     0 0 0 sub follow_up {
182 0         0 my $self = shift;
183             my ($sub) = @_;
184              
185 0 0 0     0 carp "Useless addition of a follow-up in a child process or thread!"
186             if $$ != $self->{+PID} || get_tid() != $self->{+TID};
187 0 0 0     0  
188             croak "follow_up only takes coderefs for arguments, got '$sub'"
189             unless ref $sub && ref $sub eq 'CODE';
190 0         0  
  0         0  
191             push @{$self->{+_FOLLOW_UPS}} => $sub;
192             }
193              
194             *add_context_aquire = \&add_context_acquire;
195 0     0 1 0 sub add_context_acquire {
196 0         0 my $self = shift;
197             my ($sub) = @_;
198 0 0 0     0  
199             croak "add_context_acquire only takes coderefs for arguments, got '$sub'"
200             unless ref $sub && ref $sub eq 'CODE';
201 0         0  
  0         0  
202             push @{$self->{+_CONTEXT_ACQUIRE}} => $sub;
203 0         0  
204             $sub; # Intentional return.
205             }
206              
207             *remove_context_aquire = \&remove_context_acquire;
208 0     0 1 0 sub remove_context_acquire {
209 0         0 my $self = shift;
  0         0  
210 0         0 my %subs = map {$_ => $_} @_;
  0         0  
  0         0  
  0         0  
211             @{$self->{+_CONTEXT_ACQUIRE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_ACQUIRE}};
212             }
213              
214 0     0 1 0 sub add_context_init {
215 0         0 my $self = shift;
216             my ($sub) = @_;
217 0 0 0     0  
218             croak "add_context_init only takes coderefs for arguments, got '$sub'"
219             unless ref $sub && ref $sub eq 'CODE';
220 0         0  
  0         0  
221             push @{$self->{+_CONTEXT_INIT}} => $sub;
222 0         0  
223             $sub; # Intentional return.
224             }
225              
226 0     0 1 0 sub remove_context_init {
227 0         0 my $self = shift;
  0         0  
228 0         0 my %subs = map {$_ => $_} @_;
  0         0  
  0         0  
  0         0  
229             @{$self->{+_CONTEXT_INIT}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_INIT}};
230             }
231              
232 0     0 1 0 sub add_context_release {
233 0         0 my $self = shift;
234             my ($sub) = @_;
235 0 0 0     0  
236             croak "add_context_release only takes coderefs for arguments, got '$sub'"
237             unless ref $sub && ref $sub eq 'CODE';
238 0         0  
  0         0  
239             push @{$self->{+_CONTEXT_RELEASE}} => $sub;
240 0         0  
241             $sub; # Intentional return.
242             }
243              
244 0     0 1 0 sub remove_context_release {
245 0         0 my $self = shift;
  0         0  
246 0         0 my %subs = map {$_ => $_} @_;
  0         0  
  0         0  
  0         0  
247             @{$self->{+_CONTEXT_RELEASE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_RELEASE}};
248             }
249              
250 77     77 1 145 sub send {
251 77         149 my $self = shift;
252             my ($e) = @_;
253 77 50       212  
254 77         114 if ($self->{+_PRE_FILTERS}) {
  77         239  
255 111         354 for (@{$self->{+_PRE_FILTERS}}) {
256 111 50       291 $e = $_->{code}->($self, $e);
257             return unless $e;
258             }
259             }
260 77   50     324  
261             my $ipc = $self->{+IPC} || return $self->process($e);
262 0 0       0  
263 0         0 if($e->global) {
264 0         0 $ipc->send($self->{+HID}, $e, 'GLOBAL');
265             return $self->process($e);
266             }
267              
268 0 0 0     0 return $ipc->send($self->{+HID}, $e)
269             if $$ != $self->{+PID} || get_tid() != $self->{+TID};
270 0         0  
271             $self->process($e);
272             }
273              
274 77     77 1 132 sub process {
275 77         137 my $self = shift;
276             my ($e) = @_;
277 77 50       179  
278 0         0 if ($self->{+_FILTERS}) {
  0         0  
279 0         0 for (@{$self->{+_FILTERS}}) {
280 0 0       0 $e = $_->{code}->($self, $e);
281             return unless $e;
282             }
283             }
284 77         146  
285 77         159 my $type = ref($e);
286 77   66     272 my $is_ok = $type eq 'Test2::Event::Ok';
287 77 100       300 my $no_fail = $type eq 'Test2::Event::Diag' || $type eq 'Test2::Event::Note';
    100          
288 77   100     353 my $causes_fail = $is_ok ? !$e->{effective_pass} : $no_fail ? 0 : $e->causes_fail;
289             my $counted = $is_ok || (!$no_fail && $e->increments_count);
290 77 100       236  
291 77 50 33     210 $self->{+COUNT}++ if $counted;
292 77 50       168 $self->{+FAILED}++ if $causes_fail && $counted;
293             $self->{+_PASSING} = 0 if $causes_fail;
294 77 100 100     284  
295             my $callback = $e->callback($self) unless $is_ok || $no_fail;
296 77         151  
297             my $count = $self->{+COUNT};
298 77 50       490  
299             $self->{+_FORMATTER}->write($e, $count) if $self->{+_FORMATTER};
300 77 100       7790  
301 34         66 if ($self->{+_LISTENERS}) {
  34         182  
302             $_->{code}->($self, $e, $count) for @{$self->{+_LISTENERS}};
303             }
304 77 100 100     459  
305             return $e if $is_ok || $no_fail;
306 24         134  
307 24 50       98 my $code = $e->terminate;
308 0 0       0 if (defined $code) {
309 0         0 $self->{+_FORMATTER}->terminate($e) if $self->{+_FORMATTER};
310             $self->terminate($code, $e);
311             }
312 24         77  
313             return $e;
314             }
315              
316 0     0 0 0 sub terminate {
317 0         0 my $self = shift;
318 0         0 my ($code) = @_;
319             exit($code);
320             }
321              
322 15     15 1 28 sub cull {
323             my $self = shift;
324 15   50     66  
325 0 0 0     0 my $ipc = $self->{+IPC} || return;
326             return if $self->{+PID} != $$ || $self->{+TID} != get_tid();
327              
328 0         0 # No need to do IPC checks on culled events
329             $self->process($_) for $ipc->cull($self->{+HID});
330             }
331              
332 15     15 0 32 sub finalize {
333 15         57 my $self = shift;
334             my ($trace, $do_plan) = @_;
335 15         82  
336             $self->cull();
337 15         34  
338 15         27 my $plan = $self->{+_PLAN};
339 15         34 my $count = $self->{+COUNT};
340 15         34 my $failed = $self->{+FAILED};
341             my $active = $self->{+ACTIVE};
342              
343 15 0 66     86 # return if NOTHING was done.
      66        
      33        
      33        
344 0 0       0 unless ($active || $do_plan || defined($plan) || $count || $failed) {
345 0         0 $self->{+_FORMATTER}->finalize($plan, $count, $failed, 0, $self->is_subtest) if $self->{+_FORMATTER};
346             return;
347             }
348 15 50       46  
349 15 50       45 unless ($self->{+ENDED}) {
350 0         0 if ($self->{+_FOLLOW_UPS}) {
  0         0  
351             $_->($trace, $self) for reverse @{$self->{+_FOLLOW_UPS}};
352             }
353              
354 15         60 # These need to be refreshed now
355 15         29 $plan = $self->{+_PLAN};
356 15         26 $count = $self->{+COUNT};
357             $failed = $self->{+FAILED};
358 15 50 66     116  
      33        
      66        
359 15         100 if (($plan && $plan eq 'NO PLAN') || ($do_plan && !$plan)) {
360             $self->send(
361             Test2::Event::Plan->new(
362             trace => $trace,
363             max => $count,
364             )
365             );
366 15         69 }
367             $plan = $self->{+_PLAN};
368             }
369 15         60  
370 15 50       62 my $frame = $trace->frame;
371 0         0 if($self->{+ENDED}) {
  0         0  
372 0         0 my (undef, $ffile, $fline) = @{$self->{+ENDED}};
373             my (undef, $sfile, $sline) = @$frame;
374 0         0  
375             die <<" EOT"
376             Test already ended!
377             First End: $ffile line $fline
378             Second End: $sfile line $sline
379             EOT
380             }
381 15         39  
382 15         60 $self->{+ENDED} = $frame;
383             my $pass = $self->is_passing(); # Generate the final boolean.
384 15 50       93  
385             $self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass, $self->is_subtest) if $self->{+_FORMATTER};
386 15         65  
387             return $pass;
388             }
389              
390 63     63 1 111 sub is_passing {
391             my $self = shift;
392 63 50       149  
393             ($self->{+_PASSING}) = @_ if @_;
394              
395 63 50       150 # If we already failed just return 0.
396 63 50       127 my $pass = $self->{+_PASSING} or return 0;
397             return $self->{+_PASSING} = 0 if $self->{+FAILED};
398 63         101  
399 63         86 my $count = $self->{+COUNT};
400 63         98 my $ended = $self->{+ENDED};
401             my $plan = $self->{+_PLAN};
402 63 0 33     139  
      33        
403             return $pass if !$count && $plan && $plan =~ m/^SKIP$/;
404 63 50 33     314  
      33        
405             return $self->{+_PASSING} = 0
406             if $ended && (!$count || !$plan);
407 63 50 33     389  
408             return $pass unless $plan && $plan =~ m/^\d+$/;
409 63 50       160  
410 63 50       135 if ($ended) {
411             return $self->{+_PASSING} = 0 if $count != $plan;
412             }
413 0 0       0 else {
414             return $self->{+_PASSING} = 0 if $count > $plan;
415             }
416 63         195  
417             return $pass;
418             }
419              
420 99     99 1 167 sub plan {
421             my $self = shift;
422 99 100       391  
423             return $self->{+_PLAN} unless @_;
424 16         41  
425             my ($plan) = @_;
426 16 50       58  
427             confess "You cannot unset the plan"
428             unless defined $plan;
429              
430 16 50 66     74 confess "You cannot change the plan"
431             if $self->{+_PLAN} && $self->{+_PLAN} !~ m/^NO PLAN$/;
432 16 50       98  
433             confess "'$plan' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'"
434             unless $plan =~ m/^(\d+|NO PLAN|SKIP)$/;
435 16         87  
436             $self->{+_PLAN} = $plan;
437             }
438              
439 0     0 1 0 sub check_plan {
440             my $self = shift;
441 0 0       0  
442 0   0     0 return undef unless $self->{+ENDED};
443             my $plan = $self->{+_PLAN} || return undef;
444 0 0       0  
445             return 1 if $plan !~ m/^\d+$/;
446 0 0       0  
447 0         0 return 1 if $plan == $self->{+COUNT};
448             return 0;
449             }
450              
451 9     9   32 sub DESTROY {
452 9   50     308 my $self = shift;
453 0 0         my $ipc = $self->{+IPC} || return;
454 0 0         return unless $$ == $self->{+PID};
455             return unless get_tid() == $self->{+TID};
456 0            
457             $ipc->drop_hub($self->{+HID});
458             }
459              
460             1;
461              
462             __END__