Commit | Line | Data |
---|---|---|
b4514920 CG |
1 | package Test2::Hub; |
2 | use strict; | |
3 | use warnings; | |
4 | ||
cf76a266 | 5 | our $VERSION = '1.302198'; |
b4514920 CG |
6 | |
7 | ||
8 | use Carp qw/carp croak confess/; | |
e82ffdf2 | 9 | use Test2::Util qw/get_tid gen_uid/; |
b4514920 CG |
10 | |
11 | use Scalar::Util qw/weaken/; | |
07bc328a | 12 | use List::Util qw/first/; |
b4514920 CG |
13 | |
14 | use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/; | |
15 | use Test2::Util::HashBase qw{ | |
16 | pid tid hid ipc | |
07bc328a | 17 | nested buffered |
b4514920 CG |
18 | no_ending |
19 | _filters | |
20 | _pre_filters | |
21 | _listeners | |
22 | _follow_ups | |
23 | _formatter | |
24 | _context_acquire | |
25 | _context_init | |
26 | _context_release | |
27 | ||
43de38c4 | 28 | uuid |
9fe558dc | 29 | active |
b4514920 CG |
30 | count |
31 | failed | |
32 | ended | |
33 | bailed_out | |
34 | _passing | |
35 | _plan | |
36 | skip_reason | |
37 | }; | |
38 | ||
43de38c4 TR |
39 | my $UUID_VIA; |
40 | ||
b4514920 CG |
41 | sub init { |
42 | my $self = shift; | |
43 | ||
44 | $self->{+PID} = $$; | |
45 | $self->{+TID} = get_tid(); | |
e82ffdf2 | 46 | $self->{+HID} = gen_uid(); |
b4514920 | 47 | |
43de38c4 TR |
48 | $UUID_VIA ||= Test2::API::_add_uuid_via_ref(); |
49 | $self->{+UUID} = ${$UUID_VIA}->('hub') if $$UUID_VIA; | |
50 | ||
07bc328a SH |
51 | $self->{+NESTED} = 0 unless defined $self->{+NESTED}; |
52 | $self->{+BUFFERED} = 0 unless defined $self->{+BUFFERED}; | |
53 | ||
b4514920 CG |
54 | $self->{+COUNT} = 0; |
55 | $self->{+FAILED} = 0; | |
56 | $self->{+_PASSING} = 1; | |
57 | ||
58 | if (my $formatter = delete $self->{formatter}) { | |
59 | $self->format($formatter); | |
60 | } | |
61 | ||
62 | if (my $ipc = $self->{+IPC}) { | |
63 | $ipc->add_hub($self->{+HID}); | |
64 | } | |
65 | } | |
66 | ||
c59be082 SH |
67 | sub is_subtest { 0 } |
68 | ||
07bc328a SH |
69 | sub _tb_reset { |
70 | my $self = shift; | |
71 | ||
72 | # Nothing to do | |
73 | return if $self->{+PID} == $$ && $self->{+TID} == get_tid(); | |
74 | ||
75 | $self->{+PID} = $$; | |
76 | $self->{+TID} = get_tid(); | |
e82ffdf2 | 77 | $self->{+HID} = gen_uid(); |
07bc328a SH |
78 | |
79 | if (my $ipc = $self->{+IPC}) { | |
80 | $ipc->add_hub($self->{+HID}); | |
81 | } | |
82 | } | |
83 | ||
b4514920 CG |
84 | sub reset_state { |
85 | my $self = shift; | |
86 | ||
87 | $self->{+COUNT} = 0; | |
88 | $self->{+FAILED} = 0; | |
89 | $self->{+_PASSING} = 1; | |
90 | ||
91 | delete $self->{+_PLAN}; | |
92 | delete $self->{+ENDED}; | |
93 | delete $self->{+BAILED_OUT}; | |
94 | delete $self->{+SKIP_REASON}; | |
95 | } | |
96 | ||
97 | sub inherit { | |
98 | my $self = shift; | |
99 | my ($from, %params) = @_; | |
100 | ||
07bc328a SH |
101 | $self->{+NESTED} ||= 0; |
102 | ||
b4514920 CG |
103 | $self->{+_FORMATTER} = $from->{+_FORMATTER} |
104 | unless $self->{+_FORMATTER} || exists($params{formatter}); | |
105 | ||
106 | if ($from->{+IPC} && !$self->{+IPC} && !exists($params{ipc})) { | |
107 | my $ipc = $from->{+IPC}; | |
108 | $self->{+IPC} = $ipc; | |
109 | $ipc->add_hub($self->{+HID}); | |
110 | } | |
111 | ||
112 | if (my $ls = $from->{+_LISTENERS}) { | |
113 | push @{$self->{+_LISTENERS}} => grep { $_->{inherit} } @$ls; | |
114 | } | |
115 | ||
35014935 SH |
116 | if (my $pfs = $from->{+_PRE_FILTERS}) { |
117 | push @{$self->{+_PRE_FILTERS}} => grep { $_->{inherit} } @$pfs; | |
118 | } | |
119 | ||
b4514920 CG |
120 | if (my $fs = $from->{+_FILTERS}) { |
121 | push @{$self->{+_FILTERS}} => grep { $_->{inherit} } @$fs; | |
122 | } | |
123 | } | |
124 | ||
125 | sub format { | |
126 | my $self = shift; | |
127 | ||
128 | my $old = $self->{+_FORMATTER}; | |
129 | ($self->{+_FORMATTER}) = @_ if @_; | |
130 | ||
131 | return $old; | |
132 | } | |
133 | ||
134 | sub is_local { | |
135 | my $self = shift; | |
136 | return $$ == $self->{+PID} | |
137 | && get_tid() == $self->{+TID}; | |
138 | } | |
139 | ||
140 | sub listen { | |
141 | my $self = shift; | |
142 | my ($sub, %params) = @_; | |
143 | ||
144 | carp "Useless addition of a listener in a child process or thread!" | |
145 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
146 | ||
147 | croak "listen only takes coderefs for arguments, got '$sub'" | |
148 | unless ref $sub && ref $sub eq 'CODE'; | |
149 | ||
150 | push @{$self->{+_LISTENERS}} => { %params, code => $sub }; | |
151 | ||
152 | $sub; # Intentional return. | |
153 | } | |
154 | ||
155 | sub unlisten { | |
156 | my $self = shift; | |
157 | ||
158 | carp "Useless removal of a listener in a child process or thread!" | |
159 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
160 | ||
161 | my %subs = map {$_ => $_} @_; | |
162 | ||
163 | @{$self->{+_LISTENERS}} = grep { !$subs{$_->{code}} } @{$self->{+_LISTENERS}}; | |
164 | } | |
165 | ||
166 | sub filter { | |
167 | my $self = shift; | |
168 | my ($sub, %params) = @_; | |
169 | ||
170 | carp "Useless addition of a filter in a child process or thread!" | |
171 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
172 | ||
173 | croak "filter only takes coderefs for arguments, got '$sub'" | |
174 | unless ref $sub && ref $sub eq 'CODE'; | |
175 | ||
176 | push @{$self->{+_FILTERS}} => { %params, code => $sub }; | |
177 | ||
178 | $sub; # Intentional Return | |
179 | } | |
180 | ||
181 | sub unfilter { | |
182 | my $self = shift; | |
183 | carp "Useless removal of a filter in a child process or thread!" | |
184 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
185 | my %subs = map {$_ => $_} @_; | |
186 | @{$self->{+_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_FILTERS}}; | |
187 | } | |
188 | ||
189 | sub pre_filter { | |
190 | my $self = shift; | |
191 | my ($sub, %params) = @_; | |
192 | ||
193 | croak "pre_filter only takes coderefs for arguments, got '$sub'" | |
194 | unless ref $sub && ref $sub eq 'CODE'; | |
195 | ||
196 | push @{$self->{+_PRE_FILTERS}} => { %params, code => $sub }; | |
197 | ||
198 | $sub; # Intentional Return | |
199 | } | |
200 | ||
201 | sub pre_unfilter { | |
202 | my $self = shift; | |
203 | my %subs = map {$_ => $_} @_; | |
204 | @{$self->{+_PRE_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_PRE_FILTERS}}; | |
205 | } | |
206 | ||
207 | sub follow_up { | |
208 | my $self = shift; | |
209 | my ($sub) = @_; | |
210 | ||
211 | carp "Useless addition of a follow-up in a child process or thread!" | |
212 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
213 | ||
214 | croak "follow_up only takes coderefs for arguments, got '$sub'" | |
215 | unless ref $sub && ref $sub eq 'CODE'; | |
216 | ||
217 | push @{$self->{+_FOLLOW_UPS}} => $sub; | |
218 | } | |
219 | ||
220 | *add_context_aquire = \&add_context_acquire; | |
221 | sub add_context_acquire { | |
222 | my $self = shift; | |
223 | my ($sub) = @_; | |
224 | ||
225 | croak "add_context_acquire only takes coderefs for arguments, got '$sub'" | |
226 | unless ref $sub && ref $sub eq 'CODE'; | |
227 | ||
228 | push @{$self->{+_CONTEXT_ACQUIRE}} => $sub; | |
229 | ||
230 | $sub; # Intentional return. | |
231 | } | |
232 | ||
233 | *remove_context_aquire = \&remove_context_acquire; | |
234 | sub remove_context_acquire { | |
235 | my $self = shift; | |
236 | my %subs = map {$_ => $_} @_; | |
237 | @{$self->{+_CONTEXT_ACQUIRE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_ACQUIRE}}; | |
238 | } | |
239 | ||
240 | sub add_context_init { | |
241 | my $self = shift; | |
242 | my ($sub) = @_; | |
243 | ||
244 | croak "add_context_init only takes coderefs for arguments, got '$sub'" | |
245 | unless ref $sub && ref $sub eq 'CODE'; | |
246 | ||
247 | push @{$self->{+_CONTEXT_INIT}} => $sub; | |
248 | ||
249 | $sub; # Intentional return. | |
250 | } | |
251 | ||
252 | sub remove_context_init { | |
253 | my $self = shift; | |
254 | my %subs = map {$_ => $_} @_; | |
255 | @{$self->{+_CONTEXT_INIT}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_INIT}}; | |
256 | } | |
257 | ||
258 | sub add_context_release { | |
259 | my $self = shift; | |
260 | my ($sub) = @_; | |
261 | ||
262 | croak "add_context_release only takes coderefs for arguments, got '$sub'" | |
263 | unless ref $sub && ref $sub eq 'CODE'; | |
264 | ||
265 | push @{$self->{+_CONTEXT_RELEASE}} => $sub; | |
266 | ||
267 | $sub; # Intentional return. | |
268 | } | |
269 | ||
270 | sub remove_context_release { | |
271 | my $self = shift; | |
272 | my %subs = map {$_ => $_} @_; | |
273 | @{$self->{+_CONTEXT_RELEASE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_RELEASE}}; | |
274 | } | |
275 | ||
276 | sub send { | |
277 | my $self = shift; | |
278 | my ($e) = @_; | |
279 | ||
e82ffdf2 SH |
280 | $e->eid; |
281 | ||
43de38c4 TR |
282 | $e->add_hub( |
283 | { | |
284 | details => ref($self), | |
285 | ||
286 | buffered => $self->{+BUFFERED}, | |
287 | hid => $self->{+HID}, | |
288 | nested => $self->{+NESTED}, | |
289 | pid => $self->{+PID}, | |
290 | tid => $self->{+TID}, | |
291 | uuid => $self->{+UUID}, | |
292 | ||
293 | ipc => $self->{+IPC} ? 1 : 0, | |
294 | } | |
295 | ); | |
296 | ||
297 | $e->set_uuid(${$UUID_VIA}->('event')) if $$UUID_VIA; | |
298 | ||
b4514920 CG |
299 | if ($self->{+_PRE_FILTERS}) { |
300 | for (@{$self->{+_PRE_FILTERS}}) { | |
301 | $e = $_->{code}->($self, $e); | |
302 | return unless $e; | |
303 | } | |
304 | } | |
305 | ||
306 | my $ipc = $self->{+IPC} || return $self->process($e); | |
307 | ||
308 | if($e->global) { | |
309 | $ipc->send($self->{+HID}, $e, 'GLOBAL'); | |
310 | return $self->process($e); | |
311 | } | |
312 | ||
313 | return $ipc->send($self->{+HID}, $e) | |
314 | if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
315 | ||
316 | $self->process($e); | |
317 | } | |
318 | ||
319 | sub process { | |
320 | my $self = shift; | |
321 | my ($e) = @_; | |
322 | ||
323 | if ($self->{+_FILTERS}) { | |
324 | for (@{$self->{+_FILTERS}}) { | |
325 | $e = $_->{code}->($self, $e); | |
326 | return unless $e; | |
327 | } | |
328 | } | |
329 | ||
07bc328a | 330 | # Optimize the most common case |
b4514920 | 331 | my $type = ref($e); |
07bc328a SH |
332 | if ($type eq 'Test2::Event::Pass' || ($type eq 'Test2::Event::Ok' && $e->{pass})) { |
333 | my $count = ++($self->{+COUNT}); | |
334 | $self->{+_FORMATTER}->write($e, $count) if $self->{+_FORMATTER}; | |
b4514920 | 335 | |
07bc328a SH |
336 | if ($self->{+_LISTENERS}) { |
337 | $_->{code}->($self, $e, $count) for @{$self->{+_LISTENERS}}; | |
338 | } | |
339 | ||
340 | return $e; | |
341 | } | |
342 | ||
343 | my $f = $e->facet_data; | |
b4514920 | 344 | |
07bc328a SH |
345 | my $fail = 0; |
346 | $fail = 1 if $f->{assert} && !$f->{assert}->{pass}; | |
95db2efb | 347 | $fail = 1 if $f->{errors} && grep { $_->{fail} } @{$f->{errors}}; |
07bc328a | 348 | $fail = 0 if $f->{amnesty}; |
b4514920 | 349 | |
07bc328a SH |
350 | $self->{+COUNT}++ if $f->{assert}; |
351 | $self->{+FAILED}++ if $fail && $f->{assert}; | |
352 | $self->{+_PASSING} = 0 if $fail; | |
353 | ||
18c72c39 | 354 | my $code = $f->{control} ? $f->{control}->{terminate} : undef; |
b4514920 CG |
355 | my $count = $self->{+COUNT}; |
356 | ||
07bc328a SH |
357 | if (my $plan = $f->{plan}) { |
358 | if ($plan->{skip}) { | |
359 | $self->plan('SKIP'); | |
360 | $self->set_skip_reason($plan->{details} || 1); | |
361 | $code ||= 0; | |
362 | } | |
363 | elsif ($plan->{none}) { | |
364 | $self->plan('NO PLAN'); | |
365 | } | |
366 | else { | |
367 | $self->plan($plan->{count}); | |
368 | } | |
369 | } | |
370 | ||
18c72c39 | 371 | $e->callback($self) if $f->{control} && $f->{control}->{has_callback}; |
07bc328a SH |
372 | |
373 | $self->{+_FORMATTER}->write($e, $count, $f) if $self->{+_FORMATTER}; | |
b4514920 CG |
374 | |
375 | if ($self->{+_LISTENERS}) { | |
07bc328a | 376 | $_->{code}->($self, $e, $count, $f) for @{$self->{+_LISTENERS}}; |
b4514920 CG |
377 | } |
378 | ||
18c72c39 | 379 | if ($f->{control} && $f->{control}->{halt}) { |
07bc328a SH |
380 | $code ||= 255; |
381 | $self->set_bailed_out($e); | |
382 | } | |
b4514920 | 383 | |
c59be082 | 384 | if (defined $code) { |
07bc328a SH |
385 | $self->{+_FORMATTER}->terminate($e, $f) if $self->{+_FORMATTER}; |
386 | $self->terminate($code, $e, $f); | |
c59be082 | 387 | } |
b4514920 CG |
388 | |
389 | return $e; | |
390 | } | |
391 | ||
392 | sub terminate { | |
393 | my $self = shift; | |
394 | my ($code) = @_; | |
395 | exit($code); | |
396 | } | |
397 | ||
398 | sub cull { | |
399 | my $self = shift; | |
400 | ||
401 | my $ipc = $self->{+IPC} || return; | |
402 | return if $self->{+PID} != $$ || $self->{+TID} != get_tid(); | |
403 | ||
404 | # No need to do IPC checks on culled events | |
405 | $self->process($_) for $ipc->cull($self->{+HID}); | |
406 | } | |
407 | ||
408 | sub finalize { | |
409 | my $self = shift; | |
410 | my ($trace, $do_plan) = @_; | |
411 | ||
412 | $self->cull(); | |
413 | ||
414 | my $plan = $self->{+_PLAN}; | |
415 | my $count = $self->{+COUNT}; | |
416 | my $failed = $self->{+FAILED}; | |
9fe558dc | 417 | my $active = $self->{+ACTIVE}; |
b4514920 | 418 | |
07bc328a SH |
419 | # return if NOTHING was done. |
420 | unless ($active || $do_plan || defined($plan) || $count || $failed) { | |
421 | $self->{+_FORMATTER}->finalize($plan, $count, $failed, 0, $self->is_subtest) if $self->{+_FORMATTER}; | |
422 | return; | |
423 | } | |
b4514920 CG |
424 | |
425 | unless ($self->{+ENDED}) { | |
426 | if ($self->{+_FOLLOW_UPS}) { | |
427 | $_->($trace, $self) for reverse @{$self->{+_FOLLOW_UPS}}; | |
428 | } | |
429 | ||
430 | # These need to be refreshed now | |
431 | $plan = $self->{+_PLAN}; | |
432 | $count = $self->{+COUNT}; | |
433 | $failed = $self->{+FAILED}; | |
434 | ||
2b6b3cbe | 435 | if ((defined($plan) && $plan eq 'NO PLAN') || ($do_plan && !defined($plan))) { |
b4514920 CG |
436 | $self->send( |
437 | Test2::Event::Plan->new( | |
438 | trace => $trace, | |
439 | max => $count, | |
440 | ) | |
441 | ); | |
442 | } | |
443 | $plan = $self->{+_PLAN}; | |
444 | } | |
445 | ||
446 | my $frame = $trace->frame; | |
447 | if($self->{+ENDED}) { | |
448 | my (undef, $ffile, $fline) = @{$self->{+ENDED}}; | |
449 | my (undef, $sfile, $sline) = @$frame; | |
450 | ||
451 | die <<" EOT" | |
452 | Test already ended! | |
453 | First End: $ffile line $fline | |
454 | Second End: $sfile line $sline | |
455 | EOT | |
456 | } | |
457 | ||
458 | $self->{+ENDED} = $frame; | |
c59be082 SH |
459 | my $pass = $self->is_passing(); # Generate the final boolean. |
460 | ||
07bc328a | 461 | $self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass, $self->is_subtest) if $self->{+_FORMATTER}; |
c59be082 SH |
462 | |
463 | return $pass; | |
b4514920 CG |
464 | } |
465 | ||
466 | sub is_passing { | |
467 | my $self = shift; | |
468 | ||
469 | ($self->{+_PASSING}) = @_ if @_; | |
470 | ||
471 | # If we already failed just return 0. | |
fa951d2c | 472 | my $pass = $self->{+_PASSING} or return 0; |
b4514920 CG |
473 | return $self->{+_PASSING} = 0 if $self->{+FAILED}; |
474 | ||
475 | my $count = $self->{+COUNT}; | |
476 | my $ended = $self->{+ENDED}; | |
477 | my $plan = $self->{+_PLAN}; | |
478 | ||
479 | return $pass if !$count && $plan && $plan =~ m/^SKIP$/; | |
480 | ||
481 | return $self->{+_PASSING} = 0 | |
482 | if $ended && (!$count || !$plan); | |
483 | ||
484 | return $pass unless $plan && $plan =~ m/^\d+$/; | |
485 | ||
486 | if ($ended) { | |
487 | return $self->{+_PASSING} = 0 if $count != $plan; | |
488 | } | |
489 | else { | |
490 | return $self->{+_PASSING} = 0 if $count > $plan; | |
491 | } | |
492 | ||
493 | return $pass; | |
494 | } | |
495 | ||
496 | sub plan { | |
497 | my $self = shift; | |
498 | ||
499 | return $self->{+_PLAN} unless @_; | |
500 | ||
501 | my ($plan) = @_; | |
502 | ||
503 | confess "You cannot unset the plan" | |
504 | unless defined $plan; | |
505 | ||
506 | confess "You cannot change the plan" | |
507 | if $self->{+_PLAN} && $self->{+_PLAN} !~ m/^NO PLAN$/; | |
508 | ||
509 | confess "'$plan' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'" | |
510 | unless $plan =~ m/^(\d+|NO PLAN|SKIP)$/; | |
511 | ||
512 | $self->{+_PLAN} = $plan; | |
513 | } | |
514 | ||
515 | sub check_plan { | |
516 | my $self = shift; | |
517 | ||
518 | return undef unless $self->{+ENDED}; | |
519 | my $plan = $self->{+_PLAN} || return undef; | |
520 | ||
521 | return 1 if $plan !~ m/^\d+$/; | |
522 | ||
523 | return 1 if $plan == $self->{+COUNT}; | |
524 | return 0; | |
525 | } | |
526 | ||
527 | sub DESTROY { | |
528 | my $self = shift; | |
529 | my $ipc = $self->{+IPC} || return; | |
530 | return unless $$ == $self->{+PID}; | |
531 | return unless get_tid() == $self->{+TID}; | |
b4514920 CG |
532 | $ipc->drop_hub($self->{+HID}); |
533 | } | |
534 | ||
535 | 1; | |
536 | ||
537 | __END__ | |
538 | ||
539 | =pod | |
540 | ||
541 | =encoding UTF-8 | |
542 | ||
543 | =head1 NAME | |
544 | ||
545 | Test2::Hub - The conduit through which all events flow. | |
546 | ||
547 | =head1 SYNOPSIS | |
548 | ||
549 | use Test2::Hub; | |
550 | ||
551 | my $hub = Test2::Hub->new(); | |
552 | $hub->send(...); | |
553 | ||
554 | =head1 DESCRIPTION | |
555 | ||
556 | The hub is the place where all events get processed and handed off to the | |
58818a66 | 557 | formatter. The hub also tracks test state, and provides several hooks into the |
b4514920 CG |
558 | event pipeline. |
559 | ||
560 | =head1 COMMON TASKS | |
561 | ||
562 | =head2 SENDING EVENTS | |
563 | ||
564 | $hub->send($event) | |
565 | ||
566 | The C<send()> method is used to issue an event to the hub. This method will | |
567 | handle thread/fork sync, filters, listeners, TAP output, etc. | |
568 | ||
569 | =head2 ALTERING OR REMOVING EVENTS | |
570 | ||
e7e8a349 | 571 | You can use either C<filter()> or C<pre_filter()>, depending on your |
b4514920 CG |
572 | needs. Both have identical syntax, so only C<filter()> is shown here. |
573 | ||
574 | $hub->filter(sub { | |
575 | my ($hub, $event) = @_; | |
576 | ||
577 | my $action = get_action($event); | |
578 | ||
579 | # No action should be taken | |
580 | return $event if $action eq 'none'; | |
581 | ||
582 | # You want your filter to remove the event | |
583 | return undef if $action eq 'delete'; | |
584 | ||
585 | if ($action eq 'do_it') { | |
586 | my $new_event = copy_event($event); | |
587 | ... Change your copy of the event ... | |
588 | return $new_event; | |
589 | } | |
590 | ||
591 | die "Should not happen"; | |
592 | }); | |
593 | ||
e7e8a349 | 594 | By default, filters are not inherited by child hubs. That means if you start a |
b4514920 CG |
595 | subtest, the subtest will not inherit the filter. You can change this behavior |
596 | with the C<inherit> parameter: | |
597 | ||
598 | $hub->filter(sub { ... }, inherit => 1); | |
599 | ||
600 | =head2 LISTENING FOR EVENTS | |
601 | ||
602 | $hub->listen(sub { | |
603 | my ($hub, $event, $number) = @_; | |
604 | ||
605 | ... do whatever you want with the event ... | |
606 | ||
607 | # return is ignored | |
608 | }); | |
609 | ||
e7e8a349 | 610 | By default listeners are not inherited by child hubs. That means if you start a |
b4514920 CG |
611 | subtest, the subtest will not inherit the listener. You can change this behavior |
612 | with the C<inherit> parameter: | |
613 | ||
614 | $hub->listen(sub { ... }, inherit => 1); | |
615 | ||
616 | ||
617 | =head2 POST-TEST BEHAVIORS | |
618 | ||
619 | $hub->follow_up(sub { | |
620 | my ($trace, $hub) = @_; | |
621 | ||
622 | ... do whatever you need to ... | |
623 | ||
624 | # Return is ignored | |
625 | }); | |
626 | ||
e7e8a349 | 627 | follow_up subs are called only once, either when done_testing is called, or in |
b4514920 CG |
628 | an END block. |
629 | ||
630 | =head2 SETTING THE FORMATTER | |
631 | ||
632 | By default an instance of L<Test2::Formatter::TAP> is created and used. | |
633 | ||
634 | my $old = $hub->format(My::Formatter->new); | |
635 | ||
636 | Setting the formatter will REPLACE any existing formatter. You may set the | |
637 | formatter to undef to prevent output. The old formatter will be returned if one | |
e7e8a349 | 638 | was already set. Only one formatter is allowed at a time. |
b4514920 CG |
639 | |
640 | =head1 METHODS | |
641 | ||
642 | =over 4 | |
643 | ||
644 | =item $hub->send($event) | |
645 | ||
646 | This is where all events enter the hub for processing. | |
647 | ||
648 | =item $hub->process($event) | |
649 | ||
650 | This is called by send after it does any IPC handling. You can use this to | |
651 | bypass the IPC process, but in general you should avoid using this. | |
652 | ||
653 | =item $old = $hub->format($formatter) | |
654 | ||
655 | Replace the existing formatter instance with a new one. Formatters must be | |
656 | objects that implement a C<< $formatter->write($event) >> method. | |
657 | ||
658 | =item $sub = $hub->listen(sub { ... }, %optional_params) | |
659 | ||
660 | You can use this to record all events AFTER they have been sent to the | |
661 | formatter. No changes made here will be meaningful, except possibly to other | |
662 | listeners. | |
663 | ||
664 | $hub->listen(sub { | |
665 | my ($hub, $event, $number) = @_; | |
666 | ||
667 | ... do whatever you want with the event ... | |
668 | ||
669 | # return is ignored | |
670 | }); | |
671 | ||
672 | Normally listeners are not inherited by child hubs such as subtests. You can | |
673 | add the C<< inherit => 1 >> parameter to allow a listener to be inherited. | |
674 | ||
675 | =item $hub->unlisten($sub) | |
676 | ||
677 | You can use this to remove a listen callback. You must pass in the coderef | |
678 | returned by the C<listen()> method. | |
679 | ||
680 | =item $sub = $hub->filter(sub { ... }, %optional_params) | |
681 | ||
682 | =item $sub = $hub->pre_filter(sub { ... }, %optional_params) | |
683 | ||
684 | These can be used to add filters. Filters can modify, replace, or remove events | |
685 | before anything else can see them. | |
686 | ||
687 | $hub->filter( | |
688 | sub { | |
689 | my ($hub, $event) = @_; | |
690 | ||
691 | return $event; # No Changes | |
692 | return; # Remove the event | |
693 | ||
694 | # Or you can modify an event before returning it. | |
695 | $event->modify; | |
696 | return $event; | |
697 | } | |
698 | ); | |
699 | ||
700 | If you are not using threads, forking, or IPC then the only difference between | |
701 | a C<filter> and a C<pre_filter> is that C<pre_filter> subs run first. When you | |
702 | are using threads, forking, or IPC, pre_filters happen to events before they | |
703 | are sent to their destination proc/thread, ordinary filters happen only in the | |
704 | destination hub/thread. | |
705 | ||
706 | You cannot add a regular filter to a hub if the hub was created in another | |
707 | process or thread. You can always add a pre_filter. | |
708 | ||
709 | =item $hub->unfilter($sub) | |
710 | ||
711 | =item $hub->pre_unfilter($sub) | |
712 | ||
713 | These can be used to remove filters and pre_filters. The C<$sub> argument is | |
714 | the reference returned by C<filter()> or C<pre_filter()>. | |
715 | ||
716 | =item $hub->follow_op(sub { ... }) | |
717 | ||
718 | Use this to add behaviors that are called just before the hub is finalized. The | |
07bc328a | 719 | only argument to your codeblock will be a L<Test2::EventFacet::Trace> instance. |
b4514920 CG |
720 | |
721 | $hub->follow_up(sub { | |
722 | my ($trace, $hub) = @_; | |
723 | ||
724 | ... do whatever you need to ... | |
725 | ||
726 | # Return is ignored | |
727 | }); | |
728 | ||
729 | follow_up subs are called only once, ether when done_testing is called, or in | |
730 | an END block. | |
731 | ||
732 | =item $sub = $hub->add_context_acquire(sub { ... }); | |
733 | ||
734 | Add a callback that will be called every time someone tries to acquire a | |
58818a66 | 735 | context. It gets a single argument, a reference of the hash of parameters |
b4514920 CG |
736 | being used the construct the context. This is your chance to change the |
737 | parameters by directly altering the hash. | |
738 | ||
739 | test2_add_callback_context_acquire(sub { | |
740 | my $params = shift; | |
741 | $params->{level}++; | |
742 | }); | |
743 | ||
744 | This is a very scary API function. Please do not use this unless you need to. | |
745 | This is here for L<Test::Builder> and backwards compatibility. This has you | |
746 | directly manipulate the hash instead of returning a new one for performance | |
747 | reasons. | |
748 | ||
749 | B<Note> Using this hook could have a huge performance impact. | |
750 | ||
751 | The coderef you provide is returned and can be used to remove the hook later. | |
752 | ||
753 | =item $hub->remove_context_acquire($sub); | |
754 | ||
755 | This can be used to remove a context acquire hook. | |
756 | ||
757 | =item $sub = $hub->add_context_init(sub { ... }); | |
758 | ||
759 | This allows you to add callbacks that will trigger every time a new context is | |
760 | created for the hub. The only argument to the sub will be the | |
761 | L<Test2::API::Context> instance that was created. | |
762 | ||
763 | B<Note> Using this hook could have a huge performance impact. | |
764 | ||
765 | The coderef you provide is returned and can be used to remove the hook later. | |
766 | ||
767 | =item $hub->remove_context_init($sub); | |
768 | ||
769 | This can be used to remove a context init hook. | |
770 | ||
771 | =item $sub = $hub->add_context_release(sub { ... }); | |
772 | ||
773 | This allows you to add callbacks that will trigger every time a context for | |
774 | this hub is released. The only argument to the sub will be the | |
775 | L<Test2::API::Context> instance that was released. These will run in reverse | |
776 | order. | |
777 | ||
778 | B<Note> Using this hook could have a huge performance impact. | |
779 | ||
780 | The coderef you provide is returned and can be used to remove the hook later. | |
781 | ||
782 | =item $hub->remove_context_release($sub); | |
783 | ||
784 | This can be used to remove a context release hook. | |
785 | ||
786 | =item $hub->cull() | |
787 | ||
788 | Cull any IPC events (and process them). | |
789 | ||
790 | =item $pid = $hub->pid() | |
791 | ||
792 | Get the process id under which the hub was created. | |
793 | ||
794 | =item $tid = $hub->tid() | |
795 | ||
796 | Get the thread id under which the hub was created. | |
797 | ||
798 | =item $hud = $hub->hid() | |
799 | ||
800 | Get the identifier string of the hub. | |
801 | ||
43de38c4 TR |
802 | =item $uuid = $hub->uuid() |
803 | ||
804 | If UUID tagging is enabled (see L<Test2::API>) then the hub will have a UUID. | |
805 | ||
b4514920 CG |
806 | =item $ipc = $hub->ipc() |
807 | ||
808 | Get the IPC object used by the hub. | |
809 | ||
810 | =item $hub->set_no_ending($bool) | |
811 | ||
812 | =item $bool = $hub->no_ending | |
813 | ||
814 | This can be used to disable auto-ending behavior for a hub. The auto-ending | |
815 | behavior is triggered by an end block and is used to cull IPC events, and | |
6c61bcd8 | 816 | output the final plan if the plan was 'NO PLAN'. |
b4514920 | 817 | |
9fe558dc SH |
818 | =item $bool = $hub->active |
819 | ||
820 | =item $hub->set_active($bool) | |
821 | ||
822 | These are used to get/set the 'active' attribute. When true this attribute will | |
823 | force C<< hub->finalize() >> to take action even if there is no plan, and no | |
824 | tests have been run. This flag is useful for plugins that add follow-up | |
825 | behaviors that need to run even if no events are seen. | |
826 | ||
b4514920 CG |
827 | =back |
828 | ||
829 | =head2 STATE METHODS | |
830 | ||
831 | =over 4 | |
832 | ||
833 | =item $hub->reset_state() | |
834 | ||
835 | Reset all state to the start. This sets the test count to 0, clears the plan, | |
836 | removes the failures, etc. | |
837 | ||
838 | =item $num = $hub->count | |
839 | ||
840 | Get the number of tests that have been run. | |
841 | ||
842 | =item $num = $hub->failed | |
843 | ||
844 | Get the number of failures (Not all failures come from a test fail, so this | |
845 | number can be larger than the count). | |
846 | ||
847 | =item $bool = $hub->ended | |
848 | ||
849 | True if the testing has ended. This MAY return the stack frame of the tool that | |
850 | ended the test, but that is not guaranteed. | |
851 | ||
852 | =item $bool = $hub->is_passing | |
853 | ||
854 | =item $hub->is_passing($bool) | |
855 | ||
856 | Check if the overall test run is a failure. Can also be used to set the | |
857 | pass/fail status. | |
858 | ||
859 | =item $hub->plan($plan) | |
860 | ||
861 | =item $plan = $hub->plan | |
862 | ||
863 | Get or set the plan. The plan must be an integer larger than 0, the string | |
6c61bcd8 | 864 | 'NO PLAN', or the string 'SKIP'. |
b4514920 CG |
865 | |
866 | =item $bool = $hub->check_plan | |
867 | ||
868 | Check if the plan and counts match, but only if the tests have ended. If tests | |
58818a66 | 869 | have not ended this will return undef, otherwise it will be a true/false. |
b4514920 CG |
870 | |
871 | =back | |
872 | ||
873 | =head1 THIRD PARTY META-DATA | |
874 | ||
875 | This object consumes L<Test2::Util::ExternalMeta> which provides a consistent | |
876 | way for you to attach meta-data to instances of this class. This is useful for | |
58818a66 | 877 | tools, plugins, and other extensions. |
b4514920 CG |
878 | |
879 | =head1 SOURCE | |
880 | ||
881 | The source code repository for Test2 can be found at | |
882 | F<http://github.com/Test-More/test-more/>. | |
883 | ||
884 | =head1 MAINTAINERS | |
885 | ||
886 | =over 4 | |
887 | ||
888 | =item Chad Granum E<lt>exodist@cpan.orgE<gt> | |
889 | ||
890 | =back | |
891 | ||
892 | =head1 AUTHORS | |
893 | ||
894 | =over 4 | |
895 | ||
896 | =item Chad Granum E<lt>exodist@cpan.orgE<gt> | |
897 | ||
898 | =back | |
899 | ||
900 | =head1 COPYRIGHT | |
901 | ||
18c72c39 | 902 | Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>. |
b4514920 CG |
903 | |
904 | This program is free software; you can redistribute it and/or | |
905 | modify it under the same terms as Perl itself. | |
906 | ||
907 | See F<http://dev.perl.org/licenses/> | |
908 | ||
909 | =cut |