1package Test2::Hub;
2use strict;
3use warnings;
4
5our $VERSION = '1.302199';
6
7
8use Carp qw/carp croak confess/;
9use Test2::Util qw/get_tid gen_uid/;
10
11use Scalar::Util qw/weaken/;
12use List::Util qw/first/;
13
14use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/;
15use Test2::Util::HashBase qw{
16    pid tid hid ipc
17    nested buffered
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
28    uuid
29    active
30    count
31    failed
32    ended
33    bailed_out
34    _passing
35    _plan
36    skip_reason
37};
38
39my $UUID_VIA;
40
41sub init {
42    my $self = shift;
43
44    $self->{+PID} = $$;
45    $self->{+TID} = get_tid();
46    $self->{+HID} = gen_uid();
47
48    $UUID_VIA ||= Test2::API::_add_uuid_via_ref();
49    $self->{+UUID} = ${$UUID_VIA}->('hub') if $$UUID_VIA;
50
51    $self->{+NESTED}   = 0 unless defined $self->{+NESTED};
52    $self->{+BUFFERED} = 0 unless defined $self->{+BUFFERED};
53
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
67sub is_subtest { 0 }
68
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();
77    $self->{+HID} = gen_uid();
78
79    if (my $ipc = $self->{+IPC}) {
80        $ipc->add_hub($self->{+HID});
81    }
82}
83
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
101    $self->{+NESTED} ||= 0;
102
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
116    if (my $pfs = $from->{+_PRE_FILTERS}) {
117        push @{$self->{+_PRE_FILTERS}} => grep { $_->{inherit} } @$pfs;
118    }
119
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
280    $e->eid;
281
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
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
330    # Optimize the most common case
331    my $type = ref($e);
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};
335
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;
344
345    my $fail = 0;
346    $fail = 1 if $f->{assert} && !$f->{assert}->{pass};
347    $fail = 1 if $f->{errors} && grep { $_->{fail} } @{$f->{errors}};
348    $fail = 0 if $f->{amnesty};
349
350    $self->{+COUNT}++ if $f->{assert};
351    $self->{+FAILED}++ if $fail && $f->{assert};
352    $self->{+_PASSING} = 0 if $fail;
353
354    my $code = $f->{control} ? $f->{control}->{terminate} : undef;
355    my $count = $self->{+COUNT};
356
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
371    $e->callback($self) if $f->{control} && $f->{control}->{has_callback};
372
373    $self->{+_FORMATTER}->write($e, $count, $f) if $self->{+_FORMATTER};
374
375    if ($self->{+_LISTENERS}) {
376        $_->{code}->($self, $e, $count, $f) for @{$self->{+_LISTENERS}};
377    }
378
379    if ($f->{control} && $f->{control}->{halt}) {
380        $code ||= 255;
381        $self->set_bailed_out($e);
382    }
383
384    if (defined $code) {
385        $self->{+_FORMATTER}->terminate($e, $f) if $self->{+_FORMATTER};
386        $self->terminate($code, $e, $f);
387    }
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};
417    my $active = $self->{+ACTIVE};
418
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    }
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
435        if ((defined($plan) && $plan eq 'NO PLAN') || ($do_plan && !defined($plan))) {
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;
459    my $pass = $self->is_passing(); # Generate the final boolean.
460
461    $self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass, $self->is_subtest) if $self->{+_FORMATTER};
462
463    return $pass;
464}
465
466sub is_passing {
467    my $self = shift;
468
469    ($self->{+_PASSING}) = @_ if @_;
470
471    # If we already failed just return 0.
472    my $pass = $self->{+_PASSING} or return 0;
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};
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
557formatter. The hub also tracks test state, and provides several hooks into the
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
571You can use either C<filter()> or C<pre_filter()>, depending on your
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
594By default, filters are not inherited by child hubs. That means if you start a
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
610By default listeners are not inherited by child hubs. That means if you start a
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
627follow_up subs are called only once, either when done_testing is called, or in
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
638was already set. Only one formatter is allowed at a time.
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
719only argument to your codeblock will be a L<Test2::EventFacet::Trace> instance.
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
735context. It gets a single argument, a reference of the hash of parameters
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
802=item $uuid = $hub->uuid()
803
804If UUID tagging is enabled (see L<Test2::API>) then the hub will have a UUID.
805
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
816output the final plan if the plan was 'NO PLAN'.
817
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
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
864'NO PLAN', or the string 'SKIP'.
865
866=item $bool = $hub->check_plan
867
868Check if the plan and counts match, but only if the tests have ended. If tests
869have not ended this will return undef, otherwise it will be a true/false.
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
877tools, plugins, and other extensions.
878
879=head1 SOURCE
880
881The source code repository for Test2 can be found at
882L<https://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
902Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<gt>.
903
904This program is free software; you can redistribute it and/or
905modify it under the same terms as Perl itself.
906
907See L<https://dev.perl.org/licenses/>
908
909=cut
910