This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
new perldelta
[perl5.git] / cpan / Test-Simple / lib / Test2 / Hub.pm
CommitLineData
b4514920
CG
1package Test2::Hub;
2use strict;
3use warnings;
4
cf76a266 5our $VERSION = '1.302198';
b4514920
CG
6
7
8use Carp qw/carp croak confess/;
e82ffdf2 9use Test2::Util qw/get_tid gen_uid/;
b4514920
CG
10
11use Scalar::Util qw/weaken/;
07bc328a 12use List::Util qw/first/;
b4514920
CG
13
14use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/;
15use 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
39my $UUID_VIA;
40
b4514920
CG
41sub 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
67sub is_subtest { 0 }
68
07bc328a
SH
69sub _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
84sub 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
97sub 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
125sub format {
126 my $self = shift;
127
128 my $old = $self->{+_FORMATTER};
129 ($self->{+_FORMATTER}) = @_ if @_;
130
131 return $old;
132}
133
134sub is_local {
135 my $self = shift;
136 return $$ == $self->{+PID}
137 && get_tid() == $self->{+TID};
138}
139
140sub 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
155sub 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
166sub 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
181sub 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
189sub 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
201sub pre_unfilter {
202 my $self = shift;
203 my %subs = map {$_ => $_} @_;
204 @{$self->{+_PRE_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_PRE_FILTERS}};
205}
206
207sub 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;
221sub 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;
234sub remove_context_acquire {
235 my $self = shift;
236 my %subs = map {$_ => $_} @_;
237 @{$self->{+_CONTEXT_ACQUIRE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_ACQUIRE}};
238}
239
240sub 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
252sub remove_context_init {
253 my $self = shift;
254 my %subs = map {$_ => $_} @_;
255 @{$self->{+_CONTEXT_INIT}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_INIT}};
256}
257
258sub 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
270sub remove_context_release {
271 my $self = shift;
272 my %subs = map {$_ => $_} @_;
273 @{$self->{+_CONTEXT_RELEASE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_RELEASE}};
274}
275
276sub 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
319sub 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
392sub terminate {
393 my $self = shift;
394 my ($code) = @_;
395 exit($code);
396}
397
398sub 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
408sub 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"
452Test already ended!
453First End: $ffile line $fline
454Second 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
466sub 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
496sub 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
515sub 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
527sub 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
5351;
536
537__END__
538
539=pod
540
541=encoding UTF-8
542
543=head1 NAME
544
545Test2::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
556The hub is the place where all events get processed and handed off to the
58818a66 557formatter. The hub also tracks test state, and provides several hooks into the
b4514920
CG
558event pipeline.
559
560=head1 COMMON TASKS
561
562=head2 SENDING EVENTS
563
564 $hub->send($event)
565
566The C<send()> method is used to issue an event to the hub. This method will
567handle thread/fork sync, filters, listeners, TAP output, etc.
568
569=head2 ALTERING OR REMOVING EVENTS
570
e7e8a349 571You can use either C<filter()> or C<pre_filter()>, depending on your
b4514920
CG
572needs. 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 594By default, filters are not inherited by child hubs. That means if you start a
b4514920
CG
595subtest, the subtest will not inherit the filter. You can change this behavior
596with 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 610By default listeners are not inherited by child hubs. That means if you start a
b4514920
CG
611subtest, the subtest will not inherit the listener. You can change this behavior
612with 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 627follow_up subs are called only once, either when done_testing is called, or in
b4514920
CG
628an END block.
629
630=head2 SETTING THE FORMATTER
631
632By default an instance of L<Test2::Formatter::TAP> is created and used.
633
634 my $old = $hub->format(My::Formatter->new);
635
636Setting the formatter will REPLACE any existing formatter. You may set the
637formatter to undef to prevent output. The old formatter will be returned if one
e7e8a349 638was 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
646This is where all events enter the hub for processing.
647
648=item $hub->process($event)
649
650This is called by send after it does any IPC handling. You can use this to
651bypass the IPC process, but in general you should avoid using this.
652
653=item $old = $hub->format($formatter)
654
655Replace the existing formatter instance with a new one. Formatters must be
656objects that implement a C<< $formatter->write($event) >> method.
657
658=item $sub = $hub->listen(sub { ... }, %optional_params)
659
660You can use this to record all events AFTER they have been sent to the
661formatter. No changes made here will be meaningful, except possibly to other
662listeners.
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
672Normally listeners are not inherited by child hubs such as subtests. You can
673add the C<< inherit => 1 >> parameter to allow a listener to be inherited.
674
675=item $hub->unlisten($sub)
676
677You can use this to remove a listen callback. You must pass in the coderef
678returned 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
684These can be used to add filters. Filters can modify, replace, or remove events
685before 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
700If you are not using threads, forking, or IPC then the only difference between
701a C<filter> and a C<pre_filter> is that C<pre_filter> subs run first. When you
702are using threads, forking, or IPC, pre_filters happen to events before they
703are sent to their destination proc/thread, ordinary filters happen only in the
704destination hub/thread.
705
706You cannot add a regular filter to a hub if the hub was created in another
707process or thread. You can always add a pre_filter.
708
709=item $hub->unfilter($sub)
710
711=item $hub->pre_unfilter($sub)
712
713These can be used to remove filters and pre_filters. The C<$sub> argument is
714the reference returned by C<filter()> or C<pre_filter()>.
715
716=item $hub->follow_op(sub { ... })
717
718Use this to add behaviors that are called just before the hub is finalized. The
07bc328a 719only 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
729follow_up subs are called only once, ether when done_testing is called, or in
730an END block.
731
732=item $sub = $hub->add_context_acquire(sub { ... });
733
734Add a callback that will be called every time someone tries to acquire a
58818a66 735context. It gets a single argument, a reference of the hash of parameters
b4514920
CG
736being used the construct the context. This is your chance to change the
737parameters by directly altering the hash.
738
739 test2_add_callback_context_acquire(sub {
740 my $params = shift;
741 $params->{level}++;
742 });
743
744This is a very scary API function. Please do not use this unless you need to.
745This is here for L<Test::Builder> and backwards compatibility. This has you
746directly manipulate the hash instead of returning a new one for performance
747reasons.
748
749B<Note> Using this hook could have a huge performance impact.
750
751The coderef you provide is returned and can be used to remove the hook later.
752
753=item $hub->remove_context_acquire($sub);
754
755This can be used to remove a context acquire hook.
756
757=item $sub = $hub->add_context_init(sub { ... });
758
759This allows you to add callbacks that will trigger every time a new context is
760created for the hub. The only argument to the sub will be the
761L<Test2::API::Context> instance that was created.
762
763B<Note> Using this hook could have a huge performance impact.
764
765The coderef you provide is returned and can be used to remove the hook later.
766
767=item $hub->remove_context_init($sub);
768
769This can be used to remove a context init hook.
770
771=item $sub = $hub->add_context_release(sub { ... });
772
773This allows you to add callbacks that will trigger every time a context for
774this hub is released. The only argument to the sub will be the
775L<Test2::API::Context> instance that was released. These will run in reverse
776order.
777
778B<Note> Using this hook could have a huge performance impact.
779
780The coderef you provide is returned and can be used to remove the hook later.
781
782=item $hub->remove_context_release($sub);
783
784This can be used to remove a context release hook.
785
786=item $hub->cull()
787
788Cull any IPC events (and process them).
789
790=item $pid = $hub->pid()
791
792Get the process id under which the hub was created.
793
794=item $tid = $hub->tid()
795
796Get the thread id under which the hub was created.
797
798=item $hud = $hub->hid()
799
800Get the identifier string of the hub.
801
43de38c4
TR
802=item $uuid = $hub->uuid()
803
804If UUID tagging is enabled (see L<Test2::API>) then the hub will have a UUID.
805
b4514920
CG
806=item $ipc = $hub->ipc()
807
808Get the IPC object used by the hub.
809
810=item $hub->set_no_ending($bool)
811
812=item $bool = $hub->no_ending
813
814This can be used to disable auto-ending behavior for a hub. The auto-ending
815behavior is triggered by an end block and is used to cull IPC events, and
6c61bcd8 816output 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
822These are used to get/set the 'active' attribute. When true this attribute will
823force C<< hub->finalize() >> to take action even if there is no plan, and no
824tests have been run. This flag is useful for plugins that add follow-up
825behaviors 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
835Reset all state to the start. This sets the test count to 0, clears the plan,
836removes the failures, etc.
837
838=item $num = $hub->count
839
840Get the number of tests that have been run.
841
842=item $num = $hub->failed
843
844Get the number of failures (Not all failures come from a test fail, so this
845number can be larger than the count).
846
847=item $bool = $hub->ended
848
849True if the testing has ended. This MAY return the stack frame of the tool that
850ended the test, but that is not guaranteed.
851
852=item $bool = $hub->is_passing
853
854=item $hub->is_passing($bool)
855
856Check if the overall test run is a failure. Can also be used to set the
857pass/fail status.
858
859=item $hub->plan($plan)
860
861=item $plan = $hub->plan
862
863Get 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
868Check if the plan and counts match, but only if the tests have ended. If tests
58818a66 869have 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
875This object consumes L<Test2::Util::ExternalMeta> which provides a consistent
876way for you to attach meta-data to instances of this class. This is useful for
58818a66 877tools, plugins, and other extensions.
b4514920
CG
878
879=head1 SOURCE
880
881The source code repository for Test2 can be found at
882F<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 902Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>.
b4514920
CG
903
904This program is free software; you can redistribute it and/or
905modify it under the same terms as Perl itself.
906
907See F<http://dev.perl.org/licenses/>
908
909=cut