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