Spaces:
Sleeping
Sleeping
package Test2::Hub; | |
use strict; | |
use warnings; | |
our $VERSION = '1.302183'; | |
use Carp qw/carp croak confess/; | |
use Test2::Util qw/get_tid gen_uid/; | |
use Scalar::Util qw/weaken/; | |
use List::Util qw/first/; | |
use Test2::Util::ExternalMeta qw/meta get_meta set_meta delete_meta/; | |
use Test2::Util::HashBase qw{ | |
pid tid hid ipc | |
nested buffered | |
no_ending | |
_filters | |
_pre_filters | |
_listeners | |
_follow_ups | |
_formatter | |
_context_acquire | |
_context_init | |
_context_release | |
uuid | |
active | |
count | |
failed | |
ended | |
bailed_out | |
_passing | |
_plan | |
skip_reason | |
}; | |
my $UUID_VIA; | |
sub init { | |
my $self = shift; | |
$self->{+PID} = $$; | |
$self->{+TID} = get_tid(); | |
$self->{+HID} = gen_uid(); | |
$UUID_VIA ||= Test2::API::_add_uuid_via_ref(); | |
$self->{+UUID} = ${$UUID_VIA}->('hub') if $$UUID_VIA; | |
$self->{+NESTED} = 0 unless defined $self->{+NESTED}; | |
$self->{+BUFFERED} = 0 unless defined $self->{+BUFFERED}; | |
$self->{+COUNT} = 0; | |
$self->{+FAILED} = 0; | |
$self->{+_PASSING} = 1; | |
if (my $formatter = delete $self->{formatter}) { | |
$self->format($formatter); | |
} | |
if (my $ipc = $self->{+IPC}) { | |
$ipc->add_hub($self->{+HID}); | |
} | |
} | |
sub is_subtest { 0 } | |
sub _tb_reset { | |
my $self = shift; | |
# Nothing to do | |
return if $self->{+PID} == $$ && $self->{+TID} == get_tid(); | |
$self->{+PID} = $$; | |
$self->{+TID} = get_tid(); | |
$self->{+HID} = gen_uid(); | |
if (my $ipc = $self->{+IPC}) { | |
$ipc->add_hub($self->{+HID}); | |
} | |
} | |
sub reset_state { | |
my $self = shift; | |
$self->{+COUNT} = 0; | |
$self->{+FAILED} = 0; | |
$self->{+_PASSING} = 1; | |
delete $self->{+_PLAN}; | |
delete $self->{+ENDED}; | |
delete $self->{+BAILED_OUT}; | |
delete $self->{+SKIP_REASON}; | |
} | |
sub inherit { | |
my $self = shift; | |
my ($from, %params) = @_; | |
$self->{+NESTED} ||= 0; | |
$self->{+_FORMATTER} = $from->{+_FORMATTER} | |
unless $self->{+_FORMATTER} || exists($params{formatter}); | |
if ($from->{+IPC} && !$self->{+IPC} && !exists($params{ipc})) { | |
my $ipc = $from->{+IPC}; | |
$self->{+IPC} = $ipc; | |
$ipc->add_hub($self->{+HID}); | |
} | |
if (my $ls = $from->{+_LISTENERS}) { | |
push @{$self->{+_LISTENERS}} => grep { $_->{inherit} } @$ls; | |
} | |
if (my $pfs = $from->{+_PRE_FILTERS}) { | |
push @{$self->{+_PRE_FILTERS}} => grep { $_->{inherit} } @$pfs; | |
} | |
if (my $fs = $from->{+_FILTERS}) { | |
push @{$self->{+_FILTERS}} => grep { $_->{inherit} } @$fs; | |
} | |
} | |
sub format { | |
my $self = shift; | |
my $old = $self->{+_FORMATTER}; | |
($self->{+_FORMATTER}) = @_ if @_; | |
return $old; | |
} | |
sub is_local { | |
my $self = shift; | |
return $$ == $self->{+PID} | |
&& get_tid() == $self->{+TID}; | |
} | |
sub listen { | |
my $self = shift; | |
my ($sub, %params) = @_; | |
carp "Useless addition of a listener in a child process or thread!" | |
if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
croak "listen only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_LISTENERS}} => { %params, code => $sub }; | |
$sub; # Intentional return. | |
} | |
sub unlisten { | |
my $self = shift; | |
carp "Useless removal of a listener in a child process or thread!" | |
if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
my %subs = map {$_ => $_} @_; | |
@{$self->{+_LISTENERS}} = grep { !$subs{$_->{code}} } @{$self->{+_LISTENERS}}; | |
} | |
sub filter { | |
my $self = shift; | |
my ($sub, %params) = @_; | |
carp "Useless addition of a filter in a child process or thread!" | |
if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
croak "filter only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_FILTERS}} => { %params, code => $sub }; | |
$sub; # Intentional Return | |
} | |
sub unfilter { | |
my $self = shift; | |
carp "Useless removal of a filter in a child process or thread!" | |
if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
my %subs = map {$_ => $_} @_; | |
@{$self->{+_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_FILTERS}}; | |
} | |
sub pre_filter { | |
my $self = shift; | |
my ($sub, %params) = @_; | |
croak "pre_filter only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_PRE_FILTERS}} => { %params, code => $sub }; | |
$sub; # Intentional Return | |
} | |
sub pre_unfilter { | |
my $self = shift; | |
my %subs = map {$_ => $_} @_; | |
@{$self->{+_PRE_FILTERS}} = grep { !$subs{$_->{code}} } @{$self->{+_PRE_FILTERS}}; | |
} | |
sub follow_up { | |
my $self = shift; | |
my ($sub) = @_; | |
carp "Useless addition of a follow-up in a child process or thread!" | |
if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
croak "follow_up only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_FOLLOW_UPS}} => $sub; | |
} | |
*add_context_aquire = \&add_context_acquire; | |
sub add_context_acquire { | |
my $self = shift; | |
my ($sub) = @_; | |
croak "add_context_acquire only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_CONTEXT_ACQUIRE}} => $sub; | |
$sub; # Intentional return. | |
} | |
*remove_context_aquire = \&remove_context_acquire; | |
sub remove_context_acquire { | |
my $self = shift; | |
my %subs = map {$_ => $_} @_; | |
@{$self->{+_CONTEXT_ACQUIRE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_ACQUIRE}}; | |
} | |
sub add_context_init { | |
my $self = shift; | |
my ($sub) = @_; | |
croak "add_context_init only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_CONTEXT_INIT}} => $sub; | |
$sub; # Intentional return. | |
} | |
sub remove_context_init { | |
my $self = shift; | |
my %subs = map {$_ => $_} @_; | |
@{$self->{+_CONTEXT_INIT}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_INIT}}; | |
} | |
sub add_context_release { | |
my $self = shift; | |
my ($sub) = @_; | |
croak "add_context_release only takes coderefs for arguments, got '$sub'" | |
unless ref $sub && ref $sub eq 'CODE'; | |
push @{$self->{+_CONTEXT_RELEASE}} => $sub; | |
$sub; # Intentional return. | |
} | |
sub remove_context_release { | |
my $self = shift; | |
my %subs = map {$_ => $_} @_; | |
@{$self->{+_CONTEXT_RELEASE}} = grep { !$subs{$_} == $_ } @{$self->{+_CONTEXT_RELEASE}}; | |
} | |
sub send { | |
my $self = shift; | |
my ($e) = @_; | |
$e->eid; | |
$e->add_hub( | |
{ | |
details => ref($self), | |
buffered => $self->{+BUFFERED}, | |
hid => $self->{+HID}, | |
nested => $self->{+NESTED}, | |
pid => $self->{+PID}, | |
tid => $self->{+TID}, | |
uuid => $self->{+UUID}, | |
ipc => $self->{+IPC} ? 1 : 0, | |
} | |
); | |
$e->set_uuid(${$UUID_VIA}->('event')) if $$UUID_VIA; | |
if ($self->{+_PRE_FILTERS}) { | |
for (@{$self->{+_PRE_FILTERS}}) { | |
$e = $_->{code}->($self, $e); | |
return unless $e; | |
} | |
} | |
my $ipc = $self->{+IPC} || return $self->process($e); | |
if($e->global) { | |
$ipc->send($self->{+HID}, $e, 'GLOBAL'); | |
return $self->process($e); | |
} | |
return $ipc->send($self->{+HID}, $e) | |
if $$ != $self->{+PID} || get_tid() != $self->{+TID}; | |
$self->process($e); | |
} | |
sub process { | |
my $self = shift; | |
my ($e) = @_; | |
if ($self->{+_FILTERS}) { | |
for (@{$self->{+_FILTERS}}) { | |
$e = $_->{code}->($self, $e); | |
return unless $e; | |
} | |
} | |
# Optimize the most common case | |
my $type = ref($e); | |
if ($type eq 'Test2::Event::Pass' || ($type eq 'Test2::Event::Ok' && $e->{pass})) { | |
my $count = ++($self->{+COUNT}); | |
$self->{+_FORMATTER}->write($e, $count) if $self->{+_FORMATTER}; | |
if ($self->{+_LISTENERS}) { | |
$_->{code}->($self, $e, $count) for @{$self->{+_LISTENERS}}; | |
} | |
return $e; | |
} | |
my $f = $e->facet_data; | |
my $fail = 0; | |
$fail = 1 if $f->{assert} && !$f->{assert}->{pass}; | |
$fail = 1 if $f->{errors} && grep { $_->{fail} } @{$f->{errors}}; | |
$fail = 0 if $f->{amnesty}; | |
$self->{+COUNT}++ if $f->{assert}; | |
$self->{+FAILED}++ if $fail && $f->{assert}; | |
$self->{+_PASSING} = 0 if $fail; | |
my $code = $f->{control} ? $f->{control}->{terminate} : undef; | |
my $count = $self->{+COUNT}; | |
if (my $plan = $f->{plan}) { | |
if ($plan->{skip}) { | |
$self->plan('SKIP'); | |
$self->set_skip_reason($plan->{details} || 1); | |
$code ||= 0; | |
} | |
elsif ($plan->{none}) { | |
$self->plan('NO PLAN'); | |
} | |
else { | |
$self->plan($plan->{count}); | |
} | |
} | |
$e->callback($self) if $f->{control} && $f->{control}->{has_callback}; | |
$self->{+_FORMATTER}->write($e, $count, $f) if $self->{+_FORMATTER}; | |
if ($self->{+_LISTENERS}) { | |
$_->{code}->($self, $e, $count, $f) for @{$self->{+_LISTENERS}}; | |
} | |
if ($f->{control} && $f->{control}->{halt}) { | |
$code ||= 255; | |
$self->set_bailed_out($e); | |
} | |
if (defined $code) { | |
$self->{+_FORMATTER}->terminate($e, $f) if $self->{+_FORMATTER}; | |
$self->terminate($code, $e, $f); | |
} | |
return $e; | |
} | |
sub terminate { | |
my $self = shift; | |
my ($code) = @_; | |
exit($code); | |
} | |
sub cull { | |
my $self = shift; | |
my $ipc = $self->{+IPC} || return; | |
return if $self->{+PID} != $$ || $self->{+TID} != get_tid(); | |
# No need to do IPC checks on culled events | |
$self->process($_) for $ipc->cull($self->{+HID}); | |
} | |
sub finalize { | |
my $self = shift; | |
my ($trace, $do_plan) = @_; | |
$self->cull(); | |
my $plan = $self->{+_PLAN}; | |
my $count = $self->{+COUNT}; | |
my $failed = $self->{+FAILED}; | |
my $active = $self->{+ACTIVE}; | |
# return if NOTHING was done. | |
unless ($active || $do_plan || defined($plan) || $count || $failed) { | |
$self->{+_FORMATTER}->finalize($plan, $count, $failed, 0, $self->is_subtest) if $self->{+_FORMATTER}; | |
return; | |
} | |
unless ($self->{+ENDED}) { | |
if ($self->{+_FOLLOW_UPS}) { | |
$_->($trace, $self) for reverse @{$self->{+_FOLLOW_UPS}}; | |
} | |
# These need to be refreshed now | |
$plan = $self->{+_PLAN}; | |
$count = $self->{+COUNT}; | |
$failed = $self->{+FAILED}; | |
if (($plan && $plan eq 'NO PLAN') || ($do_plan && !$plan)) { | |
$self->send( | |
Test2::Event::Plan->new( | |
trace => $trace, | |
max => $count, | |
) | |
); | |
} | |
$plan = $self->{+_PLAN}; | |
} | |
my $frame = $trace->frame; | |
if($self->{+ENDED}) { | |
my (undef, $ffile, $fline) = @{$self->{+ENDED}}; | |
my (undef, $sfile, $sline) = @$frame; | |
die <<" EOT" | |
Test already ended! | |
First End: $ffile line $fline | |
Second End: $sfile line $sline | |
EOT | |
} | |
$self->{+ENDED} = $frame; | |
my $pass = $self->is_passing(); # Generate the final boolean. | |
$self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass, $self->is_subtest) if $self->{+_FORMATTER}; | |
return $pass; | |
} | |
sub is_passing { | |
my $self = shift; | |
($self->{+_PASSING}) = @_ if @_; | |
# If we already failed just return 0. | |
my $pass = $self->{+_PASSING} or return 0; | |
return $self->{+_PASSING} = 0 if $self->{+FAILED}; | |
my $count = $self->{+COUNT}; | |
my $ended = $self->{+ENDED}; | |
my $plan = $self->{+_PLAN}; | |
return $pass if !$count && $plan && $plan =~ m/^SKIP$/; | |
return $self->{+_PASSING} = 0 | |
if $ended && (!$count || !$plan); | |
return $pass unless $plan && $plan =~ m/^\d+$/; | |
if ($ended) { | |
return $self->{+_PASSING} = 0 if $count != $plan; | |
} | |
else { | |
return $self->{+_PASSING} = 0 if $count > $plan; | |
} | |
return $pass; | |
} | |
sub plan { | |
my $self = shift; | |
return $self->{+_PLAN} unless @_; | |
my ($plan) = @_; | |
confess "You cannot unset the plan" | |
unless defined $plan; | |
confess "You cannot change the plan" | |
if $self->{+_PLAN} && $self->{+_PLAN} !~ m/^NO PLAN$/; | |
confess "'$plan' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'" | |
unless $plan =~ m/^(\d+|NO PLAN|SKIP)$/; | |
$self->{+_PLAN} = $plan; | |
} | |
sub check_plan { | |
my $self = shift; | |
return undef unless $self->{+ENDED}; | |
my $plan = $self->{+_PLAN} || return undef; | |
return 1 if $plan !~ m/^\d+$/; | |
return 1 if $plan == $self->{+COUNT}; | |
return 0; | |
} | |
sub DESTROY { | |
my $self = shift; | |
my $ipc = $self->{+IPC} || return; | |
return unless $$ == $self->{+PID}; | |
return unless get_tid() == $self->{+TID}; | |
$ipc->drop_hub($self->{+HID}); | |
} | |
1; | |
__END__ | |
=pod | |
=encoding UTF-8 | |
=head1 NAME | |
Test2::Hub - The conduit through which all events flow. | |
=head1 SYNOPSIS | |
use Test2::Hub; | |
my $hub = Test2::Hub->new(); | |
$hub->send(...); | |
=head1 DESCRIPTION | |
The hub is the place where all events get processed and handed off to the | |
formatter. The hub also tracks test state, and provides several hooks into the | |
event pipeline. | |
=head1 COMMON TASKS | |
=head2 SENDING EVENTS | |
$hub->send($event) | |
The C<send()> method is used to issue an event to the hub. This method will | |
handle thread/fork sync, filters, listeners, TAP output, etc. | |
=head2 ALTERING OR REMOVING EVENTS | |
You can use either C<filter()> or C<pre_filter()>, depending on your | |
needs. Both have identical syntax, so only C<filter()> is shown here. | |
$hub->filter(sub { | |
my ($hub, $event) = @_; | |
my $action = get_action($event); | |
# No action should be taken | |
return $event if $action eq 'none'; | |
# You want your filter to remove the event | |
return undef if $action eq 'delete'; | |
if ($action eq 'do_it') { | |
my $new_event = copy_event($event); | |
... Change your copy of the event ... | |
return $new_event; | |
} | |
die "Should not happen"; | |
}); | |
By default, filters are not inherited by child hubs. That means if you start a | |
subtest, the subtest will not inherit the filter. You can change this behavior | |
with the C<inherit> parameter: | |
$hub->filter(sub { ... }, inherit => 1); | |
=head2 LISTENING FOR EVENTS | |
$hub->listen(sub { | |
my ($hub, $event, $number) = @_; | |
... do whatever you want with the event ... | |
# return is ignored | |
}); | |
By default listeners are not inherited by child hubs. That means if you start a | |
subtest, the subtest will not inherit the listener. You can change this behavior | |
with the C<inherit> parameter: | |
$hub->listen(sub { ... }, inherit => 1); | |
=head2 POST-TEST BEHAVIORS | |
$hub->follow_up(sub { | |
my ($trace, $hub) = @_; | |
... do whatever you need to ... | |
# Return is ignored | |
}); | |
follow_up subs are called only once, either when done_testing is called, or in | |
an END block. | |
=head2 SETTING THE FORMATTER | |
By default an instance of L<Test2::Formatter::TAP> is created and used. | |
my $old = $hub->format(My::Formatter->new); | |
Setting the formatter will REPLACE any existing formatter. You may set the | |
formatter to undef to prevent output. The old formatter will be returned if one | |
was already set. Only one formatter is allowed at a time. | |
=head1 METHODS | |
=over 4 | |
=item $hub->send($event) | |
This is where all events enter the hub for processing. | |
=item $hub->process($event) | |
This is called by send after it does any IPC handling. You can use this to | |
bypass the IPC process, but in general you should avoid using this. | |
=item $old = $hub->format($formatter) | |
Replace the existing formatter instance with a new one. Formatters must be | |
objects that implement a C<< $formatter->write($event) >> method. | |
=item $sub = $hub->listen(sub { ... }, %optional_params) | |
You can use this to record all events AFTER they have been sent to the | |
formatter. No changes made here will be meaningful, except possibly to other | |
listeners. | |
$hub->listen(sub { | |
my ($hub, $event, $number) = @_; | |
... do whatever you want with the event ... | |
# return is ignored | |
}); | |
Normally listeners are not inherited by child hubs such as subtests. You can | |
add the C<< inherit => 1 >> parameter to allow a listener to be inherited. | |
=item $hub->unlisten($sub) | |
You can use this to remove a listen callback. You must pass in the coderef | |
returned by the C<listen()> method. | |
=item $sub = $hub->filter(sub { ... }, %optional_params) | |
=item $sub = $hub->pre_filter(sub { ... }, %optional_params) | |
These can be used to add filters. Filters can modify, replace, or remove events | |
before anything else can see them. | |
$hub->filter( | |
sub { | |
my ($hub, $event) = @_; | |
return $event; # No Changes | |
return; # Remove the event | |
# Or you can modify an event before returning it. | |
$event->modify; | |
return $event; | |
} | |
); | |
If you are not using threads, forking, or IPC then the only difference between | |
a C<filter> and a C<pre_filter> is that C<pre_filter> subs run first. When you | |
are using threads, forking, or IPC, pre_filters happen to events before they | |
are sent to their destination proc/thread, ordinary filters happen only in the | |
destination hub/thread. | |
You cannot add a regular filter to a hub if the hub was created in another | |
process or thread. You can always add a pre_filter. | |
=item $hub->unfilter($sub) | |
=item $hub->pre_unfilter($sub) | |
These can be used to remove filters and pre_filters. The C<$sub> argument is | |
the reference returned by C<filter()> or C<pre_filter()>. | |
=item $hub->follow_op(sub { ... }) | |
Use this to add behaviors that are called just before the hub is finalized. The | |
only argument to your codeblock will be a L<Test2::EventFacet::Trace> instance. | |
$hub->follow_up(sub { | |
my ($trace, $hub) = @_; | |
... do whatever you need to ... | |
# Return is ignored | |
}); | |
follow_up subs are called only once, ether when done_testing is called, or in | |
an END block. | |
=item $sub = $hub->add_context_acquire(sub { ... }); | |
Add a callback that will be called every time someone tries to acquire a | |
context. It gets a single argument, a reference of the hash of parameters | |
being used the construct the context. This is your chance to change the | |
parameters by directly altering the hash. | |
test2_add_callback_context_acquire(sub { | |
my $params = shift; | |
$params->{level}++; | |
}); | |
This is a very scary API function. Please do not use this unless you need to. | |
This is here for L<Test::Builder> and backwards compatibility. This has you | |
directly manipulate the hash instead of returning a new one for performance | |
reasons. | |
B<Note> Using this hook could have a huge performance impact. | |
The coderef you provide is returned and can be used to remove the hook later. | |
=item $hub->remove_context_acquire($sub); | |
This can be used to remove a context acquire hook. | |
=item $sub = $hub->add_context_init(sub { ... }); | |
This allows you to add callbacks that will trigger every time a new context is | |
created for the hub. The only argument to the sub will be the | |
L<Test2::API::Context> instance that was created. | |
B<Note> Using this hook could have a huge performance impact. | |
The coderef you provide is returned and can be used to remove the hook later. | |
=item $hub->remove_context_init($sub); | |
This can be used to remove a context init hook. | |
=item $sub = $hub->add_context_release(sub { ... }); | |
This allows you to add callbacks that will trigger every time a context for | |
this hub is released. The only argument to the sub will be the | |
L<Test2::API::Context> instance that was released. These will run in reverse | |
order. | |
B<Note> Using this hook could have a huge performance impact. | |
The coderef you provide is returned and can be used to remove the hook later. | |
=item $hub->remove_context_release($sub); | |
This can be used to remove a context release hook. | |
=item $hub->cull() | |
Cull any IPC events (and process them). | |
=item $pid = $hub->pid() | |
Get the process id under which the hub was created. | |
=item $tid = $hub->tid() | |
Get the thread id under which the hub was created. | |
=item $hud = $hub->hid() | |
Get the identifier string of the hub. | |
=item $uuid = $hub->uuid() | |
If UUID tagging is enabled (see L<Test2::API>) then the hub will have a UUID. | |
=item $ipc = $hub->ipc() | |
Get the IPC object used by the hub. | |
=item $hub->set_no_ending($bool) | |
=item $bool = $hub->no_ending | |
This can be used to disable auto-ending behavior for a hub. The auto-ending | |
behavior is triggered by an end block and is used to cull IPC events, and | |
output the final plan if the plan was 'NO PLAN'. | |
=item $bool = $hub->active | |
=item $hub->set_active($bool) | |
These are used to get/set the 'active' attribute. When true this attribute will | |
force C<< hub->finalize() >> to take action even if there is no plan, and no | |
tests have been run. This flag is useful for plugins that add follow-up | |
behaviors that need to run even if no events are seen. | |
=back | |
=head2 STATE METHODS | |
=over 4 | |
=item $hub->reset_state() | |
Reset all state to the start. This sets the test count to 0, clears the plan, | |
removes the failures, etc. | |
=item $num = $hub->count | |
Get the number of tests that have been run. | |
=item $num = $hub->failed | |
Get the number of failures (Not all failures come from a test fail, so this | |
number can be larger than the count). | |
=item $bool = $hub->ended | |
True if the testing has ended. This MAY return the stack frame of the tool that | |
ended the test, but that is not guaranteed. | |
=item $bool = $hub->is_passing | |
=item $hub->is_passing($bool) | |
Check if the overall test run is a failure. Can also be used to set the | |
pass/fail status. | |
=item $hub->plan($plan) | |
=item $plan = $hub->plan | |
Get or set the plan. The plan must be an integer larger than 0, the string | |
'NO PLAN', or the string 'SKIP'. | |
=item $bool = $hub->check_plan | |
Check if the plan and counts match, but only if the tests have ended. If tests | |
have not ended this will return undef, otherwise it will be a true/false. | |
=back | |
=head1 THIRD PARTY META-DATA | |
This object consumes L<Test2::Util::ExternalMeta> which provides a consistent | |
way for you to attach meta-data to instances of this class. This is useful for | |
tools, plugins, and other extensions. | |
=head1 SOURCE | |
The source code repository for Test2 can be found at | |
F<http://github.com/Test-More/test-more/>. | |
=head1 MAINTAINERS | |
=over 4 | |
=item Chad Granum E<lt>[email protected]<gt> | |
=back | |
=head1 AUTHORS | |
=over 4 | |
=item Chad Granum E<lt>[email protected]<gt> | |
=back | |
=head1 COPYRIGHT | |
Copyright 2020 Chad Granum E<lt>[email protected]<gt>. | |
This program is free software; you can redistribute it and/or | |
modify it under the same terms as Perl itself. | |
See F<http://dev.perl.org/licenses/> | |
=cut | |