Initial Commit
This commit is contained in:
180
database/perl/lib/Test2/API/Breakage.pm
Normal file
180
database/perl/lib/Test2/API/Breakage.pm
Normal file
@@ -0,0 +1,180 @@
|
||||
package Test2::API::Breakage;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
|
||||
use Test2::Util qw/pkg_to_file/;
|
||||
|
||||
our @EXPORT_OK = qw{
|
||||
upgrade_suggested
|
||||
upgrade_required
|
||||
known_broken
|
||||
};
|
||||
BEGIN { require Exporter; our @ISA = qw(Exporter) }
|
||||
|
||||
sub upgrade_suggested {
|
||||
return (
|
||||
'Test::Exception' => '0.42',
|
||||
'Test::FITesque' => '0.04',
|
||||
'Test::Module::Used' => '0.2.5',
|
||||
'Test::Moose::More' => '0.025',
|
||||
);
|
||||
}
|
||||
|
||||
sub upgrade_required {
|
||||
return (
|
||||
'Test::Builder::Clutch' => '0.07',
|
||||
'Test::Dist::VersionSync' => '1.1.4',
|
||||
'Test::Modern' => '0.012',
|
||||
'Test::SharedFork' => '0.34',
|
||||
'Test::Alien' => '0.04',
|
||||
'Test::UseAllModules' => '0.14',
|
||||
'Test::More::Prefix' => '0.005',
|
||||
|
||||
'Test2::Tools::EventDumper' => 0.000007,
|
||||
'Test2::Harness' => 0.000013,
|
||||
|
||||
'Test::DBIx::Class::Schema' => '1.0.9',
|
||||
'Test::Clustericious::Cluster' => '0.30',
|
||||
);
|
||||
}
|
||||
|
||||
sub known_broken {
|
||||
return (
|
||||
'Net::BitTorrent' => '0.052',
|
||||
'Test::Able' => '0.11',
|
||||
'Test::Aggregate' => '0.373',
|
||||
'Test::Flatten' => '0.11',
|
||||
'Test::Group' => '0.20',
|
||||
'Test::ParallelSubtest' => '0.05',
|
||||
'Test::Pretty' => '0.32',
|
||||
'Test::Wrapper' => '0.3.0',
|
||||
|
||||
'Log::Dispatch::Config::TestLog' => '0.02',
|
||||
);
|
||||
}
|
||||
|
||||
# Not reportable:
|
||||
# Device::Chip => 0.07 - Tests will not pass, but not broken if already installed, also no fixed version we can upgrade to.
|
||||
|
||||
sub report {
|
||||
my $class = shift;
|
||||
my ($require) = @_;
|
||||
|
||||
my %suggest = __PACKAGE__->upgrade_suggested();
|
||||
my %required = __PACKAGE__->upgrade_required();
|
||||
my %broken = __PACKAGE__->known_broken();
|
||||
|
||||
my @warn;
|
||||
for my $mod (keys %suggest) {
|
||||
my $file = pkg_to_file($mod);
|
||||
next unless $INC{$file} || ($require && eval { require $file; 1 });
|
||||
my $want = $suggest{$mod};
|
||||
next if eval { $mod->VERSION($want); 1 };
|
||||
my $error = $@;
|
||||
chomp $error;
|
||||
push @warn => " * Module '$mod' is outdated, we recommed updating above $want. error was: '$error'; INC is $INC{$file}";
|
||||
}
|
||||
|
||||
for my $mod (keys %required) {
|
||||
my $file = pkg_to_file($mod);
|
||||
next unless $INC{$file} || ($require && eval { require $file; 1 });
|
||||
my $want = $required{$mod};
|
||||
next if eval { $mod->VERSION($want); 1 };
|
||||
push @warn => " * Module '$mod' is outdated and known to be broken, please update to $want or higher.";
|
||||
}
|
||||
|
||||
for my $mod (keys %broken) {
|
||||
my $file = pkg_to_file($mod);
|
||||
next unless $INC{$file} || ($require && eval { require $file; 1 });
|
||||
my $tested = $broken{$mod};
|
||||
push @warn => " * Module '$mod' is known to be broken in version $tested and below, newer versions have not been tested. You have: " . $mod->VERSION;
|
||||
}
|
||||
|
||||
return @warn;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::API::Breakage - What breaks at what version
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module provides lists of modules that are broken, or have been broken in
|
||||
the past, when upgrading L<Test::Builder> to use L<Test2>.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
These can be imported, or called as methods on the class.
|
||||
|
||||
=over 4
|
||||
|
||||
=item %mod_ver = upgrade_suggested()
|
||||
|
||||
=item %mod_ver = Test2::API::Breakage->upgrade_suggested()
|
||||
|
||||
This returns key/value pairs. The key is the module name, the value is the
|
||||
version number. If the installed version of the module is at or below the
|
||||
specified one then an upgrade would be a good idea, but not strictly necessary.
|
||||
|
||||
=item %mod_ver = upgrade_required()
|
||||
|
||||
=item %mod_ver = Test2::API::Breakage->upgrade_required()
|
||||
|
||||
This returns key/value pairs. The key is the module name, the value is the
|
||||
version number. If the installed version of the module is at or below the
|
||||
specified one then an upgrade is required for the module to work properly.
|
||||
|
||||
=item %mod_ver = known_broken()
|
||||
|
||||
=item %mod_ver = Test2::API::Breakage->known_broken()
|
||||
|
||||
This returns key/value pairs. The key is the module name, the value is the
|
||||
version number. If the installed version of the module is at or below the
|
||||
specified one then the module will not work. A newer version may work, but is
|
||||
not tested or verified.
|
||||
|
||||
=back
|
||||
|
||||
=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>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|
||||
1019
database/perl/lib/Test2/API/Context.pm
Normal file
1019
database/perl/lib/Test2/API/Context.pm
Normal file
File diff suppressed because it is too large
Load Diff
822
database/perl/lib/Test2/API/Instance.pm
Normal file
822
database/perl/lib/Test2/API/Instance.pm
Normal file
@@ -0,0 +1,822 @@
|
||||
package Test2::API::Instance;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
our @CARP_NOT = qw/Test2::API Test2::API::Instance Test2::IPC::Driver Test2::Formatter/;
|
||||
use Carp qw/confess carp/;
|
||||
use Scalar::Util qw/reftype/;
|
||||
|
||||
use Test2::Util qw/get_tid USE_THREADS CAN_FORK pkg_to_file try CAN_SIGSYS/;
|
||||
|
||||
use Test2::EventFacet::Trace();
|
||||
use Test2::API::Stack();
|
||||
|
||||
use Test2::Util::HashBase qw{
|
||||
_pid _tid
|
||||
no_wait
|
||||
finalized loaded
|
||||
ipc stack formatter
|
||||
contexts
|
||||
|
||||
add_uuid_via
|
||||
|
||||
-preload
|
||||
|
||||
ipc_disabled
|
||||
ipc_polling
|
||||
ipc_drivers
|
||||
ipc_timeout
|
||||
formatters
|
||||
|
||||
exit_callbacks
|
||||
post_load_callbacks
|
||||
context_acquire_callbacks
|
||||
context_init_callbacks
|
||||
context_release_callbacks
|
||||
pre_subtest_callbacks
|
||||
};
|
||||
|
||||
sub DEFAULT_IPC_TIMEOUT() { 30 }
|
||||
|
||||
sub pid { $_[0]->{+_PID} }
|
||||
sub tid { $_[0]->{+_TID} }
|
||||
|
||||
# Wrap around the getters that should call _finalize.
|
||||
BEGIN {
|
||||
for my $finalizer (IPC, FORMATTER) {
|
||||
my $orig = __PACKAGE__->can($finalizer);
|
||||
my $new = sub {
|
||||
my $self = shift;
|
||||
$self->_finalize unless $self->{+FINALIZED};
|
||||
$self->$orig;
|
||||
};
|
||||
|
||||
no strict 'refs';
|
||||
no warnings 'redefine';
|
||||
*{$finalizer} = $new;
|
||||
}
|
||||
}
|
||||
|
||||
sub has_ipc { !!$_[0]->{+IPC} }
|
||||
|
||||
sub import {
|
||||
my $class = shift;
|
||||
return unless @_;
|
||||
my ($ref) = @_;
|
||||
$$ref = $class->new;
|
||||
}
|
||||
|
||||
sub init { $_[0]->reset }
|
||||
|
||||
sub start_preload {
|
||||
my $self = shift;
|
||||
|
||||
confess "preload cannot be started, Test2::API has already been initialized"
|
||||
if $self->{+FINALIZED} || $self->{+LOADED};
|
||||
|
||||
return $self->{+PRELOAD} = 1;
|
||||
}
|
||||
|
||||
sub stop_preload {
|
||||
my $self = shift;
|
||||
|
||||
return 0 unless $self->{+PRELOAD};
|
||||
$self->{+PRELOAD} = 0;
|
||||
|
||||
$self->post_preload_reset();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub post_preload_reset {
|
||||
my $self = shift;
|
||||
|
||||
delete $self->{+_PID};
|
||||
delete $self->{+_TID};
|
||||
|
||||
$self->{+ADD_UUID_VIA} = undef unless exists $self->{+ADD_UUID_VIA};
|
||||
|
||||
$self->{+CONTEXTS} = {};
|
||||
|
||||
$self->{+FORMATTERS} = [];
|
||||
|
||||
$self->{+FINALIZED} = undef;
|
||||
$self->{+IPC} = undef;
|
||||
$self->{+IPC_DISABLED} = $ENV{T2_NO_IPC} ? 1 : 0;
|
||||
|
||||
$self->{+IPC_TIMEOUT} = DEFAULT_IPC_TIMEOUT() unless defined $self->{+IPC_TIMEOUT};
|
||||
|
||||
$self->{+LOADED} = 0;
|
||||
|
||||
$self->{+STACK} ||= Test2::API::Stack->new;
|
||||
}
|
||||
|
||||
sub reset {
|
||||
my $self = shift;
|
||||
|
||||
delete $self->{+_PID};
|
||||
delete $self->{+_TID};
|
||||
|
||||
$self->{+ADD_UUID_VIA} = undef;
|
||||
|
||||
$self->{+CONTEXTS} = {};
|
||||
|
||||
$self->{+IPC_DRIVERS} = [];
|
||||
$self->{+IPC_POLLING} = undef;
|
||||
|
||||
$self->{+FORMATTERS} = [];
|
||||
$self->{+FORMATTER} = undef;
|
||||
|
||||
$self->{+FINALIZED} = undef;
|
||||
$self->{+IPC} = undef;
|
||||
$self->{+IPC_DISABLED} = $ENV{T2_NO_IPC} ? 1 : 0;
|
||||
|
||||
$self->{+IPC_TIMEOUT} = DEFAULT_IPC_TIMEOUT() unless defined $self->{+IPC_TIMEOUT};
|
||||
|
||||
$self->{+NO_WAIT} = 0;
|
||||
$self->{+LOADED} = 0;
|
||||
|
||||
$self->{+EXIT_CALLBACKS} = [];
|
||||
$self->{+POST_LOAD_CALLBACKS} = [];
|
||||
$self->{+CONTEXT_ACQUIRE_CALLBACKS} = [];
|
||||
$self->{+CONTEXT_INIT_CALLBACKS} = [];
|
||||
$self->{+CONTEXT_RELEASE_CALLBACKS} = [];
|
||||
$self->{+PRE_SUBTEST_CALLBACKS} = [];
|
||||
|
||||
$self->{+STACK} = Test2::API::Stack->new;
|
||||
}
|
||||
|
||||
sub _finalize {
|
||||
my $self = shift;
|
||||
my ($caller) = @_;
|
||||
$caller ||= [caller(1)];
|
||||
|
||||
confess "Attempt to initialize Test2::API during preload"
|
||||
if $self->{+PRELOAD};
|
||||
|
||||
$self->{+FINALIZED} = $caller;
|
||||
|
||||
$self->{+_PID} = $$ unless defined $self->{+_PID};
|
||||
$self->{+_TID} = get_tid() unless defined $self->{+_TID};
|
||||
|
||||
unless ($self->{+FORMATTER}) {
|
||||
my ($formatter, $source);
|
||||
if ($ENV{T2_FORMATTER}) {
|
||||
$source = "set by the 'T2_FORMATTER' environment variable";
|
||||
|
||||
if ($ENV{T2_FORMATTER} =~ m/^(\+)?(.*)$/) {
|
||||
$formatter = $1 ? $2 : "Test2::Formatter::$2"
|
||||
}
|
||||
else {
|
||||
$formatter = '';
|
||||
}
|
||||
}
|
||||
elsif (@{$self->{+FORMATTERS}}) {
|
||||
($formatter) = @{$self->{+FORMATTERS}};
|
||||
$source = "Most recently added";
|
||||
}
|
||||
else {
|
||||
$formatter = 'Test2::Formatter::TAP';
|
||||
$source = 'default formatter';
|
||||
}
|
||||
|
||||
unless (ref($formatter) || $formatter->can('write')) {
|
||||
my $file = pkg_to_file($formatter);
|
||||
my ($ok, $err) = try { require $file };
|
||||
unless ($ok) {
|
||||
my $line = "* COULD NOT LOAD FORMATTER '$formatter' ($source) *";
|
||||
my $border = '*' x length($line);
|
||||
die "\n\n $border\n $line\n $border\n\n$err";
|
||||
}
|
||||
}
|
||||
|
||||
$self->{+FORMATTER} = $formatter;
|
||||
}
|
||||
|
||||
# Turn on IPC if threads are on, drivers are registered, or the Test2::IPC
|
||||
# module is loaded.
|
||||
return if $self->{+IPC_DISABLED};
|
||||
return unless USE_THREADS || $INC{'Test2/IPC.pm'} || @{$self->{+IPC_DRIVERS}};
|
||||
|
||||
# Turn on polling by default, people expect it.
|
||||
$self->enable_ipc_polling;
|
||||
|
||||
unless (@{$self->{+IPC_DRIVERS}}) {
|
||||
my ($ok, $error) = try { require Test2::IPC::Driver::Files };
|
||||
die $error unless $ok;
|
||||
push @{$self->{+IPC_DRIVERS}} => 'Test2::IPC::Driver::Files';
|
||||
}
|
||||
|
||||
for my $driver (@{$self->{+IPC_DRIVERS}}) {
|
||||
next unless $driver->can('is_viable') && $driver->is_viable;
|
||||
$self->{+IPC} = $driver->new or next;
|
||||
return;
|
||||
}
|
||||
|
||||
die "IPC has been requested, but no viable drivers were found. Aborting...\n";
|
||||
}
|
||||
|
||||
sub formatter_set { $_[0]->{+FORMATTER} ? 1 : 0 }
|
||||
|
||||
sub add_formatter {
|
||||
my $self = shift;
|
||||
my ($formatter) = @_;
|
||||
unshift @{$self->{+FORMATTERS}} => $formatter;
|
||||
|
||||
return unless $self->{+FINALIZED};
|
||||
|
||||
# Why is the @CARP_NOT entry not enough?
|
||||
local %Carp::Internal = %Carp::Internal;
|
||||
$Carp::Internal{'Test2::Formatter'} = 1;
|
||||
|
||||
carp "Formatter $formatter loaded too late to be used as the global formatter";
|
||||
}
|
||||
|
||||
sub add_context_acquire_callback {
|
||||
my $self = shift;
|
||||
my ($code) = @_;
|
||||
|
||||
my $rtype = reftype($code) || "";
|
||||
|
||||
confess "Context-acquire callbacks must be coderefs"
|
||||
unless $code && $rtype eq 'CODE';
|
||||
|
||||
push @{$self->{+CONTEXT_ACQUIRE_CALLBACKS}} => $code;
|
||||
}
|
||||
|
||||
sub add_context_init_callback {
|
||||
my $self = shift;
|
||||
my ($code) = @_;
|
||||
|
||||
my $rtype = reftype($code) || "";
|
||||
|
||||
confess "Context-init callbacks must be coderefs"
|
||||
unless $code && $rtype eq 'CODE';
|
||||
|
||||
push @{$self->{+CONTEXT_INIT_CALLBACKS}} => $code;
|
||||
}
|
||||
|
||||
sub add_context_release_callback {
|
||||
my $self = shift;
|
||||
my ($code) = @_;
|
||||
|
||||
my $rtype = reftype($code) || "";
|
||||
|
||||
confess "Context-release callbacks must be coderefs"
|
||||
unless $code && $rtype eq 'CODE';
|
||||
|
||||
push @{$self->{+CONTEXT_RELEASE_CALLBACKS}} => $code;
|
||||
}
|
||||
|
||||
sub add_post_load_callback {
|
||||
my $self = shift;
|
||||
my ($code) = @_;
|
||||
|
||||
my $rtype = reftype($code) || "";
|
||||
|
||||
confess "Post-load callbacks must be coderefs"
|
||||
unless $code && $rtype eq 'CODE';
|
||||
|
||||
push @{$self->{+POST_LOAD_CALLBACKS}} => $code;
|
||||
$code->() if $self->{+LOADED};
|
||||
}
|
||||
|
||||
sub add_pre_subtest_callback {
|
||||
my $self = shift;
|
||||
my ($code) = @_;
|
||||
|
||||
my $rtype = reftype($code) || "";
|
||||
|
||||
confess "Pre-subtest callbacks must be coderefs"
|
||||
unless $code && $rtype eq 'CODE';
|
||||
|
||||
push @{$self->{+PRE_SUBTEST_CALLBACKS}} => $code;
|
||||
}
|
||||
|
||||
sub load {
|
||||
my $self = shift;
|
||||
unless ($self->{+LOADED}) {
|
||||
confess "Attempt to initialize Test2::API during preload"
|
||||
if $self->{+PRELOAD};
|
||||
|
||||
$self->{+_PID} = $$ unless defined $self->{+_PID};
|
||||
$self->{+_TID} = get_tid() unless defined $self->{+_TID};
|
||||
|
||||
# This is for https://github.com/Test-More/test-more/issues/16
|
||||
# and https://rt.perl.org/Public/Bug/Display.html?id=127774
|
||||
# END blocks run in reverse order. This insures the END block is loaded
|
||||
# as late as possible. It will not solve all cases, but it helps.
|
||||
eval "END { Test2::API::test2_set_is_end() }; 1" or die $@;
|
||||
|
||||
$self->{+LOADED} = 1;
|
||||
$_->() for @{$self->{+POST_LOAD_CALLBACKS}};
|
||||
}
|
||||
return $self->{+LOADED};
|
||||
}
|
||||
|
||||
sub add_exit_callback {
|
||||
my $self = shift;
|
||||
my ($code) = @_;
|
||||
my $rtype = reftype($code) || "";
|
||||
|
||||
confess "End callbacks must be coderefs"
|
||||
unless $code && $rtype eq 'CODE';
|
||||
|
||||
push @{$self->{+EXIT_CALLBACKS}} => $code;
|
||||
}
|
||||
|
||||
sub ipc_disable {
|
||||
my $self = shift;
|
||||
|
||||
confess "Attempt to disable IPC after it has been initialized"
|
||||
if $self->{+IPC};
|
||||
|
||||
$self->{+IPC_DISABLED} = 1;
|
||||
}
|
||||
|
||||
sub add_ipc_driver {
|
||||
my $self = shift;
|
||||
my ($driver) = @_;
|
||||
unshift @{$self->{+IPC_DRIVERS}} => $driver;
|
||||
|
||||
return unless $self->{+FINALIZED};
|
||||
|
||||
# Why is the @CARP_NOT entry not enough?
|
||||
local %Carp::Internal = %Carp::Internal;
|
||||
$Carp::Internal{'Test2::IPC::Driver'} = 1;
|
||||
|
||||
carp "IPC driver $driver loaded too late to be used as the global ipc driver";
|
||||
}
|
||||
|
||||
sub enable_ipc_polling {
|
||||
my $self = shift;
|
||||
|
||||
$self->{+_PID} = $$ unless defined $self->{+_PID};
|
||||
$self->{+_TID} = get_tid() unless defined $self->{+_TID};
|
||||
|
||||
$self->add_context_init_callback(
|
||||
# This is called every time a context is created, it needs to be fast.
|
||||
# $_[0] is a context object
|
||||
sub {
|
||||
return unless $self->{+IPC_POLLING};
|
||||
return unless $self->{+IPC};
|
||||
return unless $self->{+IPC}->pending();
|
||||
return $_[0]->{hub}->cull;
|
||||
}
|
||||
) unless defined $self->ipc_polling;
|
||||
|
||||
$self->set_ipc_polling(1);
|
||||
}
|
||||
|
||||
sub get_ipc_pending {
|
||||
my $self = shift;
|
||||
return -1 unless $self->{+IPC};
|
||||
$self->{+IPC}->pending();
|
||||
}
|
||||
|
||||
sub _check_pid {
|
||||
my $self = shift;
|
||||
my ($pid) = @_;
|
||||
return kill(0, $pid);
|
||||
}
|
||||
|
||||
sub set_ipc_pending {
|
||||
my $self = shift;
|
||||
return unless $self->{+IPC};
|
||||
my ($val) = @_;
|
||||
|
||||
confess "value is required for set_ipc_pending"
|
||||
unless $val;
|
||||
|
||||
$self->{+IPC}->set_pending($val);
|
||||
}
|
||||
|
||||
sub disable_ipc_polling {
|
||||
my $self = shift;
|
||||
return unless defined $self->{+IPC_POLLING};
|
||||
$self->{+IPC_POLLING} = 0;
|
||||
}
|
||||
|
||||
sub _ipc_wait {
|
||||
my ($timeout) = @_;
|
||||
my $fail = 0;
|
||||
|
||||
$timeout = DEFAULT_IPC_TIMEOUT() unless defined $timeout;
|
||||
|
||||
my $ok = eval {
|
||||
if (CAN_FORK) {
|
||||
local $SIG{ALRM} = sub { die "Timeout waiting on child processes" };
|
||||
alarm $timeout;
|
||||
|
||||
while (1) {
|
||||
my $pid = CORE::wait();
|
||||
my $err = $?;
|
||||
last if $pid == -1;
|
||||
next unless $err;
|
||||
$fail++;
|
||||
|
||||
my $sig = $err & 127;
|
||||
my $exit = $err >> 8;
|
||||
warn "Process $pid did not exit cleanly (wstat: $err, exit: $exit, sig: $sig)\n";
|
||||
}
|
||||
|
||||
alarm 0;
|
||||
}
|
||||
|
||||
if (USE_THREADS) {
|
||||
my $start = time;
|
||||
|
||||
while (1) {
|
||||
last unless threads->list();
|
||||
die "Timeout waiting on child thread" if time - $start >= $timeout;
|
||||
sleep 1;
|
||||
for my $t (threads->list) {
|
||||
# threads older than 1.34 do not have this :-(
|
||||
next if $t->can('is_joinable') && !$t->is_joinable;
|
||||
$t->join;
|
||||
# In older threads we cannot check if a thread had an error unless
|
||||
# we control it and its return.
|
||||
my $err = $t->can('error') ? $t->error : undef;
|
||||
next unless $err;
|
||||
my $tid = $t->tid();
|
||||
$fail++;
|
||||
chomp($err);
|
||||
warn "Thread $tid did not end cleanly: $err\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
};
|
||||
my $error = $@;
|
||||
|
||||
return 0 if $ok && !$fail;
|
||||
warn $error unless $ok;
|
||||
return 255;
|
||||
}
|
||||
|
||||
sub set_exit {
|
||||
my $self = shift;
|
||||
|
||||
return if $self->{+PRELOAD};
|
||||
|
||||
my $exit = $?;
|
||||
my $new_exit = $exit;
|
||||
|
||||
if ($INC{'Test/Builder.pm'} && $Test::Builder::VERSION ne $Test2::API::VERSION) {
|
||||
print STDERR <<" EOT";
|
||||
|
||||
********************************************************************************
|
||||
* *
|
||||
* Test::Builder -- Test2::API version mismatch detected *
|
||||
* *
|
||||
********************************************************************************
|
||||
Test2::API Version: $Test2::API::VERSION
|
||||
Test::Builder Version: $Test::Builder::VERSION
|
||||
|
||||
This is not a supported configuration, you will have problems.
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
for my $ctx (values %{$self->{+CONTEXTS}}) {
|
||||
next unless $ctx;
|
||||
|
||||
next if $ctx->_aborted && ${$ctx->_aborted};
|
||||
|
||||
# Only worry about contexts in this PID
|
||||
my $trace = $ctx->trace || next;
|
||||
next unless $trace->pid && $trace->pid == $$;
|
||||
|
||||
# Do not worry about contexts that have no hub
|
||||
my $hub = $ctx->hub || next;
|
||||
|
||||
# Do not worry if the state came to a sudden end.
|
||||
next if $hub->bailed_out;
|
||||
next if defined $hub->skip_reason;
|
||||
|
||||
# now we worry
|
||||
$trace->alert("context object was never released! This means a testing tool is behaving very badly");
|
||||
|
||||
$exit = 255;
|
||||
$new_exit = 255;
|
||||
}
|
||||
|
||||
if (!defined($self->{+_PID}) or !defined($self->{+_TID}) or $self->{+_PID} != $$ or $self->{+_TID} != get_tid()) {
|
||||
$? = $exit;
|
||||
return;
|
||||
}
|
||||
|
||||
my @hubs = $self->{+STACK} ? $self->{+STACK}->all : ();
|
||||
|
||||
if (@hubs and $self->{+IPC} and !$self->{+NO_WAIT}) {
|
||||
local $?;
|
||||
my %seen;
|
||||
for my $hub (reverse @hubs) {
|
||||
my $ipc = $hub->ipc or next;
|
||||
next if $seen{$ipc}++;
|
||||
$ipc->waiting();
|
||||
}
|
||||
|
||||
my $ipc_exit = _ipc_wait($self->{+IPC_TIMEOUT});
|
||||
$new_exit ||= $ipc_exit;
|
||||
}
|
||||
|
||||
# None of this is necessary if we never got a root hub
|
||||
if(my $root = shift @hubs) {
|
||||
my $trace = Test2::EventFacet::Trace->new(
|
||||
frame => [__PACKAGE__, __FILE__, 0, __PACKAGE__ . '::END'],
|
||||
detail => __PACKAGE__ . ' END Block finalization',
|
||||
);
|
||||
my $ctx = Test2::API::Context->new(
|
||||
trace => $trace,
|
||||
hub => $root,
|
||||
);
|
||||
|
||||
if (@hubs) {
|
||||
$ctx->diag("Test ended with extra hubs on the stack!");
|
||||
$new_exit = 255;
|
||||
}
|
||||
|
||||
unless ($root->no_ending) {
|
||||
local $?;
|
||||
$root->finalize($trace) unless $root->ended;
|
||||
$_->($ctx, $exit, \$new_exit) for @{$self->{+EXIT_CALLBACKS}};
|
||||
$new_exit ||= $root->failed;
|
||||
$new_exit ||= 255 unless $root->is_passing;
|
||||
}
|
||||
}
|
||||
|
||||
$new_exit = 255 if $new_exit > 255;
|
||||
|
||||
if ($new_exit && eval { require Test2::API::Breakage; 1 }) {
|
||||
my @warn = Test2::API::Breakage->report();
|
||||
|
||||
if (@warn) {
|
||||
print STDERR "\nYou have loaded versions of test modules known to have problems with Test2.\nThis could explain some test failures.\n";
|
||||
print STDERR "$_\n" for @warn;
|
||||
print STDERR "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$? = $new_exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::API::Instance - Object used by Test2::API under the hood
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This object encapsulates the global shared state tracked by
|
||||
L<Test2>. A single global instance of this package is stored (and
|
||||
obscured) by the L<Test2::API> package.
|
||||
|
||||
There is no reason to directly use this package. This package is documented for
|
||||
completeness. This package can change, or go away completely at any time.
|
||||
Directly using, or monkeypatching this package is not supported in any way
|
||||
shape or form.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Test2::API::Instance;
|
||||
|
||||
my $obj = Test2::API::Instance->new;
|
||||
|
||||
=over 4
|
||||
|
||||
=item $pid = $obj->pid
|
||||
|
||||
PID of this instance.
|
||||
|
||||
=item $obj->tid
|
||||
|
||||
Thread ID of this instance.
|
||||
|
||||
=item $obj->reset()
|
||||
|
||||
Reset the object to defaults.
|
||||
|
||||
=item $obj->load()
|
||||
|
||||
Set the internal state to loaded, and run and stored post-load callbacks.
|
||||
|
||||
=item $bool = $obj->loaded
|
||||
|
||||
Check if the state is set to loaded.
|
||||
|
||||
=item $arrayref = $obj->post_load_callbacks
|
||||
|
||||
Get the post-load callbacks.
|
||||
|
||||
=item $obj->add_post_load_callback(sub { ... })
|
||||
|
||||
Add a post-load callback. If C<load()> has already been called then the callback will
|
||||
be immediately executed. If C<load()> has not been called then the callback will be
|
||||
stored and executed later when C<load()> is called.
|
||||
|
||||
=item $hashref = $obj->contexts()
|
||||
|
||||
Get a hashref of all active contexts keyed by hub id.
|
||||
|
||||
=item $arrayref = $obj->context_acquire_callbacks
|
||||
|
||||
Get all context acquire callbacks.
|
||||
|
||||
=item $arrayref = $obj->context_init_callbacks
|
||||
|
||||
Get all context init callbacks.
|
||||
|
||||
=item $arrayref = $obj->context_release_callbacks
|
||||
|
||||
Get all context release callbacks.
|
||||
|
||||
=item $arrayref = $obj->pre_subtest_callbacks
|
||||
|
||||
Get all pre-subtest callbacks.
|
||||
|
||||
=item $obj->add_context_init_callback(sub { ... })
|
||||
|
||||
Add a context init callback. Subs are called every time a context is created. Subs
|
||||
get the newly created context as their only argument.
|
||||
|
||||
=item $obj->add_context_release_callback(sub { ... })
|
||||
|
||||
Add a context release callback. Subs are called every time a context is released. Subs
|
||||
get the released context as their only argument. These callbacks should not
|
||||
call release on the context.
|
||||
|
||||
=item $obj->add_pre_subtest_callback(sub { ... })
|
||||
|
||||
Add a pre-subtest callback. Subs are called every time a subtest is
|
||||
going to be run. Subs get the subtest name, coderef, and any
|
||||
arguments.
|
||||
|
||||
=item $obj->set_exit()
|
||||
|
||||
This is intended to be called in an C<END { ... }> block. This will look at
|
||||
test state and set $?. This will also call any end callbacks, and wait on child
|
||||
processes/threads.
|
||||
|
||||
=item $obj->set_ipc_pending($val)
|
||||
|
||||
Tell other processes and threads there is a pending event. C<$val> should be a
|
||||
unique value no other thread/process will generate.
|
||||
|
||||
B<Note:> This will also make the current process see a pending event.
|
||||
|
||||
=item $pending = $obj->get_ipc_pending()
|
||||
|
||||
This returns -1 if it is not possible to know.
|
||||
|
||||
This returns 0 if there are no pending events.
|
||||
|
||||
This returns 1 if there are pending events.
|
||||
|
||||
=item $timeout = $obj->ipc_timeout;
|
||||
|
||||
=item $obj->set_ipc_timeout($timeout);
|
||||
|
||||
How long to wait for child processes and threads before aborting.
|
||||
|
||||
=item $drivers = $obj->ipc_drivers
|
||||
|
||||
Get the list of IPC drivers.
|
||||
|
||||
=item $obj->add_ipc_driver($DRIVER_CLASS)
|
||||
|
||||
Add an IPC driver to the list. The most recently added IPC driver will become
|
||||
the global one during initialization. If a driver is added after initialization
|
||||
has occurred a warning will be generated:
|
||||
|
||||
"IPC driver $driver loaded too late to be used as the global ipc driver"
|
||||
|
||||
=item $bool = $obj->ipc_polling
|
||||
|
||||
Check if polling is enabled.
|
||||
|
||||
=item $obj->enable_ipc_polling
|
||||
|
||||
Turn on polling. This will cull events from other processes and threads every
|
||||
time a context is created.
|
||||
|
||||
=item $obj->disable_ipc_polling
|
||||
|
||||
Turn off IPC polling.
|
||||
|
||||
=item $bool = $obj->no_wait
|
||||
|
||||
=item $bool = $obj->set_no_wait($bool)
|
||||
|
||||
Get/Set no_wait. This option is used to turn off process/thread waiting at exit.
|
||||
|
||||
=item $arrayref = $obj->exit_callbacks
|
||||
|
||||
Get the exit callbacks.
|
||||
|
||||
=item $obj->add_exit_callback(sub { ... })
|
||||
|
||||
Add an exit callback. This callback will be called by C<set_exit()>.
|
||||
|
||||
=item $bool = $obj->finalized
|
||||
|
||||
Check if the object is finalized. Finalization happens when either C<ipc()>,
|
||||
C<stack()>, or C<format()> are called on the object. Once finalization happens
|
||||
these fields are considered unchangeable (not enforced here, enforced by
|
||||
L<Test2>).
|
||||
|
||||
=item $ipc = $obj->ipc
|
||||
|
||||
Get the one true IPC instance.
|
||||
|
||||
=item $obj->ipc_disable
|
||||
|
||||
Turn IPC off
|
||||
|
||||
=item $bool = $obj->ipc_disabled
|
||||
|
||||
Check if IPC is disabled
|
||||
|
||||
=item $stack = $obj->stack
|
||||
|
||||
Get the one true hub stack.
|
||||
|
||||
=item $formatter = $obj->formatter
|
||||
|
||||
Get the global formatter. By default this is the C<'Test2::Formatter::TAP'>
|
||||
package. This could be any package that implements the C<write()> method. This
|
||||
can also be an instantiated object.
|
||||
|
||||
=item $bool = $obj->formatter_set()
|
||||
|
||||
Check if a formatter has been set.
|
||||
|
||||
=item $obj->add_formatter($class)
|
||||
|
||||
=item $obj->add_formatter($obj)
|
||||
|
||||
Add a formatter. The most recently added formatter will become the global one
|
||||
during initialization. If a formatter is added after initialization has occurred
|
||||
a warning will be generated:
|
||||
|
||||
"Formatter $formatter loaded too late to be used as the global formatter"
|
||||
|
||||
=item $obj->set_add_uuid_via(sub { ... })
|
||||
|
||||
=item $sub = $obj->add_uuid_via()
|
||||
|
||||
This allows you to provide a UUID generator. If provided UUIDs will be attached
|
||||
to all events, hubs, and contexts. This is useful for storing, tracking, and
|
||||
linking these objects.
|
||||
|
||||
The sub you provide should always return a unique identifier. Most things will
|
||||
expect a proper UUID string, however nothing in Test2::API enforces this.
|
||||
|
||||
The sub will receive exactly 1 argument, the type of thing being tagged
|
||||
'context', 'hub', or 'event'. In the future additional things may be tagged, in
|
||||
which case new strings will be passed in. These are purely informative, you can
|
||||
(and usually should) ignore them.
|
||||
|
||||
=back
|
||||
|
||||
=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>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|
||||
634
database/perl/lib/Test2/API/InterceptResult.pm
Normal file
634
database/perl/lib/Test2/API/InterceptResult.pm
Normal file
@@ -0,0 +1,634 @@
|
||||
package Test2::API::InterceptResult;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
use Scalar::Util qw/blessed/;
|
||||
use Test2::Util qw/pkg_to_file/;
|
||||
use Storable qw/dclone/;
|
||||
use Carp qw/croak/;
|
||||
|
||||
use Test2::API::InterceptResult::Squasher;
|
||||
use Test2::API::InterceptResult::Event;
|
||||
use Test2::API::InterceptResult::Hub;
|
||||
|
||||
sub new {
|
||||
croak "Called a method that creates a new instance in void context" unless defined wantarray;
|
||||
my $class = shift;
|
||||
bless([@_], $class);
|
||||
}
|
||||
|
||||
sub new_from_ref {
|
||||
croak "Called a method that creates a new instance in void context" unless defined wantarray;
|
||||
bless($_[1], $_[0]);
|
||||
}
|
||||
|
||||
sub clone { blessed($_[0])->new(@{dclone($_[0])}) }
|
||||
|
||||
sub event_list { @{$_[0]} }
|
||||
|
||||
sub _upgrade {
|
||||
my $self = shift;
|
||||
my ($event, %params) = @_;
|
||||
|
||||
my $blessed = blessed($event);
|
||||
|
||||
my $upgrade_class = $params{upgrade_class} ||= 'Test2::API::InterceptResult::Event';
|
||||
|
||||
return $event if $blessed && $event->isa($upgrade_class) && !$params{_upgrade_clone};
|
||||
|
||||
my $fd = dclone($blessed ? $event->facet_data : $event);
|
||||
|
||||
my $class = $params{result_class} ||= blessed($self);
|
||||
|
||||
if (my $parent = $fd->{parent}) {
|
||||
$parent->{children} = $class->new_from_ref($parent->{children} || [])->upgrade(%params);
|
||||
}
|
||||
|
||||
my $uc_file = pkg_to_file($upgrade_class);
|
||||
require($uc_file) unless $INC{$uc_file};
|
||||
return $upgrade_class->new(facet_data => $fd, result_class => $class);
|
||||
}
|
||||
|
||||
sub hub {
|
||||
my $self = shift;
|
||||
|
||||
my $hub = Test2::API::InterceptResult::Hub->new();
|
||||
$hub->process($_) for @$self;
|
||||
$hub->set_ended(1);
|
||||
|
||||
return $hub;
|
||||
}
|
||||
|
||||
sub state {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my $hub = $self->hub;
|
||||
|
||||
my $out = {
|
||||
map {($_ => scalar $hub->$_)} qw/count failed is_passing plan bailed_out skip_reason/
|
||||
};
|
||||
|
||||
$out->{bailed_out} = $self->_upgrade($out->{bailed_out}, %params)->bailout_reason || 1
|
||||
if $out->{bailed_out};
|
||||
|
||||
$out->{follows_plan} = $hub->check_plan;
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
sub upgrade {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my @out = map { $self->_upgrade($_, %params, _upgrade_clone => 1) } @$self;
|
||||
|
||||
return blessed($self)->new_from_ref(\@out)
|
||||
unless $params{in_place};
|
||||
|
||||
@$self = @out;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub squash_info {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my @out;
|
||||
|
||||
{
|
||||
my $squasher = Test2::API::InterceptResult::Squasher->new(events => \@out);
|
||||
# Clone to make sure we do not indirectly modify an existing one if it
|
||||
# is already upgraded
|
||||
$squasher->process($self->_upgrade($_, %params)->clone) for @$self;
|
||||
$squasher->flush_down();
|
||||
}
|
||||
|
||||
return blessed($self)->new_from_ref(\@out)
|
||||
unless $params{in_place};
|
||||
|
||||
@$self = @out;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub asserts { shift->grep(has_assert => @_) }
|
||||
sub subtests { shift->grep(has_subtest => @_) }
|
||||
sub diags { shift->grep(has_diags => @_) }
|
||||
sub notes { shift->grep(has_notes => @_) }
|
||||
sub errors { shift->grep(has_errors => @_) }
|
||||
sub plans { shift->grep(has_plan => @_) }
|
||||
sub causes_fail { shift->grep(causes_fail => @_) }
|
||||
sub causes_failure { shift->grep(causes_failure => @_) }
|
||||
|
||||
sub flatten { shift->map(flatten => @_) }
|
||||
sub briefs { shift->map(brief => @_) }
|
||||
sub summaries { shift->map(summary => @_) }
|
||||
sub subtest_results { shift->map(subtest_result => @_) }
|
||||
sub diag_messages { shift->map(diag_messages => @_) }
|
||||
sub note_messages { shift->map(note_messages => @_) }
|
||||
sub error_messages { shift->map(error_messages => @_) }
|
||||
|
||||
no warnings 'once';
|
||||
|
||||
*map = sub {
|
||||
my $self = shift;
|
||||
my ($call, %params) = @_;
|
||||
|
||||
my $args = $params{args} ||= [];
|
||||
|
||||
return [map { local $_ = $self->_upgrade($_, %params); $_->$call(@$args) } @$self];
|
||||
};
|
||||
|
||||
*grep = sub {
|
||||
my $self = shift;
|
||||
my ($call, %params) = @_;
|
||||
|
||||
my $args = $params{args} ||= [];
|
||||
|
||||
my @out = grep { local $_ = $self->_upgrade($_, %params); $_->$call(@$args) } @$self;
|
||||
|
||||
return blessed($self)->new_from_ref(\@out)
|
||||
unless $params{in_place};
|
||||
|
||||
@$self = @out;
|
||||
return $self;
|
||||
};
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::API::InterceptResult - Representation of a list of events.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This class represents a list of events, normally obtained using C<intercept()>
|
||||
from L<Test2::API>.
|
||||
|
||||
This class is intended for people who with to verify the results of test tools
|
||||
they write.
|
||||
|
||||
This class provides methods to normalize, summarize, or map the list of events.
|
||||
The output of these operations makes verifying your testing tools and the
|
||||
events they generate significantly easier. In most cases this spares you from
|
||||
needing a deep understanding of the event/facet model.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Usually you get an instance of this class when you use C<intercept()> from
|
||||
L<Test2::API>.
|
||||
|
||||
use Test2::V0;
|
||||
use Test2::API qw/intercept/;
|
||||
|
||||
my $events = intercept {
|
||||
ok(1, "pass");
|
||||
ok(0, "fail");
|
||||
todo "broken" => sub { ok(0, "fixme") };
|
||||
plan 3;
|
||||
};
|
||||
|
||||
# This is typically the most useful construct
|
||||
# squash_info() merges assertions and diagnostics that are associated
|
||||
# (and returns a new instance with the modifications)
|
||||
# flatten() condenses the facet data into the key details for each event
|
||||
# (and returns those structures in an arrayref)
|
||||
is(
|
||||
$events->squash_info->flatten(),
|
||||
[
|
||||
{
|
||||
causes_failure => 0,
|
||||
|
||||
name => 'pass',
|
||||
pass => 1,
|
||||
|
||||
trace_file => 'xxx.t',
|
||||
trace_line => 5,
|
||||
},
|
||||
{
|
||||
causes_failure => 1,
|
||||
|
||||
name => 'fail',
|
||||
pass => 0,
|
||||
|
||||
trace_file => 'xxx.t',
|
||||
trace_line => 6,
|
||||
|
||||
# There can be more than one diagnostics message so this is
|
||||
# always an array when present.
|
||||
diag => ["Failed test 'fail'\nat xxx.t line 6."],
|
||||
},
|
||||
{
|
||||
causes_failure => 0,
|
||||
|
||||
name => 'fixme',
|
||||
pass => 0,
|
||||
|
||||
trace_file => 'xxx.t',
|
||||
trace_line => 7,
|
||||
|
||||
# There can be more than one diagnostics message or todo
|
||||
# reason, so these are always an array when present.
|
||||
todo => ['broken'],
|
||||
|
||||
# Diag message was turned into a note since the assertion was
|
||||
# TODO
|
||||
note => ["Failed test 'fixme'\nat xxx.t line 7."],
|
||||
},
|
||||
{
|
||||
causes_failure => 0,
|
||||
|
||||
plan => 3,
|
||||
|
||||
trace_file => 'xxx.t',
|
||||
trace_line => 8,
|
||||
},
|
||||
],
|
||||
"Flattened events look like we expect"
|
||||
);
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for a full description of what
|
||||
C<flatten()> provides for each event.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
Please note that no methods modify the original instance unless asked to do so.
|
||||
|
||||
=head2 CONSTRUCTION
|
||||
|
||||
=over 4
|
||||
|
||||
=item $events = Test2::API::InterceptResult->new(@EVENTS)
|
||||
|
||||
=item $events = Test2::API::InterceptResult->new_from_ref(\@EVENTS)
|
||||
|
||||
These create a new instance of Test2::API::InterceptResult from the given
|
||||
events.
|
||||
|
||||
In the first form a new blessed arrayref is returned. In the 'new_from_ref'
|
||||
form the reference you pass in is directly blessed.
|
||||
|
||||
Both of these will throw an exception if called in void context. This is mainly
|
||||
important for the 'filtering' methods listed below which normally return a new
|
||||
instance, they throw an exception in such cases as it probably means someone
|
||||
meant to filter the original in place.
|
||||
|
||||
=item $clone = $events->clone()
|
||||
|
||||
Make a clone of the original events. Note that this is a deep copy, the entire
|
||||
structure is duplicated. This uses C<dclone> from L<Storable> to achieve the
|
||||
deep clone.
|
||||
|
||||
=back
|
||||
|
||||
=head2 NORMALIZATION
|
||||
|
||||
=over 4
|
||||
|
||||
=item @events = $events->event_list
|
||||
|
||||
This returns all the events in list-form.
|
||||
|
||||
=item $hub = $events->hub
|
||||
|
||||
This returns a new L<Test2::Hub> instance that has processed all the events
|
||||
contained in the instance. This gives you a simple way to inspect the state
|
||||
changes your events cause.
|
||||
|
||||
=item $state = $events->state
|
||||
|
||||
This returns a summary of the state of a hub after processing all the events.
|
||||
|
||||
{
|
||||
count => 2, # Number of assertions made
|
||||
failed => 1, # Number of test failures seen
|
||||
is_passing => 0, # Boolean, true if the test would be passing
|
||||
# after the events are processed.
|
||||
|
||||
plan => 2, # Plan, either a number, undef, 'SKIP', or 'NO PLAN'
|
||||
follows_plan => 1, # True if there is a plan and it was followed.
|
||||
# False if the plan and assertions did not
|
||||
# match, undef if no plan was present in the
|
||||
# event list.
|
||||
|
||||
bailed_out => undef, # undef unless there was a bail-out in the
|
||||
# events in which case this will be a string
|
||||
# explaining why there was a bailout, if no
|
||||
# reason was given this will simply be set to
|
||||
# true (1).
|
||||
|
||||
skip_reason => undef, # If there was a skip_all this will give the
|
||||
# reason.
|
||||
}
|
||||
|
||||
|
||||
=item $new = $events->upgrade
|
||||
|
||||
=item $events->upgrade(in_place => $BOOL)
|
||||
|
||||
B<Note:> This normally returns a new instance, leaving the original unchanged.
|
||||
If you call it in void context it will throw an exception. If you want to
|
||||
modify the original you must pass in the C<< in_place => 1 >> option. You may
|
||||
call this in void context when you ask to modify it in place. The in-place form
|
||||
returns the instance that was modified so you can chain methods.
|
||||
|
||||
This will create a clone of the list where all events have been converted into
|
||||
L<Test2::API::InterceptResult::Event> instances. This is extremely helpful as
|
||||
L<Test2::API::InterceptResult::Event> provide a much better interface for
|
||||
working with events. This allows you to avoid thinking about legacy event
|
||||
types.
|
||||
|
||||
This also means your tests against the list are not fragile if the tool
|
||||
you are testing randomly changes what type of events it generates (IE Changing
|
||||
from L<Test2::Event::Ok> to L<Test2::Event::Pass>, both make assertions and
|
||||
both will normalize to identical (or close enough)
|
||||
L<Test2::API::InterceptResult::Event> instances.
|
||||
|
||||
Really you almost always want this, the only reason it is not done
|
||||
automatically is to make sure the C<intercept()> tool is backwards compatible.
|
||||
|
||||
=item $new = $events->squash_info
|
||||
|
||||
=item $events->squash_info(in_place => $BOOL)
|
||||
|
||||
B<Note:> This normally returns a new instance, leaving the original unchanged.
|
||||
If you call it in void context it will throw an exception. If you want to
|
||||
modify the original you must pass in the C<< in_place => 1 >> option. You may
|
||||
call this in void context when you ask to modify it in place. The in-place form
|
||||
returns the instance that was modified so you can chain methods.
|
||||
|
||||
B<Note:> All events in the new or modified instance will be converted to
|
||||
L<Test2::API::InterceptResult::Event> instances. There is no way to avoid this,
|
||||
the squash operation requires the upgraded event class.
|
||||
|
||||
L<Test::More> and many other legacy tools would send notes, diags, and
|
||||
assertions as seperate events. A subtest in L<Test::More> would send a note
|
||||
with the subtest name, the subtest assertion, and finally a diagnostics event
|
||||
if the subtest failed. This method will normalize things by squashing the note
|
||||
and diag into the same event as the subtest (This is different from putting
|
||||
them into the subtest, which is not what happens).
|
||||
|
||||
=back
|
||||
|
||||
=head2 FILTERING
|
||||
|
||||
B<Note:> These normally return new instances, leaving the originals unchanged.
|
||||
If you call them in void context they will throw exceptions. If you want to
|
||||
modify the originals you must pass in the C<< in_place => 1 >> option. You may
|
||||
call these in void context when you ask to modify them in place. The in-place
|
||||
forms return the instance that was modified so you can chain methods.
|
||||
|
||||
=head3 %PARAMS
|
||||
|
||||
These all accept the same 2 optional parameters:
|
||||
|
||||
=over 4
|
||||
|
||||
=item in_place => $BOOL
|
||||
|
||||
When true the method will modify the instance in place instead of returning a
|
||||
new instance.
|
||||
|
||||
=item args => \@ARGS
|
||||
|
||||
If you wish to pass parameters into the event method being used for filtering,
|
||||
you may do so here.
|
||||
|
||||
=back
|
||||
|
||||
=head3 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item $events->grep($CALL, %PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
Test2::API::InterceptResult->new(
|
||||
grep { $_->$CALL( @{$PARAMS{args}} ) } $self->event_list,
|
||||
);
|
||||
|
||||
B<Note:> that $CALL is called on an upgraded version of the event, though
|
||||
the events returned will be the original ones, not the upgraded ones.
|
||||
|
||||
$CALL may be either the name of a method on
|
||||
L<Test2::API::InterceptResult::Event>, or a coderef.
|
||||
|
||||
=item $events->asserts(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
$events->grep(has_assert => @{$PARAMS{args}})
|
||||
|
||||
It returns a new instance containing only the events that made assertions.
|
||||
|
||||
=item $events->subtests(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
$events->grep(has_subtest => @{$PARAMS{args}})
|
||||
|
||||
It returns a new instance containing only the events that have subtests.
|
||||
|
||||
=item $events->diags(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
$events->grep(has_diags => @{$PARAMS{args}})
|
||||
|
||||
It returns a new instance containing only the events that have diags.
|
||||
|
||||
=item $events->notes(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
$events->grep(has_notes => @{$PARAMS{args}})
|
||||
|
||||
It returns a new instance containing only the events that have notes.
|
||||
|
||||
=item $events->errors(%PARAMS)
|
||||
|
||||
B<Note:> Errors are NOT failing assertions. Failing assertions are a different
|
||||
thing.
|
||||
|
||||
This is essentially:
|
||||
|
||||
$events->grep(has_errors => @{$PARAMS{args}})
|
||||
|
||||
It returns a new instance containing only the events that have errors.
|
||||
|
||||
=item $events->plans(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
$events->grep(has_plan => @{$PARAMS{args}})
|
||||
|
||||
It returns a new instance containing only the events that set the plan.
|
||||
|
||||
=item $events->causes_fail(%PARAMS)
|
||||
|
||||
=item $events->causes_failure(%PARAMS)
|
||||
|
||||
These are essentially:
|
||||
|
||||
$events->grep(causes_fail => @{$PARAMS{args}})
|
||||
$events->grep(causes_failure => @{$PARAMS{args}})
|
||||
|
||||
B<Note:> C<causes_fail()> and C<causes_failure()> are both aliases for
|
||||
eachother in events, so these methods are effectively aliases here as well.
|
||||
|
||||
It returns a new instance containing only the events that cause failure.
|
||||
|
||||
=back
|
||||
|
||||
=head2 MAPPING
|
||||
|
||||
These methods B<ALWAYS> return an arrayref.
|
||||
|
||||
B<Note:> No methods on L<Test2::API::InterceptResult::Event> alter the event in
|
||||
any way.
|
||||
|
||||
B<Important Notes about Events>:
|
||||
|
||||
L<Test2::API::InterceptResult::Event> was tailor-made to be used in
|
||||
event-lists. Most methods that are not applicable to a given event will return
|
||||
an empty list, so you normally do not need to worry about unwanted C<undef>
|
||||
values or exceptions being thrown. Mapping over event methods is an entended
|
||||
use, so it works well to produce lists.
|
||||
|
||||
B<Exceptions to the rule:>
|
||||
|
||||
Some methods such as C<causes_fail> always return a boolean true or false for
|
||||
all events. Any method prefixed with C<the_> conveys the intent that the event
|
||||
should have exactly 1 of something, so those will throw an exception when that
|
||||
condition is not true.
|
||||
|
||||
=over 4
|
||||
|
||||
=item $arrayref = $events->map($CALL, %PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->$CALL(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
$CALL may be either the name of a method on
|
||||
L<Test2::API::InterceptResult::Event>, or a coderef.
|
||||
|
||||
=item $arrayref = $events->flatten(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->flatten(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of flattened structures.
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what C<flatten()>
|
||||
returns.
|
||||
|
||||
=item $arrayref = $events->briefs(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->briefs(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of event briefs.
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what C<brief()>
|
||||
returns.
|
||||
|
||||
=item $arrayref = $events->summaries(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->summaries(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of event summaries.
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what C<summary()>
|
||||
returns.
|
||||
|
||||
=item $arrayref = $events->subtest_results(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->subtest_result(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of event summaries.
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what
|
||||
C<subtest_result()> returns.
|
||||
|
||||
=item $arrayref = $events->diag_messages(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->diag_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of diagnostic messages (strings).
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what
|
||||
C<diag_messages()> returns.
|
||||
|
||||
=item $arrayref = $events->note_messages(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->note_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of notification messages (strings).
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what
|
||||
C<note_messages()> returns.
|
||||
|
||||
=item $arrayref = $events->error_messages(%PARAMS)
|
||||
|
||||
This is essentially:
|
||||
|
||||
[ map { $_->error_messages(@{ $PARAMS{args} }) } $events->upgrade->event_list ];
|
||||
|
||||
It returns a new list of error messages (strings).
|
||||
|
||||
See L<Test2::API::InterceptResult::Event> for details on what
|
||||
C<error_messages()> returns.
|
||||
|
||||
=back
|
||||
|
||||
=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>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|
||||
1087
database/perl/lib/Test2/API/InterceptResult/Event.pm
Normal file
1087
database/perl/lib/Test2/API/InterceptResult/Event.pm
Normal file
File diff suppressed because it is too large
Load Diff
25
database/perl/lib/Test2/API/InterceptResult/Facet.pm
Normal file
25
database/perl/lib/Test2/API/InterceptResult/Facet.pm
Normal file
@@ -0,0 +1,25 @@
|
||||
package Test2::API::InterceptResult::Facet;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
BEGIN {
|
||||
require Test2::EventFacet;
|
||||
our @ISA = ('Test2::EventFacet');
|
||||
}
|
||||
|
||||
our $AUTOLOAD;
|
||||
sub AUTOLOAD {
|
||||
my $self = shift;
|
||||
|
||||
my $name = $AUTOLOAD;
|
||||
$name =~ s/^.*:://g;
|
||||
|
||||
return undef unless exists $self->{$name};
|
||||
return $self->{$name};
|
||||
}
|
||||
|
||||
sub DESTROY {}
|
||||
|
||||
1;
|
||||
66
database/perl/lib/Test2/API/InterceptResult/Hub.pm
Normal file
66
database/perl/lib/Test2/API/InterceptResult/Hub.pm
Normal file
@@ -0,0 +1,66 @@
|
||||
package Test2::API::InterceptResult::Hub;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
BEGIN { require Test2::Hub; our @ISA = qw(Test2::Hub) }
|
||||
use Test2::Util::HashBase;
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
$self->SUPER::init();
|
||||
$self->{+NESTED} = 0;
|
||||
}
|
||||
|
||||
sub inherit {
|
||||
my $self = shift;
|
||||
|
||||
$self->{+NESTED} = 0;
|
||||
}
|
||||
|
||||
sub terminate { }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::API::InterceptResult::Hub - Hub used by InterceptResult.
|
||||
|
||||
=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>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|
||||
196
database/perl/lib/Test2/API/InterceptResult/Squasher.pm
Normal file
196
database/perl/lib/Test2/API/InterceptResult/Squasher.pm
Normal file
@@ -0,0 +1,196 @@
|
||||
package Test2::API::InterceptResult::Squasher;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
use Carp qw/croak/;
|
||||
use List::Util qw/first/;
|
||||
|
||||
use Test2::Util::HashBase qw{
|
||||
<events
|
||||
|
||||
+down_sig +down_buffer
|
||||
|
||||
+up_into +up_sig +up_clear
|
||||
};
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
|
||||
croak "'events' is a required attribute" unless $self->{+EVENTS};
|
||||
}
|
||||
|
||||
sub can_squash {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
|
||||
# No info, no squash
|
||||
return unless $event->has_info;
|
||||
|
||||
# Do not merge up if one of these is true
|
||||
return if first { $event->$_ } 'causes_fail', 'has_assert', 'has_bailout', 'has_errors', 'has_plan', 'has_subtest';
|
||||
|
||||
# Signature if we can squash
|
||||
return $event->trace_signature;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
|
||||
return if $self->squash_up($event);
|
||||
return if $self->squash_down($event);
|
||||
|
||||
$self->flush_down($event);
|
||||
|
||||
push @{$self->{+EVENTS}} => $event;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub squash_down {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
|
||||
my $sig = $self->can_squash($event)
|
||||
or return;
|
||||
|
||||
$self->flush_down()
|
||||
if $self->{+DOWN_SIG} && $self->{+DOWN_SIG} ne $sig;
|
||||
|
||||
$self->{+DOWN_SIG} ||= $sig;
|
||||
push @{$self->{+DOWN_BUFFER}} => $event;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub flush_down {
|
||||
my $self = shift;
|
||||
my ($into) = @_;
|
||||
|
||||
my $sig = delete $self->{+DOWN_SIG};
|
||||
my $buffer = delete $self->{+DOWN_BUFFER};
|
||||
|
||||
return unless $buffer && @$buffer;
|
||||
|
||||
my $fsig = $into ? $into->trace_signature : undef;
|
||||
|
||||
if ($fsig && $fsig eq $sig) {
|
||||
$self->squash($into, @$buffer);
|
||||
}
|
||||
else {
|
||||
push @{$self->{+EVENTS}} => @$buffer if $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
sub clear_up {
|
||||
my $self = shift;
|
||||
|
||||
return unless $self->{+UP_CLEAR};
|
||||
|
||||
delete $self->{+UP_INTO};
|
||||
delete $self->{+UP_SIG};
|
||||
delete $self->{+UP_CLEAR};
|
||||
}
|
||||
|
||||
sub squash_up {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
no warnings 'uninitialized';
|
||||
|
||||
$self->clear_up;
|
||||
|
||||
if ($event->has_assert) {
|
||||
if(my $sig = $event->trace_signature) {
|
||||
$self->{+UP_INTO} = $event;
|
||||
$self->{+UP_SIG} = $sig;
|
||||
$self->{+UP_CLEAR} = 0;
|
||||
}
|
||||
else {
|
||||
$self->{+UP_CLEAR} = 1;
|
||||
$self->clear_up;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
my $into = $self->{+UP_INTO} or return;
|
||||
|
||||
# Next iteration should clear unless something below changes that
|
||||
$self->{+UP_CLEAR} = 1;
|
||||
|
||||
# Only merge into matching trace signatres
|
||||
my $sig = $self->can_squash($event);
|
||||
return unless $sig eq $self->{+UP_SIG};
|
||||
|
||||
# OK Merge! Do not clear merge in case the return event is also a matching sig diag-only
|
||||
$self->{+UP_CLEAR} = 0;
|
||||
|
||||
$self->squash($into, $event);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub squash {
|
||||
my $self = shift;
|
||||
my ($into, @from) = @_;
|
||||
push @{$into->facet_data->{info}} => $_->info for @from;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
|
||||
return unless $self->{+EVENTS};
|
||||
$self->flush_down();
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::API::InterceptResult::Squasher - Encapsulation of the algorithm that
|
||||
squashes diags into assertions.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Internal use only, please ignore.
|
||||
|
||||
=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>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|
||||
226
database/perl/lib/Test2/API/Stack.pm
Normal file
226
database/perl/lib/Test2/API/Stack.pm
Normal file
@@ -0,0 +1,226 @@
|
||||
package Test2::API::Stack;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '1.302183';
|
||||
|
||||
|
||||
use Test2::Hub();
|
||||
|
||||
use Carp qw/confess/;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
return bless [], $class;
|
||||
}
|
||||
|
||||
sub new_hub {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my $class = delete $params{class} || 'Test2::Hub';
|
||||
|
||||
my $hub = $class->new(%params);
|
||||
|
||||
if (@$self) {
|
||||
$hub->inherit($self->[-1], %params);
|
||||
}
|
||||
else {
|
||||
require Test2::API;
|
||||
$hub->format(Test2::API::test2_formatter()->new_root)
|
||||
unless $hub->format || exists($params{formatter});
|
||||
|
||||
my $ipc = Test2::API::test2_ipc();
|
||||
if ($ipc && !$hub->ipc && !exists($params{ipc})) {
|
||||
$hub->set_ipc($ipc);
|
||||
$ipc->add_hub($hub->hid);
|
||||
}
|
||||
}
|
||||
|
||||
push @$self => $hub;
|
||||
|
||||
$hub;
|
||||
}
|
||||
|
||||
sub top {
|
||||
my $self = shift;
|
||||
return $self->new_hub unless @$self;
|
||||
return $self->[-1];
|
||||
}
|
||||
|
||||
sub peek {
|
||||
my $self = shift;
|
||||
return @$self ? $self->[-1] : undef;
|
||||
}
|
||||
|
||||
sub cull {
|
||||
my $self = shift;
|
||||
$_->cull for reverse @$self;
|
||||
}
|
||||
|
||||
sub all {
|
||||
my $self = shift;
|
||||
return @$self;
|
||||
}
|
||||
|
||||
sub root {
|
||||
my $self = shift;
|
||||
return unless @$self;
|
||||
return $self->[0];
|
||||
}
|
||||
|
||||
sub clear {
|
||||
my $self = shift;
|
||||
@$self = ();
|
||||
}
|
||||
|
||||
# Do these last without keywords in order to prevent them from getting used
|
||||
# when we want the real push/pop.
|
||||
|
||||
{
|
||||
no warnings 'once';
|
||||
|
||||
*push = sub {
|
||||
my $self = shift;
|
||||
my ($hub) = @_;
|
||||
$hub->inherit($self->[-1]) if @$self;
|
||||
push @$self => $hub;
|
||||
};
|
||||
|
||||
*pop = sub {
|
||||
my $self = shift;
|
||||
my ($hub) = @_;
|
||||
confess "No hubs on the stack"
|
||||
unless @$self;
|
||||
confess "You cannot pop the root hub"
|
||||
if 1 == @$self;
|
||||
confess "Hub stack mismatch, attempted to pop incorrect hub"
|
||||
unless $self->[-1] == $hub;
|
||||
pop @$self;
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::API::Stack - Object to manage a stack of L<Test2::Hub>
|
||||
instances.
|
||||
|
||||
=head1 ***INTERNALS NOTE***
|
||||
|
||||
B<The internals of this package are subject to change at any time!> The public
|
||||
methods provided will not change in backwards incompatible ways, but the
|
||||
underlying implementation details might. B<Do not break encapsulation here!>
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used to represent and manage a stack of L<Test2::Hub>
|
||||
objects. Hubs are usually in a stack so that you can push a new hub into place
|
||||
that can intercept and handle events differently than the primary hub.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $stack = Test2::API::Stack->new;
|
||||
my $hub = $stack->top;
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item $stack = Test2::API::Stack->new()
|
||||
|
||||
This will create a new empty stack instance. All arguments are ignored.
|
||||
|
||||
=item $hub = $stack->new_hub()
|
||||
|
||||
=item $hub = $stack->new_hub(%params)
|
||||
|
||||
=item $hub = $stack->new_hub(%params, class => $class)
|
||||
|
||||
This will generate a new hub and push it to the top of the stack. Optionally
|
||||
you can provide arguments that will be passed into the constructor for the
|
||||
L<Test2::Hub> object.
|
||||
|
||||
If you specify the C<< 'class' => $class >> argument, the new hub will be an
|
||||
instance of the specified class.
|
||||
|
||||
Unless your parameters specify C<'formatter'> or C<'ipc'> arguments, the
|
||||
formatter and IPC instance will be inherited from the current top hub. You can
|
||||
set the parameters to C<undef> to avoid having a formatter or IPC instance.
|
||||
|
||||
If there is no top hub, and you do not ask to leave IPC and formatter undef,
|
||||
then a new formatter will be created, and the IPC instance from
|
||||
L<Test2::API> will be used.
|
||||
|
||||
=item $hub = $stack->top()
|
||||
|
||||
This will return the top hub from the stack. If there is no top hub yet this
|
||||
will create it.
|
||||
|
||||
=item $hub = $stack->peek()
|
||||
|
||||
This will return the top hub from the stack. If there is no top hub yet this
|
||||
will return undef.
|
||||
|
||||
=item $stack->cull
|
||||
|
||||
This will call C<< $hub->cull >> on all hubs in the stack.
|
||||
|
||||
=item @hubs = $stack->all
|
||||
|
||||
This will return all the hubs in the stack as a list.
|
||||
|
||||
=item $stack->clear
|
||||
|
||||
This will completely remove all hubs from the stack. Normally you do not want
|
||||
to do this, but there are a few valid reasons for it.
|
||||
|
||||
=item $stack->push($hub)
|
||||
|
||||
This will push the new hub onto the stack.
|
||||
|
||||
=item $stack->pop($hub)
|
||||
|
||||
This will pop a hub from the stack, if the hub at the top of the stack does not
|
||||
match the hub you expect (passed in as an argument) it will throw an exception.
|
||||
|
||||
=back
|
||||
|
||||
=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>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2020 Chad Granum E<lt>exodist@cpan.orgE<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
|
||||
Reference in New Issue
Block a user