The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

use strict;
BEGIN { require "t/tools.pl" };
use Test2::API qw/context test2_ipc_drivers/;
use Test2::Util qw/CAN_FORK CAN_THREAD CAN_REALLY_FORK/;
{
package My::Formatter;
sub new { bless [], shift };
my $check = 1;
sub write {
my $self = shift;
my ($e, $count) = @_;
push @$self => $e;
}
}
{
package My::Event;
use Test2::Util::HashBase qw{msg};
}
tests basic => sub {
my $hub = Test2::Hub->new(
formatter => My::Formatter->new,
);
my $send_event = sub {
my ($msg) = @_;
my $e = My::Event->new(msg => $msg, trace => 'fake');
$hub->send($e);
};
ok(my $e1 = $send_event->('foo'), "Created event");
ok(my $e2 = $send_event->('bar'), "Created event");
ok(my $e3 = $send_event->('baz'), "Created event");
my $old = $hub->format(My::Formatter->new);
ok($old->isa('My::Formatter'), "old formatter");
is_deeply(
$old,
[$e1, $e2, $e3],
"Formatter got all events"
);
};
tests follow_ups => sub {
my $hub = Test2::Hub->new;
$hub->set_count(1);
my $trace = Test2::Util::Trace->new(
frame => [__PACKAGE__, __FILE__, __LINE__],
);
my $ran = 0;
$hub->follow_up(sub {
my ($d, $h) = @_;
is_deeply($d, $trace, "Got trace");
is_deeply($h, $hub, "Got hub");
ok(!$hub->ended, "Hub state has not ended yet");
$ran++;
});
like(
exception { $hub->follow_up('xxx') },
qr/follow_up only takes coderefs for arguments, got 'xxx'/,
"follow_up takes a coderef"
);
$hub->finalize($trace);
is($ran, 1, "ran once");
is_deeply(
$hub->ended,
$trace->frame,
"Ended at the expected place."
);
eval { $hub->finalize($trace) };
is($ran, 1, "ran once");
$hub = undef;
};
tests IPC => sub {
my ($driver) = test2_ipc_drivers();
is($driver, 'Test2::IPC::Driver::Files', "Default Driver");
my $ipc = $driver->new;
my $hub = Test2::Hub->new(
formatter => My::Formatter->new,
ipc => $ipc,
);
my $build_event = sub {
my ($msg) = @_;
return My::Event->new(msg => $msg, trace => 'fake');
};
my $e1 = $build_event->('foo');
my $e2 = $build_event->('bar');
my $e3 = $build_event->('baz');
my $do_send = sub {
$hub->send($e1);
$hub->send($e2);
$hub->send($e3);
};
my $do_check = sub {
my $name = shift;
my $old = $hub->format(My::Formatter->new);
ok($old->isa('My::Formatter'), "old formatter");
is_deeply(
$old,
[$e1, $e2, $e3],
"Formatter got all events ($name)"
);
};
if (CAN_REALLY_FORK) {
my $pid = fork();
die "Could not fork!" unless defined $pid;
if ($pid) {
is(waitpid($pid, 0), $pid, "waited properly");
ok(!$?, "child exited with success");
$hub->cull();
$do_check->('Fork');
}
else {
$do_send->();
exit 0;
}
}
if (CAN_THREAD && $] ge '5.010') {
require threads;
my $thr = threads->new(sub { $do_send->() });
$thr->join;
$hub->cull();
$do_check->('Threads');
}
$do_send->();
$hub->cull();
$do_check->('no IPC');
};
tests listen => sub {
my $hub = Test2::Hub->new();
my @events;
my @counts;
my $it = $hub->listen(sub {
my ($h, $e, $count) = @_;
is_deeply($h, $hub, "got hub");
push @events => $e;
push @counts => $count;
});
my $second;
my $it2 = $hub->listen(sub { $second++ });
my $ok1 = Test2::Event::Ok->new(
pass => 1,
name => 'foo',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
my $ok2 = Test2::Event::Ok->new(
pass => 0,
name => 'bar',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
my $ok3 = Test2::Event::Ok->new(
pass => 1,
name => 'baz',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
$hub->send($ok1);
$hub->send($ok2);
$hub->unlisten($it);
$hub->send($ok3);
is_deeply(\@counts, [1, 2], "Got counts");
is_deeply(\@events, [$ok1, $ok2], "got events");
is($second, 3, "got all events in listener that was not removed");
like(
exception { $hub->listen('xxx') },
qr/listen only takes coderefs for arguments, got 'xxx'/,
"listen takes a coderef"
);
};
tests metadata => sub {
my $hub = Test2::Hub->new();
my $default = { foo => 1 };
my $meta = $hub->meta('Foo', $default);
is_deeply($meta, $default, "Set Meta");
$meta = $hub->meta('Foo', {});
is_deeply($meta, $default, "Same Meta");
$hub->delete_meta('Foo');
is($hub->meta('Foo'), undef, "No Meta");
$hub->meta('Foo', {})->{xxx} = 1;
is($hub->meta('Foo')->{xxx}, 1, "Vivified meta and set it");
like(
exception { $hub->meta(undef) },
qr/Invalid META key: undef, keys must be true, and may not be references/,
"Cannot use undef as a meta key"
);
like(
exception { $hub->meta(0) },
qr/Invalid META key: '0', keys must be true, and may not be references/,
"Cannot use 0 as a meta key"
);
like(
exception { $hub->delete_meta(undef) },
qr/Invalid META key: undef, keys must be true, and may not be references/,
"Cannot use undef as a meta key"
);
like(
exception { $hub->delete_meta(0) },
qr/Invalid META key: '0', keys must be true, and may not be references/,
"Cannot use 0 as a meta key"
);
};
tests filter => sub {
my $hub = Test2::Hub->new();
my @events;
my $it = $hub->filter(sub {
my ($h, $e) = @_;
is($h, $hub, "got hub");
push @events => $e;
return $e;
});
my $count;
my $it2 = $hub->filter(sub { $count++; $_[1] });
my $ok1 = Test2::Event::Ok->new(
pass => 1,
name => 'foo',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
my $ok2 = Test2::Event::Ok->new(
pass => 0,
name => 'bar',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
my $ok3 = Test2::Event::Ok->new(
pass => 1,
name => 'baz',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
$hub->send($ok1);
$hub->send($ok2);
$hub->unfilter($it);
$hub->send($ok3);
is_deeply(\@events, [$ok1, $ok2], "got events");
is($count, 3, "got all events, even after other filter was removed");
$hub = Test2::Hub->new();
@events = ();
$hub->filter(sub { undef });
$hub->listen(sub {
my ($hub, $e) = @_;
push @events => $e;
});
$hub->send($ok1);
$hub->send($ok2);
$hub->send($ok3);
ok(!@events, "Blocked events");
like(
exception { $hub->filter('xxx') },
qr/filter only takes coderefs for arguments, got 'xxx'/,
"filter takes a coderef"
);
};
tests pre_filter => sub {
my $hub = Test2::Hub->new();
my @events;
my $it = $hub->pre_filter(sub {
my ($h, $e) = @_;
is($h, $hub, "got hub");
push @events => $e;
return $e;
});
my $count;
my $it2 = $hub->pre_filter(sub { $count++; $_[1] });
my $ok1 = Test2::Event::Ok->new(
pass => 1,
name => 'foo',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
my $ok2 = Test2::Event::Ok->new(
pass => 0,
name => 'bar',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
my $ok3 = Test2::Event::Ok->new(
pass => 1,
name => 'baz',
trace => Test2::Util::Trace->new(
frame => [ __PACKAGE__, __FILE__, __LINE__ ],
),
);
$hub->send($ok1);
$hub->send($ok2);
$hub->pre_unfilter($it);
$hub->send($ok3);
is_deeply(\@events, [$ok1, $ok2], "got events");
is($count, 3, "got all events, even after other pre_filter was removed");
$hub = Test2::Hub->new();
@events = ();
$hub->pre_filter(sub { undef });
$hub->listen(sub {
my ($hub, $e) = @_;
push @events => $e;
});
$hub->send($ok1);
$hub->send($ok2);
$hub->send($ok3);
ok(!@events, "Blocked events");
like(
exception { $hub->pre_filter('xxx') },
qr/pre_filter only takes coderefs for arguments, got 'xxx'/,
"pre_filter takes a coderef"
);
};
tests state => sub {
my $hub = Test2::Hub->new;
is($hub->count, 0, "count starts at 0");
is($hub->failed, 0, "failed starts at 0");
is($hub->is_passing, 1, "start off passing");
is($hub->plan, undef, "no plan yet");
$hub->is_passing(0);
is($hub->is_passing, 0, "Can Fail");
$hub->is_passing(1);
is($hub->is_passing, 1, "Passes again");
$hub->set_count(1);
is($hub->count, 1, "Added a passing result");
is($hub->failed, 0, "still no fails");
is($hub->is_passing, 1, "Still passing");
$hub->set_count(2);
$hub->set_failed(1);
is($hub->count, 2, "Added a result");
is($hub->failed, 1, "new failure");
is($hub->is_passing, 0, "Not passing");
$hub->is_passing(1);
is($hub->is_passing, 0, "is_passing always false after a failure");
$hub->set_failed(0);
$hub->is_passing(1);
is($hub->is_passing, 1, "Passes again");
$hub->set_failed(1);
is($hub->count, 2, "No new result");
is($hub->failed, 1, "new failure");
is($hub->is_passing, 0, "Not passing");
ok(!eval { $hub->plan('foo'); 1 }, "Could not set plan to 'foo'");
like($@, qr/'foo' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'/, "Got expected error");
ok($hub->plan(5), "Can set plan to integer");
is($hub->plan, 5, "Set the plan to an integer");
$hub->set__plan(undef);
ok($hub->plan('NO PLAN'), "Can set plan to 'NO PLAN'");
is($hub->plan, 'NO PLAN', "Set the plan to 'NO PLAN'");
$hub->set__plan(undef);
ok($hub->plan('SKIP'), "Can set plan to 'SKIP'");
is($hub->plan, 'SKIP', "Set the plan to 'SKIP'");
ok(!eval { $hub->plan(5); 1 }, "Cannot change plan");
like($@, qr/You cannot change the plan/, "Got error");
my $trace = Test2::Util::Trace->new(frame => ['Foo::Bar', 'foo.t', 42, 'blah']);
$hub->finalize($trace);
my $ok = eval { $hub->finalize($trace) };
my $err = $@;
ok(!$ok, "died");
is($err, <<" EOT", "Got expected error");
Test already ended!
First End: foo.t line 42
Second End: foo.t line 42
EOT
$hub = Test2::Hub->new;
$hub->plan(5);
$hub->set_count(5);
$hub->set_failed(1);
$hub->set_ended($trace);
$hub->set_bailed_out("foo");
$hub->set_skip_reason('xxx');
ok(!$hub->is_passing, "not passing");
$hub->reset_state;
ok(!$hub->plan, "no plan");
is($hub->count, 0, "count reset to 0");
is($hub->failed, 0, "reset failures");
ok(!$hub->ended, "not ended");
ok(!$hub->bailed_out, "did not bail out");
ok(!$hub->skip_reason, "no skip reason");
};
done_testing;