diff options
author | Chad Granum <exodist7@gmail.com> | 2016-05-10 07:44:27 -0700 |
---|---|---|
committer | Tony Cook <tony@develop-help.com> | 2016-05-12 09:32:16 +1000 |
commit | b4514920cd5cabccad6add35edf1bef258070a11 (patch) | |
tree | 5b325fe0b70f3820ce5da1163326e555b4240aa1 /cpan/Test-Simple/t/Test2 | |
parent | b25b06cfba95499e3ff101909adcc2c23aea0d58 (diff) | |
download | perl-b4514920cd5cabccad6add35edf1bef258070a11.tar.gz |
Update to the latest Test-Simple cpan dist
Diffstat (limited to 'cpan/Test-Simple/t/Test2')
45 files changed, 4307 insertions, 0 deletions
diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_done_testing.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_done_testing.t new file mode 100644 index 0000000000..7badf3e6ee --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_done_testing.t @@ -0,0 +1,26 @@ +use strict; +use warnings; + +use Test2::API qw/context/; + +sub done_testing { + my $ctx = context(); + + die "Test Already ended!" if $ctx->hub->ended; + $ctx->hub->finalize($ctx->trace, 1); + $ctx->release; +} + +sub ok($;$) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; +} + +ok(1, "First"); +ok(1, "Second"); + +done_testing; + +1; diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_fork.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_fork.t new file mode 100644 index 0000000000..f6d72f643e --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_fork.t @@ -0,0 +1,34 @@ +use strict; +use warnings; + +use Test2::Util qw/CAN_FORK/; +use Test2::IPC; +use Test2::API qw/context/; + +sub plan { + my $ctx = context(); + $ctx->plan(@_); + $ctx->release; +} + +sub ok($;$) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; +} + +plan(0, skip_all => 'System cannot fork') unless CAN_FORK(); + +plan(6); + +for (1 .. 3) { + my $pid = fork; + die "Failed to fork" unless defined $pid; + next if $pid; + ok(1, "test 1 in pid $$"); + ok(1, "test 2 in pid $$"); + last; +} + +1; diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_no_plan.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_no_plan.t new file mode 100644 index 0000000000..32dde2cc45 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_no_plan.t @@ -0,0 +1,24 @@ +use strict; +use warnings; + +use Test2::API qw/context/; + +sub plan { + my $ctx = context(); + $ctx->plan(@_); + $ctx->release; +} + +sub ok($;$) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; +} + +plan(0, 'no_plan'); + +ok(1, "First"); +ok(1, "Second"); + +1; diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_plan.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_plan.t new file mode 100644 index 0000000000..3656d85d12 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_plan.t @@ -0,0 +1,24 @@ +use strict; +use warnings; + +use Test2::API qw/context/; + +sub plan { + my $ctx = context(); + $ctx->plan(@_); + $ctx->release; +} + +sub ok($;$) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; +} + +plan(2); + +ok(1, "First"); +ok(1, "Second"); + +1; diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_skip.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_skip.t new file mode 100644 index 0000000000..3816eb035a --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_skip.t @@ -0,0 +1,16 @@ +use strict; +use warnings; + +use Test2::API qw/context/; + +sub plan { + my $ctx = context(); + $ctx->plan(@_); + $ctx->release; +} + +plan(0, skip_all => 'testing skip all'); + +die "Should not see this"; + +1; diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_threads.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_threads.t new file mode 100644 index 0000000000..e3201585fd --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_threads.t @@ -0,0 +1,35 @@ +use strict; +use warnings; + +use Test2::Util qw/CAN_THREAD/; +use Test2::IPC; +use Test2::API qw/context/; + +sub plan { + my $ctx = context(); + $ctx->plan(@_); + $ctx->release; +} + +sub ok($;$) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; +} + +plan(0, skip_all => 'System does not have threads') unless CAN_THREAD(); + +plan(6); + +require threads; +threads->import; + +for (1 .. 3) { + threads->create(sub { + ok(1, "test 1 in thread " . threads->tid()); + ok(1, "test 2 in thread " . threads->tid()); + }); +} + +1; diff --git a/cpan/Test-Simple/t/Test2/acceptance/try_it_todo.t b/cpan/Test-Simple/t/Test2/acceptance/try_it_todo.t new file mode 100644 index 0000000000..5c5f6945ec --- /dev/null +++ b/cpan/Test-Simple/t/Test2/acceptance/try_it_todo.t @@ -0,0 +1,52 @@ +use strict; +use warnings; + +use Test2::API qw/context test2_stack/; + +sub done_testing { + my $ctx = context(); + + die "Test Already ended!" if $ctx->hub->ended; + $ctx->hub->finalize($ctx->trace, 1); + $ctx->release; +} + +sub ok($;$) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; +} + +sub diag { + my $ctx = context(); + $ctx->diag( join '', @_ ); + $ctx->release; +} + +ok(1, "First"); + +my $filter = test2_stack->top->filter(sub { + my ($hub, $event) = @_; + + # Turn a diag into a note + return Test2::Event::Note->new(%$event) if ref($event) eq 'Test2::Event::Diag'; + + # Set todo on ok's + if ($event->isa('Test2::Event::Ok')) { + $event->set_todo('here be dragons'); + $event->set_effective_pass(1); + } + + return $event; +}); + +ok(0, "Second"); +diag "should be a note"; + +test2_stack->top->unfilter($filter); + +ok(1, "Third"); +diag "should be a diag"; + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/Subtest_buffer_formatter.t b/cpan/Test-Simple/t/Test2/behavior/Subtest_buffer_formatter.t new file mode 100644 index 0000000000..6aa0ffb6b3 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/Subtest_buffer_formatter.t @@ -0,0 +1,90 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; + +use Test2::API qw/run_subtest intercept test2_stack/; + +{ + package Formatter::Hide; + sub write { } + sub hide_buffered { 1 }; + + package Formatter::Show; + sub write { } + sub hide_buffered { 0 }; + + package Formatter::NA; + sub write { } +} + +my %HAS_FORMATTER; + +my $events = intercept { + my $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{unbuffered_none} = $hub->format ? 1 : 0; + }; + run_subtest('unbuffered', $code); + + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{buffered_none} = $hub->format ? 1 : 0; + }; + run_subtest('buffered', $code, 'BUFFERED'); + + + ##################### + test2_stack->top->format(bless {}, 'Formatter::Hide'); + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{unbuffered_hide} = $hub->format ? 1 : 0; + }; + run_subtest('unbuffered', $code); + + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{buffered_hide} = $hub->format ? 1 : 0; + }; + run_subtest('buffered', $code, 'BUFFERED'); + + + ##################### + test2_stack->top->format(bless {}, 'Formatter::Show'); + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{unbuffered_show} = $hub->format ? 1 : 0; + }; + run_subtest('unbuffered', $code); + + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{buffered_show} = $hub->format ? 1 : 0; + }; + run_subtest('buffered', $code, 'BUFFERED'); + + + ##################### + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{unbuffered_na} = $hub->format ? 1 : 0; + }; + run_subtest('unbuffered', $code); + + test2_stack->top->format(bless {}, 'Formatter::NA'); + $code = sub { + my $hub = test2_stack->top; + $HAS_FORMATTER{buffered_na} = $hub->format ? 1 : 0; + }; + run_subtest('buffered', $code, 'BUFFERED'); +}; + +ok(!$HAS_FORMATTER{unbuffered_none}, "Unbuffered with no parent formatter has no formatter"); +ok( $HAS_FORMATTER{unbuffered_show}, "Unbuffered where parent has 'show' formatter has formatter"); +ok( $HAS_FORMATTER{unbuffered_hide}, "Unbuffered where parent has 'hide' formatter has formatter"); + +ok(!$HAS_FORMATTER{buffered_none}, "Buffered with no parent formatter has no formatter"); +ok( $HAS_FORMATTER{buffered_show}, "Buffered where parent has 'show' formatter has formatter"); +ok(!$HAS_FORMATTER{buffered_hide}, "Buffered where parent has 'hide' formatter has no formatter"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/Subtest_events.t b/cpan/Test-Simple/t/Test2/behavior/Subtest_events.t new file mode 100644 index 0000000000..8113dd9078 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/Subtest_events.t @@ -0,0 +1,17 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; + +use Test2::API qw/run_subtest intercept/; + +my $events = intercept { + my $code = sub { ok(1) }; + run_subtest('blah', $code, 'buffered'); +}; + +ok(!$events->[0]->in_subtest, "main event is not inside a subtest"); +ok($events->[0]->subtest_id, "Got subtest id"); +ok($events->[0]->subevents->[0]->in_subtest, "nested events are in the subtest"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/Subtest_plan.t b/cpan/Test-Simple/t/Test2/behavior/Subtest_plan.t new file mode 100644 index 0000000000..f3dab3ca6a --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/Subtest_plan.t @@ -0,0 +1,19 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; + +use Test2::API qw/run_subtest intercept/; + +my $events = intercept { + my $code = sub { plan 4; ok(1) }; + run_subtest('bad_plan', $code, 'buffered'); +}; + +is( + $events->[-1]->message, + "Bad subtest plan, expected 4 but ran 1", + "Helpful message if subtest has a bad plan", +); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/Taint.t b/cpan/Test-Simple/t/Test2/behavior/Taint.t new file mode 100644 index 0000000000..5af9298683 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/Taint.t @@ -0,0 +1,23 @@ +#!/usr/bin/env perl -T +# HARNESS-NO-FORMATTER + +use Test2::API qw/context/; + +sub ok($;$@) { + my ($bool, $name) = @_; + my $ctx = context(); + $ctx->ok($bool, $name); + $ctx->release; + return $bool ? 1 : 0; +} + +sub done_testing { + my $ctx = context(); + $ctx->hub->finalize($ctx->trace, 1); + $ctx->release; +} + +ok(1); +ok(1); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/err_var.t b/cpan/Test-Simple/t/Test2/behavior/err_var.t new file mode 100644 index 0000000000..7d8bc7c4ee --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/err_var.t @@ -0,0 +1,15 @@ +use strict; +use warnings; + +use Test2::IPC; + +BEGIN { require "t/tools.pl" }; + +{ + local $! = 100; + + is(0 + $!, 100, 'set $!'); + is(0 + $!, 100, 'preserved $!'); +} + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/init_croak.t b/cpan/Test-Simple/t/Test2/behavior/init_croak.t new file mode 100644 index 0000000000..e8eb44b8ac --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/init_croak.t @@ -0,0 +1,28 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; + +BEGIN { + package Foo::Bar; + use Test2::Util::HashBase qw/foo bar baz/; + use Carp qw/croak/; + + sub init { + my $self = shift; + croak "'foo' is a required attribute" + unless $self->{+FOO}; + } +} + +$@ = ""; +my ($file, $line) = (__FILE__, __LINE__ + 1); +eval { my $one = Foo::Bar->new }; +my $err = $@; + +like( + $err, + qr/^'foo' is a required attribute at \Q$file\E line $line/, + "Croak does not report to HashBase from init" +); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/nested_context_exception.t b/cpan/Test-Simple/t/Test2/behavior/nested_context_exception.t new file mode 100644 index 0000000000..04cbdb8067 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/nested_context_exception.t @@ -0,0 +1,111 @@ +use strict; +use warnings; +BEGIN { $Test2::API::DO_DEPTH_CHECK = 1 } +BEGIN { require "t/tools.pl" }; + +use Test2::API qw/context/; + +sub outer { + my $code = shift; + my $ctx = context(); + + $ctx->note("outer"); + + my $out = eval { $code->() }; + + $ctx->release; + + return $out; +} + +sub dies { + my $ctx = context(); + $ctx->note("dies"); + die "Foo"; +} + +sub bad_store { + my $ctx = context(); + $ctx->note("bad store"); + return $ctx; # Emulate storing it somewhere +} + +sub bad_simple { + my $ctx = context(); + $ctx->note("bad simple"); + return; +} + +my @warnings; +{ + local $SIG{__WARN__} = sub { push @warnings => @_ }; + eval { dies() }; +} +ok(!@warnings, "no warnings") || diag @warnings; + +@warnings = (); +my $keep = bad_store(); +eval { my $x = 1 }; # Ensure an eval changing $@ does not meddle. +{ + local $SIG{__WARN__} = sub { push @warnings => @_ }; + ok(1, "random event"); +} +ok(@warnings, "got warnings"); +like( + $warnings[0], + qr/context\(\) was called to retrieve an existing context/, + "got expected warning" +); +$keep = undef; + +{ + @warnings = (); + local $SIG{__WARN__} = sub { push @warnings => @_ }; + bad_simple(); +} +ok(@warnings, "got warnings"); +like( + $warnings[0], + qr/A context appears to have been destroyed without first calling release/, + "got expected warning" +); + +@warnings = (); +outer(\&dies); +{ + local $SIG{__WARN__} = sub { push @warnings => @_ }; + ok(1, "random event"); +} +ok(!@warnings, "no warnings") || diag @warnings; + + + +@warnings = (); +{ + local $SIG{__WARN__} = sub { push @warnings => @_ }; + outer(\&bad_store); +} +ok(@warnings, "got warnings"); +like( + $warnings[0], + qr/A context appears to have been destroyed without first calling release/, + "got expected warning" +); + + + +{ + @warnings = (); + local $SIG{__WARN__} = sub { push @warnings => @_ }; + outer(\&bad_simple); +} +ok(@warnings, "got warnings") || diag @warnings; +like( + $warnings[0], + qr/A context appears to have been destroyed without first calling release/, + "got expected warning" +); + + + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/behavior/no_load_api.t b/cpan/Test-Simple/t/Test2/behavior/no_load_api.t new file mode 100644 index 0000000000..eab0da82ae --- /dev/null +++ b/cpan/Test-Simple/t/Test2/behavior/no_load_api.t @@ -0,0 +1,49 @@ +use strict; +use warnings; +use Data::Dumper; + +############################################################################### +# # +# This test is to insure certain objects do not load Test2::API directly or # +# indirectly when being required. It is ok for import() to load Test2::API if # +# necessary, but simply requiring the modules should not. # +# # +############################################################################### + +require Test2::Formatter; +require Test2::Formatter::TAP; + +require Test2::Event; +require Test2::Event::Bail; +require Test2::Event::Diag; +require Test2::Event::Exception; +require Test2::Event::Note; +require Test2::Event::Ok; +require Test2::Event::Plan; +require Test2::Event::Skip; +require Test2::Event::Subtest; +require Test2::Event::Waiting; + +require Test2::Util; +require Test2::Util::ExternalMeta; +require Test2::Util::HashBase; +require Test2::Util::Trace; + +require Test2::Hub; +require Test2::Hub::Interceptor; +require Test2::Hub::Subtest; +require Test2::Hub::Interceptor::Terminator; + +my @loaded = grep { $INC{$_} } qw{ + Test2/API.pm + Test2/API/Instance.pm + Test2/API/Context.pm + Test2/API/Stack.pm +}; + +require "t/tools.pl"; + +ok(!@loaded, "Test2::API was not loaded") + || diag("Loaded: " . Dumper(\@loaded)); + +done_testing(); diff --git a/cpan/Test-Simple/t/Test2/legacy/TAP.t b/cpan/Test-Simple/t/Test2/legacy/TAP.t new file mode 100644 index 0000000000..d98d7445f8 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/legacy/TAP.t @@ -0,0 +1,182 @@ +use strict; +use warnings; +# HARNESS-NO-FORMATTER + +BEGIN { require "t/tools.pl" }; + +######################### +# +# This test us here to insure that Ok, Diag, and Note events render the way +# Test::More renders them, trailing whitespace and all. +# +######################### + +use Test2::API qw/test2_stack/; + +sub capture(&) { + my $code = shift; + + my ($err, $out) = ("", ""); + + my $handles = test2_stack->top->format->handles; + my ($ok, $e); + { + my ($out_fh, $err_fh); + + ($ok, $e) = try { + open($out_fh, '>', \$out) or die "Failed to open a temporary STDOUT: $!"; + open($err_fh, '>', \$err) or die "Failed to open a temporary STDERR: $!"; + + test2_stack->top->format->set_handles([$out_fh, $err_fh, $out_fh]); + + $code->(); + }; + } + test2_stack->top->format->set_handles($handles); + + die $e unless $ok; + + $err =~ s/ $/_/mg; + $out =~ s/ $/_/mg; + + return { + STDOUT => $out, + STDERR => $err, + }; +} + +# The tools in tools.pl have some intentional differences from the Test::More +# versions, these behave more like Test::More which is important for +# back-compat. +sub tm_ok($;$) { + my ($bool, $name) = @_; + my $ctx = context; + + $name && ( + (index($name, "#" ) >= 0 && $name =~ s|#|\\#|g), + (index($name, "\n") >= 0 && $name =~ s{\n}{\n# }sg) + ); + + my $ok = bless { + pass => $bool, + name => $name, + effective_pass => 1, + trace => $ctx->trace->snapshot, + }, 'Test2::Event::Ok'; + # Do not call init + + $ctx->hub->send($ok); + $ctx->release; + return $bool; +} + +# Test::More actually does a bit more, but for this test we just want to see +# what happens when message is a specific string, or undef. +sub tm_diag { + my $ctx = context(); + $ctx->diag(@_); + $ctx->release; +} + +sub tm_note { + my $ctx = context(); + $ctx->note(@_); + $ctx->release; +} + +# Ensure the top hub is generated +test2_stack->top; + +my $temp_hub = test2_stack->new_hub(); +my $diag = capture { + tm_diag(undef); + tm_diag(""); + tm_diag(" "); + tm_diag("A"); + tm_diag("\n"); + tm_diag("\nB"); + tm_diag("C\n"); + tm_diag("\nD\n"); + tm_diag("E\n\n"); +}; + +my $note = capture { + tm_note(undef); + tm_note(""); + tm_note(" "); + tm_note("A"); + tm_note("\n"); + tm_note("\nB"); + tm_note("C\n"); + tm_note("\nD\n"); + tm_note("E\n\n"); +}; + +my $ok = capture { + tm_ok(1); + tm_ok(1, ""); + tm_ok(1, " "); + tm_ok(1, "A"); + tm_ok(1, "\n"); + tm_ok(1, "\nB"); + tm_ok(1, "C\n"); + tm_ok(1, "\nD\n"); + tm_ok(1, "E\n\n"); +}; +test2_stack->pop($temp_hub); + +is($diag->{STDOUT}, "", "STDOUT is empty for diag"); +is($diag->{STDERR}, <<EOT, "STDERR for diag looks right"); +# undef +#_ +# _ +# A +#_ +#_ +# B +# C +#_ +# D +# E +#_ +EOT + + +is($note->{STDERR}, "", "STDERR for note is empty"); +is($note->{STDOUT}, <<EOT, "STDOUT looks right for note"); +# undef +#_ +# _ +# A +#_ +#_ +# B +# C +#_ +# D +# E +#_ +EOT + + +is($ok->{STDERR}, "", "STDERR for ok is empty"); +is($ok->{STDOUT}, <<EOT, "STDOUT looks right for ok"); +ok 1 +ok 2 -_ +ok 3 - _ +ok 4 - A +ok 5 -_ +#_ +ok 6 -_ +# B +ok 7 - C +#_ +ok 8 -_ +# D +#_ +ok 9 - E +#_ +#_ +EOT + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/API.t b/cpan/Test-Simple/t/Test2/modules/API.t new file mode 100644 index 0000000000..c916d2a159 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/API.t @@ -0,0 +1,266 @@ +use strict; +use warnings; + +use Test2::API; + +my ($LOADED, $INIT); +BEGIN { + $INIT = Test2::API::test2_init_done; + $LOADED = Test2::API::test2_load_done; +}; + +use Test2::IPC; +BEGIN { require "t/tools.pl" }; +use Test2::Util qw/get_tid/; +my $CLASS = 'Test2::API'; + +# Ensure we do not break backcompat later by removing anything +ok(Test2::API->can($_), "$_ method is present") for qw{ + context_do + no_context + + test2_init_done + test2_load_done + + test2_pid + test2_tid + test2_stack + test2_no_wait + + test2_add_callback_context_init + test2_add_callback_context_release + test2_add_callback_exit + test2_add_callback_post_load + test2_list_context_init_callbacks + test2_list_context_release_callbacks + test2_list_exit_callbacks + test2_list_post_load_callbacks + + test2_ipc + test2_ipc_drivers + test2_ipc_add_driver + test2_ipc_polling + test2_ipc_disable_polling + test2_ipc_enable_polling + + test2_formatter + test2_formatters + test2_formatter_add + test2_formatter_set +}; + +ok(!$LOADED, "Was not load_done right away"); +ok(!$INIT, "Init was not done right away"); +ok(Test2::API::test2_load_done, "We loaded it"); + +# Note: This is a check that stuff happens in an END block. +{ + { + package FOLLOW; + + sub DESTROY { + return if $_[0]->{fixed}; + print "not ok - Did not run end ($_[0]->{name})!"; + $? = 255; + exit 255; + } + } + + our $kill1 = bless {fixed => 0, name => "Custom Hook"}, 'FOLLOW'; + Test2::API::test2_add_callback_exit( + sub { + print "# Running END hook\n"; + $kill1->{fixed} = 1; + } + ); + + our $kill2 = bless {fixed => 0, name => "set exit"}, 'FOLLOW'; + my $old = Test2::API::Instance->can('set_exit'); + no warnings 'redefine'; + *Test2::API::Instance::set_exit = sub { + $kill2->{fixed} = 1; + print "# Running set_exit\n"; + $old->(@_); + }; +} + +ok($CLASS->can('test2_init_done')->(), "init is done."); +ok($CLASS->can('test2_load_done')->(), "Test2 is finished loading"); + +is($CLASS->can('test2_pid')->(), $$, "got pid"); +is($CLASS->can('test2_tid')->(), get_tid(), "got tid"); + +ok($CLASS->can('test2_stack')->(), 'got stack'); +is($CLASS->can('test2_stack')->(), $CLASS->can('test2_stack')->(), "always get the same stack"); + +ok($CLASS->can('test2_ipc')->(), 'got ipc'); +is($CLASS->can('test2_ipc')->(), $CLASS->can('test2_ipc')->(), "always get the same IPC"); + +is_deeply([$CLASS->can('test2_ipc_drivers')->()], [qw/Test2::IPC::Driver::Files/], "Got driver list"); + +# Verify it reports to the correct file/line, there was some trouble with this... +my $file = __FILE__; +my $line = __LINE__ + 1; +my $warnings = warnings { $CLASS->can('test2_ipc_add_driver')->('fake') }; +like( + $warnings->[0], + qr{^IPC driver fake loaded too late to be used as the global ipc driver at \Q$file\E line $line}, + "got warning about adding driver too late" +); + +is_deeply([$CLASS->can('test2_ipc_drivers')->()], [qw/fake Test2::IPC::Driver::Files/], "Got updated list"); + +ok($CLASS->can('test2_ipc_polling')->(), "Polling is on"); +$CLASS->can('test2_ipc_disable_polling')->(); +ok(!$CLASS->can('test2_ipc_polling')->(), "Polling is off"); +$CLASS->can('test2_ipc_enable_polling')->(); +ok($CLASS->can('test2_ipc_polling')->(), "Polling is on"); + +ok($CLASS->can('test2_formatter')->(), "Got a formatter"); +is($CLASS->can('test2_formatter')->(), $CLASS->can('test2_formatter')->(), "always get the same Formatter (class name)"); + +my $ran = 0; +$CLASS->can('test2_add_callback_post_load')->(sub { $ran++ }); +is($ran, 1, "ran the post-load"); + +like( + exception { $CLASS->can('test2_formatter_set')->() }, + qr/No formatter specified/, + "formatter_set requires an argument" +); + +like( + exception { $CLASS->can('test2_formatter_set')->('fake') }, + qr/Global Formatter already set/, + "formatter_set doesn't work after initialization", +); + +ok(!$CLASS->can('test2_no_wait')->(), "no_wait is not set"); +$CLASS->can('test2_no_wait')->(1); +ok($CLASS->can('test2_no_wait')->(), "no_wait is set"); +$CLASS->can('test2_no_wait')->(undef); +ok(!$CLASS->can('test2_no_wait')->(), "no_wait is not set"); + +my $pctx; +sub tool_a($;$) { + Test2::API::context_do { + my $ctx = shift; + my ($bool, $name) = @_; + $pctx = wantarray; + die "xyz" unless $bool; + $ctx->ok($bool, $name); + return unless defined $pctx; + return (1, 2) if $pctx; + return 'a'; + } @_; +} + +$pctx = 'x'; +tool_a(1, "void context test"); +ok(!defined($pctx), "void context"); + +my $x = tool_a(1, "scalar context test"); +ok(defined($pctx) && $pctx == 0, "scalar context"); +is($x, 'a', "got scalar return"); + +my @x = tool_a(1, "array context test"); +ok($pctx, "array context"); +is_deeply(\@x, [1, 2], "Got array return"); + +like( + exception { tool_a(0) }, + qr/^xyz/, + "got exception" +); + +sub { + my $outer = context(); + sub { + my $middle = context(); + is($outer->trace, $middle->trace, "got the same context before calling no_context"); + + Test2::API::no_context { + my $inner = context(); + ok($inner->trace != $outer->trace, "Got a different context inside of no_context()"); + $inner->release; + }; + + $middle->release; + }->(); + + $outer->release; +}->(); + +sub { + my $outer = context(); + sub { + my $middle = context(); + is($outer->trace, $middle->trace, "got the same context before calling no_context"); + + Test2::API::no_context { + my $inner = context(); + ok($inner->trace != $outer->trace, "Got a different context inside of no_context({}, hid)"); + $inner->release; + } $outer->hub->hid; + + $middle->release; + }->(); + + $outer->release; +}->(); + +sub { + my @warnings; + my $outer = context(); + sub { + my $middle = context(); + is($outer->trace, $middle->trace, "got the same context before calling no_context"); + + local $SIG{__WARN__} = sub { push @warnings => @_ }; + Test2::API::no_context { + my $inner = context(); + ok($inner->trace != $outer->trace, "Got a different context inside of no_context({}, hid)"); + } $outer->hub->hid; + + $middle->release; + }->(); + + $outer->release; + + is(@warnings, 1, "1 warning"); + like( + $warnings[0], + qr/A context appears to have been destroyed without first calling release/, + "Got warning about unreleased context" + ); +}->(); + + +my $sub = sub { }; + +Test2::API::test2_add_callback_context_acquire($sub); +Test2::API::test2_add_callback_context_init($sub); +Test2::API::test2_add_callback_context_release($sub); +Test2::API::test2_add_callback_exit($sub); +Test2::API::test2_add_callback_post_load($sub); + +is((grep { $_ == $sub } Test2::API::test2_list_context_acquire_callbacks()), 1, "got the one instance of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_context_init_callbacks()), 1, "got the one instance of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_context_release_callbacks()), 1, "got the one instance of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_exit_callbacks()), 1, "got the one instance of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_post_load_callbacks()), 1, "got the one instance of the hook"); + +Test2::API::test2_add_callback_context_acquire($sub); +Test2::API::test2_add_callback_context_init($sub); +Test2::API::test2_add_callback_context_release($sub); +Test2::API::test2_add_callback_exit($sub); +Test2::API::test2_add_callback_post_load($sub); + +is((grep { $_ == $sub } Test2::API::test2_list_context_acquire_callbacks()), 2, "got the two instances of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_context_init_callbacks()), 2, "got the two instances of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_context_release_callbacks()), 2, "got the two instances of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_exit_callbacks()), 2, "got the two instances of the hook"); +is((grep { $_ == $sub } Test2::API::test2_list_post_load_callbacks()), 2, "got the two instances of the hook"); + +done_testing; + diff --git a/cpan/Test-Simple/t/Test2/modules/API/Breakage.t b/cpan/Test-Simple/t/Test2/modules/API/Breakage.t new file mode 100644 index 0000000000..2069c93626 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/API/Breakage.t @@ -0,0 +1,89 @@ +use strict; +use warnings; + +use Test2::IPC; +BEGIN { require "t/tools.pl" }; +use Test2::API::Breakage; +my $CLASS = 'Test2::API::Breakage'; + +for my $meth (qw/upgrade_suggested upgrade_required known_broken/) { + my @list = $CLASS->$meth; + ok(!(@list % 2), "Got even list ($meth)"); + ok(!(grep {!defined($_)} @list), "No undefined items ($meth)"); +} + +{ + no warnings 'redefine'; + local *Test2::API::Breakage::upgrade_suggested = sub { + return ('T2Test::UG1' => '1.0', 'T2Test::UG2' => '0.5'); + }; + + local *Test2::API::Breakage::upgrade_required = sub { + return ('T2Test::UR1' => '1.0', 'T2Test::UR2' => '0.5'); + }; + + local *Test2::API::Breakage::known_broken = sub { + return ('T2Test::KB1' => '1.0', 'T2Test::KB2' => '0.5'); + }; + use warnings 'redefine'; + + ok(!$CLASS->report, "Nothing to report"); + ok(!$CLASS->report(1), "Still nothing to report"); + + { + local %INC = ( + %INC, + 'T2Test/UG1.pm' => 1, + 'T2Test/UG2.pm' => 1, + 'T2Test/UR1.pm' => 1, + 'T2Test/UR2.pm' => 1, + 'T2Test/KB1.pm' => 1, + 'T2Test/KB2.pm' => 1, + ); + local $T2Test::UG1::VERSION = '0.9'; + local $T2Test::UG2::VERSION = '0.9'; + local $T2Test::UR1::VERSION = '0.9'; + local $T2Test::UR2::VERSION = '0.9'; + local $T2Test::KB1::VERSION = '0.9'; + local $T2Test::KB2::VERSION = '0.9'; + + my @report = $CLASS->report; + + is_deeply( + [sort @report], + [ + sort + " * Module 'T2Test::UG1' is outdated, we recommed updating above 1.0.", + " * Module 'T2Test::UR1' is outdated and known to be broken, please update to 1.0 or higher.", + " * Module 'T2Test::KB1' is known to be broken in version 1.0 and below, newer versions have not been tested. You have: 0.9", + " * Module 'T2Test::KB2' is known to be broken in version 0.5 and below, newer versions have not been tested. You have: 0.9", + ], + "Got expected report items" + ); + } + + my %look; + unshift @INC => sub { + my ($this, $file) = @_; + $look{$file}++ if $file =~ m{T2Test}; + return; + }; + ok(!$CLASS->report, "Nothing to report"); + is_deeply(\%look, {}, "Did not try to load anything"); + + ok(!$CLASS->report(1), "Nothing to report"); + is_deeply( + \%look, + { + 'T2Test/UG1.pm' => 1, + 'T2Test/UG2.pm' => 1, + 'T2Test/UR1.pm' => 1, + 'T2Test/UR2.pm' => 1, + 'T2Test/KB1.pm' => 1, + 'T2Test/KB2.pm' => 1, + }, + "Tried to load modules" + ); +} + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/API/Context.t b/cpan/Test-Simple/t/Test2/modules/API/Context.t new file mode 100644 index 0000000000..b4ca009f5e --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/API/Context.t @@ -0,0 +1,444 @@ +use strict; +use warnings; + +BEGIN { $Test2::API::DO_DEPTH_CHECK = 1 } +BEGIN { require "t/tools.pl" }; + +use Test2::API qw{ + context intercept + test2_stack + test2_add_callback_context_acquire + test2_add_callback_context_init + test2_add_callback_context_release +}; + +my $error = exception { context(); 1 }; +my $exception = "context() called, but return value is ignored at " . __FILE__ . ' line ' . (__LINE__ - 1); +like($error, qr/^\Q$exception\E/, "Got the exception" ); + +my $ref; +my $frame; +sub wrap(&) { + my $ctx = context(); + my ($pkg, $file, $line, $sub) = caller(0); + $frame = [$pkg, $file, $line, $sub]; + + $_[0]->($ctx); + + $ref = "$ctx"; + + $ctx->release; +} + +wrap { + my $ctx = shift; + ok($ctx->hub, "got hub"); + delete $ctx->trace->frame->[4]; + is_deeply($ctx->trace->frame, $frame, "Found place to report errors"); +}; + +wrap { + my $ctx = shift; + ok("$ctx" ne "$ref", "Got a new context"); + my $new = context(); + my @caller = caller(0); + is_deeply( + $new, + {%$ctx, _is_canon => undef, _is_spawn => [@caller[0,1,2,3]]}, + "Additional call to context gets spawn" + ); + delete $ctx->trace->frame->[4]; + is_deeply($ctx->trace->frame, $frame, "Found place to report errors"); + $new->release; +}; + +wrap { + my $ctx = shift; + my $snap = $ctx->snapshot; + + is_deeply( + $snap, + {%$ctx, _is_canon => undef, _is_spawn => undef, _aborted => undef}, + "snapshot is identical except for canon/spawn/aborted" + ); + ok($ctx != $snap, "snapshot is a new instance"); +}; + +my $end_ctx; +{ # Simulate an END block... + local *END = sub { local *__ANON__ = 'END'; context() }; + my $ctx = END(); $frame = [ __PACKAGE__, __FILE__, __LINE__, 'main::END' ]; + $end_ctx = $ctx->snapshot; + $ctx->release; +} +delete $end_ctx->trace->frame->[4]; +is_deeply( $end_ctx->trace->frame, $frame, 'context is ok in an end block'); + +# Test event generation +{ + package My::Formatter; + + sub write { + my $self = shift; + my ($e) = @_; + push @$self => $e; + } +} +my $events = bless [], 'My::Formatter'; +my $hub = Test2::Hub->new( + formatter => $events, +); +my $trace = Test2::Util::Trace->new( + frame => [ 'Foo::Bar', 'foo_bar.t', 42, 'Foo::Bar::baz' ], +); +my $ctx = Test2::API::Context->new( + trace => $trace, + hub => $hub, +); + +my $e = $ctx->build_event('Ok', pass => 1, name => 'foo'); +is($e->pass, 1, "Pass"); +is($e->name, 'foo', "got name"); +is_deeply($e->trace, $trace, "Got the trace info"); +ok(!@$events, "No events yet"); + +$e = $ctx->send_event('Ok', pass => 1, name => 'foo'); +is($e->pass, 1, "Pass"); +is($e->name, 'foo', "got name"); +is_deeply($e->trace, $trace, "Got the trace info"); +is(@$events, 1, "1 event"); +is_deeply($events, [$e], "Hub saw the event"); +pop @$events; + +$e = $ctx->ok(1, 'foo'); +is($e->pass, 1, "Pass"); +is($e->name, 'foo', "got name"); +is_deeply($e->trace, $trace, "Got the trace info"); +is(@$events, 1, "1 event"); +is_deeply($events, [$e], "Hub saw the event"); +pop @$events; + +$e = $ctx->note('foo'); +is($e->message, 'foo', "got message"); +is_deeply($e->trace, $trace, "Got the trace info"); +is(@$events, 1, "1 event"); +is_deeply($events, [$e], "Hub saw the event"); +pop @$events; + +$e = $ctx->diag('foo'); +is($e->message, 'foo', "got message"); +is_deeply($e->trace, $trace, "Got the trace info"); +is(@$events, 1, "1 event"); +is_deeply($events, [$e], "Hub saw the event"); +pop @$events; + +$e = $ctx->plan(100); +is($e->max, 100, "got max"); +is_deeply($e->trace, $trace, "Got the trace info"); +is(@$events, 1, "1 event"); +is_deeply($events, [$e], "Hub saw the event"); +pop @$events; + +$e = $ctx->skip('foo', 'because'); +is($e->name, 'foo', "got name"); +is($e->reason, 'because', "got reason"); +ok($e->pass, "skip events pass by default"); +is_deeply($e->trace, $trace, "Got the trace info"); +is(@$events, 1, "1 event"); +is_deeply($events, [$e], "Hub saw the event"); +pop @$events; + +$e = $ctx->skip('foo', 'because', pass => 0); +ok(!$e->pass, "can override skip params"); +pop @$events; + +# Test hooks + +my @hooks; +$hub = test2_stack()->top; +my $ref1 = $hub->add_context_init(sub { die "Bad Arg" unless ref($_[0]) eq 'Test2::API::Context'; push @hooks => 'hub_init' }); +my $ref2 = $hub->add_context_release(sub { die "Bad Arg" unless ref($_[0]) eq 'Test2::API::Context'; push @hooks => 'hub_release' }); +test2_add_callback_context_init(sub { die "Bad Arg" unless ref($_[0]) eq 'Test2::API::Context'; push @hooks => 'global_init' }); +test2_add_callback_context_release(sub { die "Bad Arg" unless ref($_[0]) eq 'Test2::API::Context'; push @hooks => 'global_release' }); + +my $ref3 = $hub->add_context_acquire(sub { die "Bad Arg" unless ref($_[0]) eq 'HASH'; push @hooks => 'hub_acquire' }); +test2_add_callback_context_acquire(sub { die "Bad Arg" unless ref($_[0]) eq 'HASH'; push @hooks => 'global_acquire' }); + +sub { + push @hooks => 'start'; + my $ctx = context(on_init => sub { push @hooks => 'ctx_init' }, on_release => sub { push @hooks => 'ctx_release' }); + push @hooks => 'deep'; + my $ctx2 = sub { + context(on_init => sub { push @hooks => 'ctx_init_deep' }, on_release => sub { push @hooks => 'ctx_release_deep' }); + }->(); + push @hooks => 'release_deep'; + $ctx2->release; + push @hooks => 'release_parent'; + $ctx->release; + push @hooks => 'released_all'; + + push @hooks => 'new'; + $ctx = context(on_init => sub { push @hooks => 'ctx_init2' }, on_release => sub { push @hooks => 'ctx_release2' }); + push @hooks => 'release_new'; + $ctx->release; + push @hooks => 'done'; +}->(); + +$hub->remove_context_init($ref1); +$hub->remove_context_release($ref2); +$hub->remove_context_acquire($ref3); +@{Test2::API::_context_init_callbacks_ref()} = (); +@{Test2::API::_context_release_callbacks_ref()} = (); +@{Test2::API::_context_acquire_callbacks_ref()} = (); + +is_deeply( + \@hooks, + [qw{ + start + global_acquire + hub_acquire + global_init + hub_init + ctx_init + deep + global_acquire + hub_acquire + release_deep + release_parent + ctx_release_deep + ctx_release + hub_release + global_release + released_all + new + global_acquire + hub_acquire + global_init + hub_init + ctx_init2 + release_new + ctx_release2 + hub_release + global_release + done + }], + "Got all hook in correct order" +); + +{ + my $ctx = context(level => -1); + + my $one = Test2::API::Context->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__, 'blah']), + hub => test2_stack()->top, + ); + is($one->_depth, 0, "default depth"); + + my $ran = 0; + my $doit = sub { + is_deeply(\@_, [qw/foo bar/], "got args"); + $ran++; + die "Make sure old context is restored"; + }; + + eval { $one->do_in_context($doit, 'foo', 'bar') }; + + my $spawn = context(level => -1, wrapped => -2); + is($spawn->trace, $ctx->trace, "Old context restored"); + $spawn->release; + $ctx->release; + + ok(!exception { $one->do_in_context(sub {1}) }, "do_in_context works without an original") +} + +{ + like(exception { Test2::API::Context->new() }, qr/The 'trace' attribute is required/, "need to have trace"); + + my $trace = Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__, 'foo']); + like(exception { Test2::API::Context->new(trace => $trace) }, qr/The 'hub' attribute is required/, "need to have hub"); + + my $hub = test2_stack()->top; + my $ctx = Test2::API::Context->new(trace => $trace, hub => $hub); + is($ctx->{_depth}, 0, "depth set to 0 when not defined."); + + $ctx = Test2::API::Context->new(trace => $trace, hub => $hub, _depth => 1); + is($ctx->{_depth}, 1, "Do not reset depth"); + + like( + exception { $ctx->release }, + qr/release\(\) should not be called on context that is neither canon nor a child/, + "Non canonical context, do not release" + ); +} + +sub { + like( + exception { my $ctx = context(level => 20) }, + qr/Could not find context at depth 21/, + "Level sanity" + ); + + ok( + !exception { + my $ctx = context(level => 20, fudge => 1); + $ctx->release; + }, + "Was able to get context when fudging level" + ); +}->(); + +sub { + my ($ctx1, $ctx2); + sub { $ctx1 = context() }->(); + + my @warnings; + { + local $SIG{__WARN__} = sub { push @warnings => @_ }; + $ctx2 = context(); + $ctx1 = undef; + } + + $ctx2->release; + + is(@warnings, 1, "1 warning"); + like( + $warnings[0], + qr/^context\(\) was called to retrieve an existing context, however the existing/, + "Got expected warning" + ); +}->(); + +sub { + my $ctx = context(); + my $e = exception { $ctx->throw('xxx') }; + like($e, qr/xxx/, "got exception"); + + $ctx = context(); + my $warnings = warnings { $ctx->alert('xxx') }; + like($warnings->[0], qr/xxx/, "got warning"); + $ctx->release; +}->(); + +sub { + my $ctx = context; + + is($ctx->_parse_event('Ok'), 'Test2::Event::Ok', "Got the Ok event class"); + is($ctx->_parse_event('+Test2::Event::Ok'), 'Test2::Event::Ok', "Got the +Ok event class"); + + like( + exception { $ctx->_parse_event('+DFASGFSDFGSDGSD') }, + qr/Could not load event module 'DFASGFSDFGSDGSD': Can't locate DFASGFSDFGSDGSD\.pm/, + "Bad event type" + ); +}->(); + +{ + my ($e1, $e2); + my $events = intercept { + my $ctx = context(); + $e1 = $ctx->ok(0, 'foo', ['xxx']); + $e2 = $ctx->ok(0, 'foo'); + $ctx->release; + }; + + ok($e1->isa('Test2::Event::Ok'), "returned ok event"); + ok($e2->isa('Test2::Event::Ok'), "returned ok event"); + + is($events->[0], $e1, "got ok event 1"); + is($events->[3], $e2, "got ok event 2"); + + is($events->[2]->message, 'xxx', "event 1 diag 2"); +} + +sub { + local $! = 100; + local $@ = 'foobarbaz'; + local $? = 123; + + my $ctx = context(); + + is($ctx->errno, 100, "saved errno"); + is($ctx->eval_error, 'foobarbaz', "saved eval error"); + is($ctx->child_error, 123, "saved child exit"); + + $! = 22; + $@ = 'xyz'; + $? = 33; + + is(0 + $!, 22, "altered \$! in tool"); + is($@, 'xyz', "altered \$@ in tool"); + is($?, 33, "altered \$? in tool"); + + sub { + my $ctx2 = context(); + + $! = 42; + $@ = 'app'; + $? = 43; + + is(0 + $!, 42, "altered \$! in tool (nested)"); + is($@, 'app', "altered \$@ in tool (nested)"); + is($?, 43, "altered \$? in tool (nested)"); + + $ctx2->release; + + is(0 + $!, 22, "restored the nested \$! in tool"); + is($@, 'xyz', "restored the nested \$@ in tool"); + is($?, 33, "restored the nested \$? in tool"); + }->(); + + sub { + my $ctx2 = context(); + + $! = 42; + $@ = 'app'; + $? = 43; + + is(0 + $!, 42, "altered \$! in tool (nested)"); + is($@, 'app', "altered \$@ in tool (nested)"); + is($?, 43, "altered \$? in tool (nested)"); + + # Will not warn since $@ is changed + $ctx2 = undef; + + is(0 + $!, 42, 'Destroy does not reset $!'); + is($@, 'app', 'Destroy does not reset $@'); + is($?, 43, 'Destroy does not reset $?'); + }->(); + + $ctx->release; + + is($ctx->errno, 100, "restored errno"); + is($ctx->eval_error, 'foobarbaz', "restored eval error"); + is($ctx->child_error, 123, "restored child exit"); +}->(); + + +sub { + local $! = 100; + local $@ = 'foobarbaz'; + local $? = 123; + + my $ctx = context(); + + is($ctx->errno, 100, "saved errno"); + is($ctx->eval_error, 'foobarbaz', "saved eval error"); + is($ctx->child_error, 123, "saved child exit"); + + $! = 22; + $@ = 'xyz'; + $? = 33; + + is(0 + $!, 22, "altered \$! in tool"); + is($@, 'xyz', "altered \$@ in tool"); + is($?, 33, "altered \$? in tool"); + + # Will not warn since $@ is changed + $ctx = undef; + + is(0 + $!, 22, "Destroy does not restore \$!"); + is($@, 'xyz', "Destroy does not restore \$@"); + is($?, 33, "Destroy does not restore \$?"); +}->(); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/API/Instance.t b/cpan/Test-Simple/t/Test2/modules/API/Instance.t new file mode 100644 index 0000000000..45e739fe6e --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/API/Instance.t @@ -0,0 +1,466 @@ +use strict; +use warnings; + +use Test2::IPC; +BEGIN { require "t/tools.pl" }; +use Test2::Util qw/CAN_THREAD CAN_REALLY_FORK USE_THREADS get_tid/; + +my $CLASS = 'Test2::API::Instance'; + +my $one = $CLASS->new; +is_deeply( + $one, + { + pid => $$, + tid => get_tid(), + contexts => {}, + + finalized => undef, + ipc => undef, + formatter => undef, + + ipc_polling => undef, + ipc_drivers => [], + + formatters => [], + + no_wait => 0, + loaded => 0, + + exit_callbacks => [], + post_load_callbacks => [], + context_acquire_callbacks => [], + context_init_callbacks => [], + context_release_callbacks => [], + + stack => [], + }, + "Got initial settings" +); + +%$one = (); +is_deeply($one, {}, "wiped object"); + +$one->reset; +is_deeply( + $one, + { + pid => $$, + tid => get_tid(), + contexts => {}, + + ipc_polling => undef, + ipc_drivers => [], + + formatters => [], + + finalized => undef, + ipc => undef, + formatter => undef, + + no_wait => 0, + loaded => 0, + + exit_callbacks => [], + post_load_callbacks => [], + context_acquire_callbacks => [], + context_init_callbacks => [], + context_release_callbacks => [], + + stack => [], + }, + "Reset Object" +); + +ok(!$one->formatter_set, "no formatter set"); +$one->set_formatter('Foo'); +ok($one->formatter_set, "formatter set"); +$one->reset; + +my $ran = 0; +my $callback = sub { $ran++ }; +$one->add_post_load_callback($callback); +ok(!$ran, "did not run yet"); +is_deeply($one->post_load_callbacks, [$callback], "stored callback for later"); + +ok(!$one->loaded, "not loaded"); +$one->load; +ok($one->loaded, "loaded"); +is($ran, 1, "ran the callback"); + +$one->load; +is($ran, 1, "Did not run the callback again"); + +$one->add_post_load_callback($callback); +is($ran, 2, "ran the new callback"); +is_deeply($one->post_load_callbacks, [$callback, $callback], "stored callback for the record"); + +like( + exception { $one->add_post_load_callback({}) }, + qr/Post-load callbacks must be coderefs/, + "Post-load callbacks must be coderefs" +); + +$one->reset; +ok($one->ipc, 'got ipc'); +ok($one->finalized, "calling ipc finalized the object"); + +$one->reset; +ok($one->stack, 'got stack'); +ok(!$one->finalized, "calling stack did not finaliz the object"); + +$one->reset; +ok($one->formatter, 'Got formatter'); +ok($one->finalized, "calling format finalized the object"); + +$one->reset; +$one->set_formatter('Foo'); +is($one->formatter, 'Foo', "got specified formatter"); +ok($one->finalized, "calling format finalized the object"); + +{ + local $ENV{T2_FORMATTER} = 'TAP'; + $one->reset; + is($one->formatter, 'Test2::Formatter::TAP', "got specified formatter"); + ok($one->finalized, "calling format finalized the object"); + + local $ENV{T2_FORMATTER} = '+Test2::Formatter::TAP'; + $one->reset; + is($one->formatter, 'Test2::Formatter::TAP', "got specified formatter"); + ok($one->finalized, "calling format finalized the object"); + + local $ENV{T2_FORMATTER} = '+Fake'; + $one->reset; + like( + exception { $one->formatter }, + qr/COULD NOT LOAD FORMATTER 'Fake' \(set by the 'T2_FORMATTER' environment variable\)/, + "Bad formatter" + ); +} + +$ran = 0; +$one->reset; +$one->add_exit_callback($callback); +is(@{$one->exit_callbacks}, 1, "added an exit callback"); +$one->add_exit_callback($callback); +is(@{$one->exit_callbacks}, 2, "added another exit callback"); + +like( + exception { $one->add_exit_callback({}) }, + qr/End callbacks must be coderefs/, + "Exit callbacks must be coderefs" +); + +if (CAN_REALLY_FORK) { + $one->reset; + my $pid = fork; + die "Failed to fork!" unless defined $pid; + unless($pid) { exit 0 } + + is($one->_ipc_wait, 0, "No errors"); + + $pid = fork; + die "Failed to fork!" unless defined $pid; + unless($pid) { exit 255 } + my @warnings; + { + local $SIG{__WARN__} = sub { push @warnings => @_ }; + is($one->_ipc_wait, 255, "Process exited badly"); + } + like($warnings[0], qr/Process .* did not exit cleanly \(status: 255\)/, "Warn about exit"); +} + +if (CAN_THREAD && $] ge '5.010') { + require threads; + $one->reset; + + threads->new(sub { 1 }); + is($one->_ipc_wait, 0, "No errors"); + + if (threads->can('error')) { + threads->new(sub { + close(STDERR); + close(STDOUT); + die "xxx" + }); + my @warnings; + { + local $SIG{__WARN__} = sub { push @warnings => @_ }; + is($one->_ipc_wait, 255, "Thread exited badly"); + } + like($warnings[0], qr/Thread .* did not end cleanly: xxx/, "Warn about exit"); + } +} + +{ + $one->reset(); + local $? = 0; + $one->set_exit; + is($?, 0, "no errors on exit"); +} + +{ + $one->reset(); + $one->set_tid(1); + local $? = 0; + $one->set_exit; + is($?, 0, "no errors on exit"); +} + +{ + $one->reset(); + $one->stack->top; + $one->no_wait(1); + local $? = 0; + $one->set_exit; + is($?, 0, "no errors on exit"); +} + +{ + $one->reset(); + $one->stack->top->set_no_ending(1); + local $? = 0; + $one->set_exit; + is($?, 0, "no errors on exit"); +} + +{ + $one->reset(); + $one->stack->top->set_failed(2); + local $? = 0; + $one->set_exit; + is($?, 2, "number of failures"); +} + +{ + $one->reset(); + local $? = 500; + $one->set_exit; + is($?, 255, "set exit code to a sane number"); +} + +{ + local %INC = %INC; + delete $INC{'Test2/IPC.pm'}; + $one->reset(); + my @events; + $one->stack->top->filter(sub { push @events => $_[1]; undef}); + $one->stack->new_hub; + local $? = 0; + $one->set_exit; + is($?, 255, "errors on exit"); + like($events[0]->message, qr/Test ended with extra hubs on the stack!/, "got diag"); +} + +{ + $one->reset; + my $stderr = ""; + { + local $INC{'Test/Builder.pm'} = __FILE__; + local $Test2::API::VERSION = '0.002'; + local $Test::Builder::VERSION = '0.001'; + local *STDERR; + open(STDERR, '>', \$stderr) or print "Failed to open new STDERR"; + + $one->set_exit; + } + + is($stderr, <<' EOT', "Got warning about version mismatch"); + +******************************************************************************** +* * +* Test::Builder -- Test2::API version mismatch detected * +* * +******************************************************************************** + Test2::API Version: 0.002 +Test::Builder Version: 0.001 + +This is not a supported configuration, you will have problems. + + EOT +} + +{ + require Test2::API::Breakage; + no warnings qw/redefine once/; + my $ran = 0; + local *Test2::API::Breakage::report = sub { $ran++; return "foo" }; + use warnings qw/redefine once/; + $one->reset(); + + my $stderr = ""; + { + local *STDERR; + open(STDERR, '>', \$stderr) or print "Failed to open new STDERR"; + local $? = 255; + $one->set_exit; + } + + is($stderr, <<" EOT", "Reported bad modules"); + +You have loaded versions of test modules known to have problems with Test2. +This could explain some test failures. +foo + + EOT +} + + +{ + $one->reset(); + my @events; + $one->stack->top->filter(sub { push @events => $_[1]; undef}); + $one->stack->new_hub; + ok($one->stack->top->ipc, "Have IPC"); + $one->stack->new_hub; + ok($one->stack->top->ipc, "Have IPC"); + $one->stack->top->set_ipc(undef); + ok(!$one->stack->top->ipc, "no IPC"); + $one->stack->new_hub; + local $? = 0; + $one->set_exit; + is($?, 255, "errors on exit"); + like($events[0]->message, qr/Test ended with extra hubs on the stack!/, "got diag"); +} + +if (CAN_REALLY_FORK) { + local $SIG{__WARN__} = sub { }; + $one->reset(); + my $pid = fork; + die "Failed to fork!" unless defined $pid; + unless ($pid) { exit 255 } + $one->_finalize; + $one->stack->top; + + local $? = 0; + $one->set_exit; + is($?, 255, "errors on exit"); + + $one->reset(); + $pid = fork; + die "Failed to fork!" unless defined $pid; + unless ($pid) { exit 255 } + $one->_finalize; + $one->stack->top; + + local $? = 122; + $one->set_exit; + is($?, 122, "kept original exit"); +} + +{ + my $ctx = bless { + trace => Test2::Util::Trace->new(frame => ['Foo::Bar', 'Foo/Bar.pm', 42, 'xxx']), + hub => Test2::Hub->new(), + }, 'Test2::API::Context'; + $one->contexts->{1234} = $ctx; + + local $? = 500; + my $warnings = warnings { $one->set_exit }; + is($?, 255, "set exit code to a sane number"); + + is_deeply( + $warnings, + [ + "context object was never released! This means a testing tool is behaving very badly at Foo/Bar.pm line 42.\n" + ], + "Warned about unfreed context" + ); +} + +{ + local %INC = %INC; + delete $INC{'Test2/IPC.pm'}; + delete $INC{'threads.pm'}; + ok(!USE_THREADS, "Sanity Check"); + + $one->reset; + ok(!$one->ipc, 'IPC not loaded, no IPC object'); + ok($one->finalized, "calling ipc finalized the object"); + is($one->ipc_polling, undef, "no polling defined"); + ok(!@{$one->ipc_drivers}, "no driver"); + + if (CAN_THREAD) { + local $INC{'threads.pm'} = 1; + no warnings 'once'; + local *threads::tid = sub { 0 } unless threads->can('tid'); + $one->reset; + ok($one->ipc, 'IPC loaded if threads are'); + ok($one->finalized, "calling ipc finalized the object"); + ok($one->ipc_polling, "polling on by default"); + is($one->ipc_drivers->[0], 'Test2::IPC::Driver::Files', "default driver"); + } + + { + local $INC{'Test2/IPC.pm'} = 1; + $one->reset; + ok($one->ipc, 'IPC loaded if Test2::IPC is'); + ok($one->finalized, "calling ipc finalized the object"); + ok($one->ipc_polling, "polling on by default"); + is($one->ipc_drivers->[0], 'Test2::IPC::Driver::Files', "default driver"); + } + + require Test2::IPC::Driver::Files; + $one->reset; + $one->add_ipc_driver('Test2::IPC::Driver::Files'); + ok($one->ipc, 'IPC loaded if drivers have been added'); + ok($one->finalized, "calling ipc finalized the object"); + ok($one->ipc_polling, "polling on by default"); + + my $file = __FILE__; + my $line = __LINE__ + 1; + my $warnings = warnings { $one->add_ipc_driver('Test2::IPC::Driver::Files') }; + like( + $warnings->[0], + qr{^IPC driver Test2::IPC::Driver::Files loaded too late to be used as the global ipc driver at \Q$file\E line $line}, + "Got warning at correct frame" + ); + + $one->reset; + $one->add_ipc_driver('Fake::Fake::XXX'); + is( + exception { $one->ipc }, + "IPC has been requested, but no viable drivers were found. Aborting...\n", + "Failed without viable IPC driver" + ); +} + +{ + $one->reset; + ok(!@{$one->context_init_callbacks}, "no callbacks"); + is($one->ipc_polling, undef, "no polling, undef"); + + $one->disable_ipc_polling; + ok(!@{$one->context_init_callbacks}, "no callbacks"); + is($one->ipc_polling, undef, "no polling, still undef"); + + my $cull = 0; + no warnings 'once'; + local *Fake::Hub::cull = sub { $cull++ }; + use warnings; + + $one->enable_ipc_polling; + is(@{$one->context_init_callbacks}, 1, "added the callback"); + is($one->ipc_polling, 1, "polling on"); + $one->set_ipc_shm_last('abc1'); + $one->context_init_callbacks->[0]->({'hub' => 'Fake::Hub'}); + is($cull, 1, "called cull once"); + $cull = 0; + + $one->disable_ipc_polling; + is(@{$one->context_init_callbacks}, 1, "kept the callback"); + is($one->ipc_polling, 0, "no polling, set to 0"); + $one->set_ipc_shm_last('abc3'); + $one->context_init_callbacks->[0]->({'hub' => 'Fake::Hub'}); + is($cull, 0, "did not call cull"); + $cull = 0; + + $one->enable_ipc_polling; + is(@{$one->context_init_callbacks}, 1, "did not add the callback"); + is($one->ipc_polling, 1, "polling on"); + $one->set_ipc_shm_last('abc3'); + $one->context_init_callbacks->[0]->({'hub' => 'Fake::Hub'}); + is($cull, 1, "called cull once"); +} + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/API/Stack.t b/cpan/Test-Simple/t/Test2/modules/API/Stack.t new file mode 100644 index 0000000000..731022e8b0 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/API/Stack.t @@ -0,0 +1,79 @@ +use strict; +use warnings; +use Test2::IPC; +BEGIN { require "t/tools.pl" }; +use Test2::API::Stack; +use Test2::API qw/test2_ipc/; + +ok(my $stack = Test2::API::Stack->new, "Create a stack"); + +ok(!@$stack, "Empty stack"); +ok(!$stack->peek, "Nothing to peek at"); + +ok(!exception { $stack->cull }, "cull lives when stack is empty"); +ok(!exception { $stack->all }, "all lives when stack is empty"); +ok(!exception { $stack->clear }, "clear lives when stack is empty"); + +like( + exception { $stack->pop(Test2::Hub->new) }, + qr/No hubs on the stack/, + "No hub to pop" +); + +my $hub = Test2::Hub->new; +ok($stack->push($hub), "pushed a hub"); + +like( + exception { $stack->pop($hub) }, + qr/You cannot pop the root hub/, + "Root hub cannot be popped" +); + +$stack->push($hub); +like( + exception { $stack->pop(Test2::Hub->new) }, + qr/Hub stack mismatch, attempted to pop incorrect hub/, + "Must specify correct hub to pop" +); + +is_deeply( + [ $stack->all ], + [ $hub, $hub ], + "Got all hubs" +); + +ok(!exception { $stack->pop($hub) }, "Popped the correct hub"); + +is_deeply( + [ $stack->all ], + [ $hub ], + "Got all hubs" +); + +is($stack->peek, $hub, "got the hub"); +is($stack->top, $hub, "got the hub"); + +$stack->clear; + +is_deeply( + [ $stack->all ], + [ ], + "no hubs" +); + +ok(my $top = $stack->top, "Generated a top hub"); +is($top->ipc, test2_ipc, "Used sync's ipc"); +ok($top->format, 'Got formatter'); + +is($stack->top, $stack->top, "do not generate a new top if there is already a top"); + +ok(my $new = $stack->new_hub(), "Add a new hub"); +is($stack->top, $new, "new one is on top"); +is($new->ipc, $top->ipc, "inherited ipc"); +is($new->format, $top->format, "inherited formatter"); + +my $new2 = $stack->new_hub(formatter => undef, ipc => undef); +ok(!$new2->ipc, "built with no ipc"); +ok(!$new2->format, "built with no formatter"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event.t b/cpan/Test-Simple/t/Test2/modules/Event.t new file mode 100644 index 0000000000..323972aa27 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event.t @@ -0,0 +1,40 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; + +use Test2::Event(); + +{ + package My::MockEvent; + + use base 'Test2::Event'; + use Test2::Util::HashBase qw/foo bar baz/; +} + +ok(My::MockEvent->can($_), "Added $_ accessor") for qw/foo bar baz/; + +my $one = My::MockEvent->new(trace => 'fake'); + +ok(!$one->causes_fail, "Events do not cause failures by default"); + +ok(!$one->$_, "$_ is false by default") for qw/increments_count terminate global/; + +ok(!$one->get_meta('xxx'), "no meta-data associated for key 'xxx'"); + +$one->set_meta('xxx', '123'); + +is($one->meta('xxx'), '123', "got meta-data"); + +is($one->meta('xxx', '321'), '123', "did not use default"); + +is($one->meta('yyy', '1221'), '1221', "got the default"); + +is($one->meta('yyy'), '1221', "last call set the value to the default for future use"); + +is($one->summary, 'My::MockEvent', "Default summary is event package"); + +is($one->diagnostics, 0, "Not diagnostics by default"); + +ok(!$one->in_subtest, "no subtest_id by default"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Bail.t b/cpan/Test-Simple/t/Test2/modules/Event/Bail.t new file mode 100644 index 0000000000..0d69f43f6f --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Bail.t @@ -0,0 +1,29 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; +use Test2::Event::Bail; + +my $bail = Test2::Event::Bail->new( + trace => 'fake', + reason => 'evil', +); + +ok($bail->causes_fail, "bailout always causes fail."); + +is($bail->terminate, 255, "Bail will cause the test to exit."); +is($bail->global, 1, "Bail is global, everything should bail"); + +my $hub = Test2::Hub->new; +ok($hub->is_passing, "passing"); +ok(!$hub->failed, "no failures"); + +$bail->callback($hub); +is($hub->bailed_out, $bail, "set bailed out"); + +is($bail->summary, "Bail out! evil", "Summary includes reason"); +$bail->set_reason(""); +is($bail->summary, "Bail out!", "Summary has no reason"); + +ok($bail->diagnostics, "Bail events are counted as diagnostics"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Diag.t b/cpan/Test-Simple/t/Test2/modules/Event/Diag.t new file mode 100644 index 0000000000..eb38554988 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Diag.t @@ -0,0 +1,31 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; +use Test2::Event::Diag; +use Test2::Util::Trace; + +my $diag = Test2::Event::Diag->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + message => 'foo', +); + +is($diag->summary, 'foo', "summary is just message"); + +$diag = Test2::Event::Diag->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + message => undef, +); + +is($diag->message, 'undef', "set undef message to undef"); +is($diag->summary, 'undef', "summary is just message even when undef"); + +$diag = Test2::Event::Diag->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + message => {}, +); + +like($diag->message, qr/^HASH\(.*\)$/, "stringified the input value"); + +ok($diag->diagnostics, "Diag events are counted as diagnostics"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Exception.t b/cpan/Test-Simple/t/Test2/modules/Event/Exception.t new file mode 100644 index 0000000000..d9082ad795 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Exception.t @@ -0,0 +1,17 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; +use Test2::Event::Exception; + +my $exception = Test2::Event::Exception->new( + trace => 'fake', + error => "evil at lake_of_fire.t line 6\n", +); + +ok($exception->causes_fail, "Exception events always cause failure"); + +is($exception->summary, "Exception: evil at lake_of_fire.t line 6", "Got summary"); + +ok($exception->diagnostics, "Exception events are counted as diagnostics"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Note.t b/cpan/Test-Simple/t/Test2/modules/Event/Note.t new file mode 100644 index 0000000000..cac7da9e7f --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Note.t @@ -0,0 +1,30 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; +use Test2::Event::Note; +use Test2::Util::Trace; + +my $note = Test2::Event::Note->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + message => 'foo', +); + +is($note->summary, 'foo', "summary is just message"); + +$note = Test2::Event::Note->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + message => undef, +); + +is($note->message, 'undef', "set undef message to undef"); +is($note->summary, 'undef', "summary is just message even when undef"); + +$note = Test2::Event::Note->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + message => {}, +); + +like($note->message, qr/^HASH\(.*\)$/, "stringified the input value"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Ok.t b/cpan/Test-Simple/t/Test2/modules/Event/Ok.t new file mode 100644 index 0000000000..ea709f5b85 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Ok.t @@ -0,0 +1,102 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; +use Test2::Util::Trace; +use Test2::Event::Ok; +use Test2::Event::Diag; + +use Test2::API qw/context/; + +my $trace; +sub before_each { + # Make sure there is a fresh trace object for each group + $trace = Test2::Util::Trace->new( + frame => ['main_foo', 'foo.t', 42, 'main_foo::flubnarb'], + ); +} + +tests Passing => sub { + my $ok = Test2::Event::Ok->new( + trace => $trace, + pass => 1, + name => 'the_test', + ); + ok($ok->increments_count, "Bumps the count"); + ok(!$ok->causes_fail, "Passing 'OK' event does not cause failure"); + is($ok->pass, 1, "got pass"); + is($ok->name, 'the_test', "got name"); + is($ok->effective_pass, 1, "effective pass"); + is($ok->summary, "the_test", "Summary is just the name of the test"); + + $ok = Test2::Event::Ok->new( + trace => $trace, + pass => 1, + name => '', + ); + is($ok->summary, "Nameless Assertion", "Nameless test"); + +}; + +tests Failing => sub { + local $ENV{HARNESS_ACTIVE} = 1; + local $ENV{HARNESS_IS_VERBOSE} = 1; + my $ok = Test2::Event::Ok->new( + trace => $trace, + pass => 0, + name => 'the_test', + ); + ok($ok->increments_count, "Bumps the count"); + ok($ok->causes_fail, "A failing test causes failures"); + is($ok->pass, 0, "got pass"); + is($ok->name, 'the_test', "got name"); + is($ok->effective_pass, 0, "effective pass"); + is($ok->summary, "the_test", "Summary is just the name of the test"); +}; + +tests "Failing TODO" => sub { + local $ENV{HARNESS_ACTIVE} = 1; + local $ENV{HARNESS_IS_VERBOSE} = 1; + my $ok = Test2::Event::Ok->new( + trace => $trace, + pass => 0, + name => 'the_test', + todo => 'A Todo', + ); + ok($ok->increments_count, "Bumps the count"); + is($ok->pass, 0, "got pass"); + is($ok->name, 'the_test', "got name"); + is($ok->effective_pass, 1, "effective pass is true from todo"); + is($ok->summary, "the_test (TODO: A Todo)", "Summary is just the name of the test + todo"); + + $ok = Test2::Event::Ok->new( + trace => $trace, + pass => 0, + name => 'the_test2', + todo => '', + ); + ok($ok->effective_pass, "empty string todo is still a todo"); + is($ok->summary, "the_test2 (TODO)", "Summary is just the name of the test + todo"); +}; + +tests init => sub { + like( + exception { Test2::Event::Ok->new(trace => $trace, pass => 1, name => "foo#foo") }, + qr/'foo#foo' is not a valid name, names must not contain '#' or newlines/, + "Some characters do not belong in a name" + ); + + like( + exception { Test2::Event::Ok->new(trace => $trace, pass => 1, name => "foo\nfoo") }, + qr/'foo\nfoo' is not a valid name, names must not contain '#' or newlines/, + "Some characters do not belong in a name" + ); + + my $ok = Test2::Event::Ok->new( + trace => $trace, + pass => 1, + ); + is($ok->effective_pass, 1, "set effective pass"); +}; + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Plan.t b/cpan/Test-Simple/t/Test2/modules/Event/Plan.t new file mode 100644 index 0000000000..33a5950773 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Plan.t @@ -0,0 +1,107 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; +use Test2::Event::Plan; +use Test2::Util::Trace; + +my $plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + max => 100, +); + +is($plan->summary, "Plan is 100 assertions", "simple summary"); +is_deeply( [$plan->sets_plan], [100, '', undef], "Got plan details"); + +ok(!$plan->global, "regular plan is not a global event"); +my $state = Test2::Hub->new; +$plan->callback($state); +is($state->plan, 100, "set plan in state"); +is($plan->terminate, undef, "No terminate for normal plan"); + +$plan->set_max(0); +$plan->set_directive('SKIP'); +$plan->set_reason('foo'); +$state = Test2::Hub->new; +$plan->callback($state); +is($state->plan, 'SKIP', "set plan in state"); +is($plan->terminate, 0, "Terminate 0 on skip_all"); + +is($plan->summary, "Plan is 'SKIP', foo", "skip summary"); +is_deeply( [$plan->sets_plan], [0, 'SKIP', 'foo'], "Got skip details"); + +$plan->set_max(0); +$plan->set_directive('NO PLAN'); +$plan->set_reason(undef); +is($plan->summary, "Plan is 'NO PLAN'", "NO PLAN summary"); +is_deeply( [$plan->sets_plan], [0, 'NO PLAN', undef], "Got 'NO PLAN' details"); +$state = Test2::Hub->new; +$plan->callback($state); +is($state->plan, 'NO PLAN', "set plan in state"); +is($plan->terminate, undef, "No terminate for no_plan"); +$plan->set_max(100); +$plan->set_directive(undef); +$plan->callback($state); +is($state->plan, '100', "Update plan in state if it is 'NO PLAN'"); + +$plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + max => 0, + directive => 'skip_all', +); +is($plan->directive, 'SKIP', "Change skip_all to SKIP"); + +$plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + max => 0, + directive => 'no_plan', +); +is($plan->directive, 'NO PLAN', "Change no_plan to 'NO PLAN'"); +ok(!$plan->global, "NO PLAN is not global"); + +like( + exception { + $plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + max => 0, + directive => 'foo', + ); + }, + qr/'foo' is not a valid plan directive/, + "Invalid Directive" +); + +like( + exception { + $plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + max => 0, + reason => 'foo', + ); + }, + qr/Cannot have a reason without a directive!/, + "Reason without directive" +); + +like( + exception { + $plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + ); + }, + qr/No number of tests specified/, + "Nothing to do" +); + +like( + exception { + $plan = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + max => 'skip', + ); + }, + qr/Plan test count 'skip' does not appear to be a valid positive integer/, + "Max must be an integer" +); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Skip.t b/cpan/Test-Simple/t/Test2/modules/Event/Skip.t new file mode 100644 index 0000000000..a1580202db --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Skip.t @@ -0,0 +1,24 @@ +BEGIN { require "t/tools.pl" }; +use strict; +use warnings; + +use Test2::Event::Skip; +use Test2::Util::Trace; + +my $skip = Test2::Event::Skip->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__]), + name => 'skip me', + reason => 'foo', +); + +is($skip->name, 'skip me', "set name"); +is($skip->reason, 'foo', "got skip reason"); +ok(!$skip->pass, "no default for pass"); +ok($skip->effective_pass, "TODO always effectively passes"); + +is($skip->summary, "skip me (SKIP: foo)", "summary with reason"); + +$skip->set_reason(''); +is($skip->summary, "skip me (SKIP)", "summary without reason"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Subtest.t b/cpan/Test-Simple/t/Test2/modules/Event/Subtest.t new file mode 100644 index 0000000000..d6bff4d3b6 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Subtest.t @@ -0,0 +1,30 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; +use Test2::Event::Subtest; +my $st = 'Test2::Event::Subtest'; + +my $trace = Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__, 'xxx']); +my $one = $st->new( + trace => $trace, + pass => 1, + buffered => 1, + name => 'foo', + subtest_id => "1-1-1", +); + +ok($one->isa('Test2::Event::Ok'), "Inherit from Ok"); +is_deeply($one->subevents, [], "subevents is an arrayref"); + +is($one->summary, "foo", "simple summary"); +$one->set_todo(''); +is($one->summary, "foo (TODO)", "simple summary + TODO"); +$one->set_todo('foo'); +is($one->summary, "foo (TODO: foo)", "simple summary + TODO + Reason"); + +$one->set_todo(undef); +$one->set_name(''); +is($one->summary, "Nameless Subtest", "unnamed summary"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Event/Waiting.t b/cpan/Test-Simple/t/Test2/modules/Event/Waiting.t new file mode 100644 index 0000000000..cc54895dae --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Event/Waiting.t @@ -0,0 +1,16 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; +use Test2::Event::Waiting; + +my $waiting = Test2::Event::Waiting->new( + trace => 'fake', +); + +ok($waiting, "Created event"); +ok($waiting->global, "waiting is global"); + +is($waiting->summary, "IPC is waiting for children to finish...", "Got summary"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Formatter/TAP.t b/cpan/Test-Simple/t/Test2/modules/Formatter/TAP.t new file mode 100644 index 0000000000..115e900a95 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Formatter/TAP.t @@ -0,0 +1,464 @@ +use strict; +use warnings; +use Test2::Formatter::TAP; +use Test2::API qw/context/; +use PerlIO; + +BEGIN { + require "t/tools.pl"; + *OUT_STD = Test2::Formatter::TAP->can('OUT_STD') or die; + *OUT_ERR = Test2::Formatter::TAP->can('OUT_ERR') or die; +} + +use Test2::API; +Test2::API::test2_add_callback_context_release(sub { + my $ctx = shift; + return if $ctx->hub->is_passing; + $ctx->throw("(Die On Fail)"); +}); + +ok(my $one = Test2::Formatter::TAP->new, "Created a new instance"); +my $handles = $one->handles; +is(@$handles, 2, "Got 2 handles"); +ok($handles->[0] != $handles->[1], "First and second handles are not the same"); +my $layers = { map {$_ => 1} PerlIO::get_layers($handles->[0]) }; + +if (${^UNICODE} & 2) { # 2 means STDIN + ok($layers->{utf8}, "'S' is set in PERL_UNICODE, or in -C, honor it, utf8 should be on") +} +else { + ok(!$layers->{utf8}, "Not utf8 by default") +} + +$one->encoding('utf8'); +is($one->encoding, 'utf8', "Got encoding"); +$handles = $one->handles; +is(@$handles, 2, "Got 2 handles"); +$layers = { map {$_ => 1} PerlIO::get_layers($handles->[0]) }; +ok($layers->{utf8}, "Now utf8"); + +my $two = Test2::Formatter::TAP->new(encoding => 'utf8'); +$handles = $two->handles; +is(@$handles, 2, "Got 2 handles"); +$layers = { map {$_ => 1} PerlIO::get_layers($handles->[0]) }; +ok($layers->{utf8}, "Now utf8"); + + +{ + package My::Event; + + use base 'Test2::Event'; + use Test2::Util::HashBase qw{pass name diag note}; + + Test2::Formatter::TAP->register_event( + __PACKAGE__, + sub { + my $self = shift; + my ($e, $num) = @_; + return ( + [main::OUT_STD, "ok $num - " . $e->name . "\n"], + [main::OUT_ERR, "# " . $e->name . " " . $e->diag . "\n"], + [main::OUT_STD, "# " . $e->name . " " . $e->note . "\n"], + ); + } + ); +} + +my ($std, $err); +open( my $stdh, '>', \$std ) || die "Ooops"; +open( my $errh, '>', \$err ) || die "Ooops"; + +my $it = Test2::Formatter::TAP->new( + handles => [$stdh, $errh, $stdh], +); + +$it->write( + My::Event->new( + pass => 1, + name => 'foo', + diag => 'diag', + note => 'note', + trace => 'fake', + ), + 55, +); + +$it->write( + My::Event->new( + pass => 1, + name => 'bar', + diag => 'diag', + note => 'note', + trace => 'fake', + nested => 1, + ), + 1, +); + +is($std, <<EOT, "Got expected TAP output to std"); +ok 55 - foo +# foo note + ok 1 - bar + # bar note +EOT + +is($err, <<EOT, "Got expected TAP output to err"); +# foo diag + # bar diag +EOT + +$it = undef; +close($stdh); +close($errh); + +my ($trace, $ok, $diag, $plan, $bail); + +my $fmt = Test2::Formatter::TAP->new; +sub before_each { + # Make sure there is a fresh trace object for each group + $trace = Test2::Util::Trace->new( + frame => ['main_foo', 'foo.t', 42, 'main_foo::flubnarb'], + ); +} + +tests bail => sub { + my $bail = Test2::Event::Bail->new( + trace => $trace, + reason => 'evil', + ); + + is_deeply( + [$fmt->event_tap($bail, 1)], + [[OUT_STD, "Bail out! evil\n" ]], + "Got tap" + ); +}; + +tests diag => sub { + my $diag = Test2::Event::Diag->new( + trace => $trace, + message => 'foo', + ); + + is_deeply( + [$fmt->event_tap($diag, 1)], + [[OUT_ERR, "# foo\n"]], + "Got tap" + ); + + $diag->set_message("foo\n"); + is_deeply( + [$fmt->event_tap($diag, 1)], + [[OUT_ERR, "# foo\n"]], + "Only 1 newline" + ); + + $diag->set_message("foo\nbar\nbaz"); + is_deeply( + [$fmt->event_tap($diag, 1)], + [[OUT_ERR, "# foo\n# bar\n# baz\n"]], + "All lines have proper prefix" + ); +}; + +tests exception => sub { + my $exception = Test2::Event::Exception->new( + trace => $trace, + error => "evil at lake_of_fire.t line 6\n", + ); + + is_deeply( + [$fmt->event_tap($exception, 1)], + [[OUT_ERR, "evil at lake_of_fire.t line 6\n" ]], + "Got tap" + ); +}; + +tests note => sub { + my $note = Test2::Event::Note->new( + trace => $trace, + message => 'foo', + ); + + is_deeply( + [$fmt->event_tap($note, 1)], + [[OUT_STD, "# foo\n"]], + "Got tap" + ); + + $note->set_message("foo\n"); + is_deeply( + [$fmt->event_tap($note, 1)], + [[OUT_STD, "# foo\n"]], + "Only 1 newline" + ); + + $note->set_message("foo\nbar\nbaz"); + is_deeply( + [$fmt->event_tap($note, 1)], + [[OUT_STD, "# foo\n# bar\n# baz\n"]], + "All lines have proper prefix" + ); +}; + +for my $pass (1, 0) { + local $ENV{HARNESS_IS_VERBOSE} = 1; + tests name_and_number => sub { + my $ok = Test2::Event::Ok->new(trace => $trace, pass => $pass, name => 'foo'); + my @tap = $fmt->event_tap($ok, 7); + is_deeply( + \@tap, + [ + [OUT_STD, ($pass ? 'ok' : 'not ok') . " 7 - foo\n"], + ], + "Got expected output" + ); + }; + + tests no_number => sub { + my $ok = Test2::Event::Ok->new(trace => $trace, pass => $pass, name => 'foo'); + my @tap = $fmt->event_tap($ok, ); + is_deeply( + \@tap, + [ + [OUT_STD, ($pass ? 'ok' : 'not ok') . " - foo\n"], + ], + "Got expected output" + ); + }; + + tests no_name => sub { + my $ok = Test2::Event::Ok->new(trace => $trace, pass => $pass); + my @tap = $fmt->event_tap($ok, 7); + is_deeply( + \@tap, + [ + [OUT_STD, ($pass ? 'ok' : 'not ok') . " 7\n"], + ], + "Got expected output" + ); + }; + + tests todo => sub { + my $ok = Test2::Event::Ok->new(trace => $trace, pass => $pass); + $ok->set_todo('b'); + my @tap = $fmt->event_tap($ok, 7); + is_deeply( + \@tap, + [ + [OUT_STD, ($pass ? 'ok' : 'not ok') . " 7 # TODO b\n"], + ], + "Got expected output" + ); + + $ok->set_todo(""); + + @tap = $fmt->event_tap($ok, 7); + is_deeply( + \@tap, + [ + [OUT_STD, ($pass ? 'ok' : 'not ok') . " 7 # TODO\n"], + ], + "Got expected output" + ); + }; +}; + +tests plan => sub { + my $plan = Test2::Event::Plan->new( + trace => $trace, + max => 100, + ); + + is_deeply( + [$fmt->event_tap($plan, 1)], + [[OUT_STD, "1..100\n"]], + "Got tap" + ); + + $plan->set_max(0); + $plan->set_directive('SKIP'); + $plan->set_reason('foo'); + is_deeply( + [$fmt->event_tap($plan, 1)], + [[OUT_STD, "1..0 # SKIP foo\n"]], + "Got tap for skip_all" + ); + + $plan = Test2::Event::Plan->new( + trace => $trace, + max => 0, + directive => 'skip_all', + ); + is_deeply( + [$fmt->event_tap($plan)], + [[OUT_STD, "1..0 # SKIP\n"]], + "SKIP without reason" + ); + + $plan = Test2::Event::Plan->new( + trace => $trace, + max => 0, + directive => 'no_plan', + ); + is_deeply( + [$fmt->event_tap($plan)], + [], + "NO PLAN" + ); + + $plan = Test2::Event::Plan->new( + trace => $trace, + max => 0, + directive => 'skip_all', + reason => "Foo\nBar\nBaz", + ); + is_deeply( + [$fmt->event_tap($plan)], + [ + [OUT_STD, "1..0 # SKIP Foo\n# Bar\n# Baz\n"], + ], + "Multi-line reason for skip" + ); +}; + +tests subtest => sub { + my $st = 'Test2::Event::Subtest'; + + my $one = $st->new( + trace => $trace, + pass => 1, + buffered => 1, + name => 'foo', + subtest_id => '1-1-1', + ); + + is_deeply( + [$fmt->event_tap($one, 5)], + [ + [OUT_STD, "ok 5 - foo {\n"], + [OUT_STD, "}\n"], + ], + "Got Buffered TAP output" + ); + + $one->set_buffered(0); + is_deeply( + [$fmt->event_tap($one, 5)], + [ + [OUT_STD, "ok 5 - foo\n"], + ], + "Got Unbuffered TAP output" + ); + + $one = $st->new( + trace => $trace, + pass => 0, + buffered => 1, + name => 'bar', + subtest_id => '1-1-1', + subevents => [ + Test2::Event::Ok->new(trace => $trace, name => 'first', pass => 1), + Test2::Event::Ok->new(trace => $trace, name => 'second', pass => 0), + Test2::Event::Ok->new(trace => $trace, name => 'third', pass => 1), + + Test2::Event::Diag->new(trace => $trace, message => 'blah blah'), + + Test2::Event::Plan->new(trace => $trace, max => 3), + ], + ); + + { + local $ENV{HARNESS_IS_VERBOSE}; + is_deeply( + [$fmt->event_tap($one, 5)], + [ + [OUT_STD, "not ok 5 - bar {\n"], + [OUT_STD, " ok 1 - first\n"], + [OUT_STD, " not ok 2 - second\n"], + [OUT_STD, " ok 3 - third\n"], + [OUT_ERR, " # blah blah\n"], + [OUT_STD, " 1..3\n"], + [OUT_STD, "}\n"], + ], + "Got Buffered TAP output (non-verbose)" + ); + } + + { + local $ENV{HARNESS_IS_VERBOSE} = 1; + is_deeply( + [$fmt->event_tap($one, 5)], + [ + [OUT_STD, "not ok 5 - bar {\n"], + [OUT_STD, " ok 1 - first\n"], + [OUT_STD, " not ok 2 - second\n"], + [OUT_STD, " ok 3 - third\n"], + [OUT_ERR, " # blah blah\n"], + [OUT_STD, " 1..3\n"], + [OUT_STD, "}\n"], + ], + "Got Buffered TAP output (verbose)" + ); + } + + { + local $ENV{HARNESS_IS_VERBOSE}; + $one->set_buffered(0); + is_deeply( + [$fmt->event_tap($one, 5)], + [ + # In unbuffered TAP the subevents are rendered outside of this. + [OUT_STD, "not ok 5 - bar\n"], + ], + "Got Unbuffered TAP output (non-verbose)" + ); + } + + { + local $ENV{HARNESS_IS_VERBOSE} = 1; + $one->set_buffered(0); + is_deeply( + [$fmt->event_tap($one, 5)], + [ + # In unbuffered TAP the subevents are rendered outside of this. + [OUT_STD, "not ok 5 - bar\n"], + ], + "Got Unbuffered TAP output (verbose)" + ); + } +}; + +tests skip => sub { + my $skip = Test2::Event::Skip->new(trace => $trace, pass => 1, name => 'foo', reason => 'xxx'); + my @tap = $fmt->event_tap($skip, 7); + is_deeply( + \@tap, + [ + [OUT_STD, "ok 7 - foo # skip xxx\n"], + ], + "Passing Skip" + ); + + $skip->set_pass(0); + @tap = $fmt->event_tap($skip, 7); + is_deeply( + \@tap, + [ + [OUT_STD, "not ok 7 - foo # skip xxx\n"], + ], + "Failling Skip" + ); + + $skip->set_todo("xxx"); + @tap = $fmt->event_tap($skip, 7); + is_deeply( + \@tap, + [ + [OUT_STD, "not ok 7 - foo # TODO & SKIP xxx\n"], + ], + "Todo Skip" + ); +}; + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Hub.t b/cpan/Test-Simple/t/Test2/modules/Hub.t new file mode 100644 index 0000000000..4ed48bb61b --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Hub.t @@ -0,0 +1,484 @@ +use strict; +use warnings; + +use Test2::IPC; +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 base 'Test2::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; diff --git a/cpan/Test-Simple/t/Test2/modules/Hub/Interceptor.t b/cpan/Test-Simple/t/Test2/modules/Hub/Interceptor.t new file mode 100644 index 0000000000..a1257210a1 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Hub/Interceptor.t @@ -0,0 +1,15 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; + +use Test2::Hub::Interceptor; + +my $one = Test2::Hub::Interceptor->new(); + +ok($one->isa('Test2::Hub'), "inheritence");; + +my $e = exception { $one->terminate(55) }; +ok($e->isa('Test2::Hub::Interceptor::Terminator'), "exception type"); +is($$e, 55, "Scalar reference value"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Hub/Interceptor/Terminator.t b/cpan/Test-Simple/t/Test2/modules/Hub/Interceptor/Terminator.t new file mode 100644 index 0000000000..9f25d4b317 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Hub/Interceptor/Terminator.t @@ -0,0 +1,9 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; + +use Test2::Hub::Interceptor::Terminator; + +ok($INC{'Test2/Hub/Interceptor/Terminator.pm'}, "loaded"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Hub/Subtest.t b/cpan/Test-Simple/t/Test2/modules/Hub/Subtest.t new file mode 100644 index 0000000000..ca16785720 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Hub/Subtest.t @@ -0,0 +1,124 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; + +use Test2::Hub::Subtest; +use Test2::Util qw/get_tid/; +use Carp qw/croak/; + +my %TODO; + +sub def { + my ($func, @args) = @_; + + my @caller = caller(0); + + $TODO{$caller[0]} ||= []; + push @{$TODO{$caller[0]}} => [$func, \@args, \@caller]; +} + +sub do_def { + my $for = caller; + my $tests = delete $TODO{$for} or croak "No tests to run!"; + + for my $test (@$tests) { + my ($func, $args, $caller) = @$test; + + my ($pkg, $file, $line) = @$caller; + +# Note: The '&' below is to bypass the prototype, which is important here. + eval <<" EOT" or die $@; +package $pkg; +# line $line "(eval in DeferredTests) $file" +\&$func(\@\$args); +1; + EOT + } +} + +my $ran = 0; +my $event; + +my $one = Test2::Hub::Subtest->new( + nested => 3, +); + +ok($one->isa('Test2::Hub'), "inheritence"); + +{ + no warnings 'redefine'; + local *Test2::Hub::process = sub { $ran++; (undef, $event) = @_; 'P!' }; + use warnings; + + my $ok = Test2::Event::Ok->new( + pass => 1, + name => 'blah', + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__, 'xxx']), + ); + + def is => ($one->process($ok), 'P!', "processed"); + def is => ($ran, 1, "ran the mocked process"); + def is => ($event, $ok, "got our event"); + def is => ($event->nested, 3, "nested was set"); + def is => ($one->bailed_out, undef, "did not bail"); + + $ran = 0; + $event = undef; + + my $bail = Test2::Event::Bail->new( + message => 'blah', + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__, 'xxx']), + ); + + def is => ($one->process($bail), 'P!', "processed"); + def is => ($ran, 1, "ran the mocked process"); + def is => ($event, $bail, "got our event"); + def is => ($event->nested, 3, "nested was set"); + def is => ($one->bailed_out, $event, "bailed"); +} + +do_def; + +my $skip = Test2::Event::Plan->new( + trace => Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__], pid => $$, tid => get_tid), + directive => 'SKIP', + reason => 'foo', +); + +$ran = 0; +T2_SUBTEST_WRAPPER: { + $ran++; + $one->terminate(100, $skip); + $ran++; +} +is($ran, 1, "did not get past the terminate"); + +$ran = 0; +T2_SUBTEST_WRAPPER: { + $ran++; + $one->send($skip); + $ran++; +} +is($ran, 1, "did not get past the terminate"); + +$one->reset_state; +$one->set_manual_skip_all(1); + +$ran = 0; +T2_SUBTEST_WRAPPER: { + $ran++; + $one->terminate(100, $skip); + $ran++; +} +is($ran, 2, "did not automatically abort"); + +$one->reset_state; +$ran = 0; +T2_SUBTEST_WRAPPER: { + $ran++; + $one->send($skip); + $ran++; +} +is($ran, 2, "did not automatically abort"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/IPC.t b/cpan/Test-Simple/t/Test2/modules/IPC.t new file mode 100644 index 0000000000..0557925ca4 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/IPC.t @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use Test2::IPC qw/cull/; +use Test2::API qw/context test2_ipc_drivers test2_ipc/; + +BEGIN { require "t/tools.pl" }; + +test2_ipc(); + +is_deeply( + [test2_ipc_drivers()], + ['Test2::IPC::Driver::Files'], + "Default driver" +); + +ok(__PACKAGE__->can('cull'), "Imported cull"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/IPC/Driver.t b/cpan/Test-Simple/t/Test2/modules/IPC/Driver.t new file mode 100644 index 0000000000..f7daf84dd7 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/IPC/Driver.t @@ -0,0 +1,57 @@ +use strict; +use warnings; + +use Test2::IPC::Driver::Files; + +BEGIN { require "t/tools.pl" }; +use Test2::API qw/context test2_ipc_drivers/; + +Test2::IPC::Driver::Files->import(); +Test2::IPC::Driver::Files->import(); +Test2::IPC::Driver::Files->import(); + +is_deeply( + [test2_ipc_drivers()], + ['Test2::IPC::Driver::Files'], + "Driver not added multiple times" +); + +for my $meth (qw/send cull add_hub drop_hub waiting is_viable/) { + my $one = Test2::IPC::Driver->new; + like( + exception { $one->$meth }, + qr/'\Q$one\E' did not define the required method '$meth'/, + "Require override of method $meth" + ); +} + +tests abort => sub { + my $one = Test2::IPC::Driver->new(no_fatal => 1); + my ($err, $out) = ("", ""); + + { + local *STDERR; + local *STDOUT; + open(STDERR, '>', \$err); + open(STDOUT, '>', \$out); + $one->abort('foo'); + } + + is($err, "IPC Fatal Error: foo\n", "Got error"); + is($out, "not ok - IPC Fatal Error\n", "got 'not ok' on stdout"); + + ($err, $out) = ("", ""); + + { + local *STDERR; + local *STDOUT; + open(STDERR, '>', \$err); + open(STDOUT, '>', \$out); + $one->abort_trace('foo'); + } + + is($out, "not ok - IPC Fatal Error\n", "got 'not ok' on stdout"); + like($err, qr/IPC Fatal Error: foo/, "Got error"); +}; + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/IPC/Driver/Files.t b/cpan/Test-Simple/t/Test2/modules/IPC/Driver/Files.t new file mode 100644 index 0000000000..368bbf2363 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/IPC/Driver/Files.t @@ -0,0 +1,282 @@ +BEGIN { require "t/tools.pl" }; +use Test2::Util qw/get_tid USE_THREADS try/; +use File::Temp qw/tempfile/; +use strict; +use warnings; + +sub capture(&) { + my $code = shift; + + my ($err, $out) = ("", ""); + + my ($ok, $e); + { + local *STDOUT; + local *STDERR; + + ($ok, $e) = try { + open(STDOUT, '>', \$out) or die "Failed to open a temporary STDOUT: $!"; + open(STDERR, '>', \$err) or die "Failed to open a temporary STDERR: $!"; + + $code->(); + }; + } + + die $e unless $ok; + + return { + STDOUT => $out, + STDERR => $err, + }; +} + +require Test2::IPC::Driver::Files; +ok(my $ipc = Test2::IPC::Driver::Files->new, "Created an IPC instance"); +ok($ipc->isa('Test2::IPC::Driver::Files'), "Correct type"); +ok($ipc->isa('Test2::IPC::Driver'), "inheritence"); + +ok(-d $ipc->tempdir, "created temp dir"); +is($ipc->pid, $$, "stored pid"); +is($ipc->tid, get_tid(), "stored the tid"); + +my $hid = '12345'; + +$ipc->add_hub($hid); +ok(-f $ipc->tempdir . '/HUB-' . $hid, "wrote hub file"); +if(ok(open(my $fh, '<', $ipc->tempdir . '/HUB-' . $hid), "opened hub file")) { + my @lines = <$fh>; + close($fh); + is_deeply( + \@lines, + [ "$$\n", get_tid() . "\n" ], + "Wrote pid and tid to hub file" + ); +} + +{ + package Foo; + use base 'Test2::Event'; +} + +$ipc->send($hid, bless({ foo => 1 }, 'Foo')); +$ipc->send($hid, bless({ bar => 1 }, 'Foo')); + +opendir(my $dh, $ipc->tempdir) || die "Could not open tempdir: !?"; +my @files = grep { $_ !~ m/^\.+$/ && $_ ne "HUB-$hid" } readdir($dh); +closedir($dh); +is(@files, 2, "2 files added to the IPC directory"); + +my @events = $ipc->cull($hid); +is_deeply( + \@events, + [{ foo => 1 }, { bar => 1 }], + "Culled both events" +); + +opendir($dh, $ipc->tempdir) || die "Could not open tempdir: !?"; +@files = grep { $_ !~ m/^\.+$/ && $_ ne "HUB-$hid" } readdir($dh); +closedir($dh); +is(@files, 0, "All files collected"); + +$ipc->drop_hub($hid); +ok(!-f $ipc->tempdir . '/' . $hid, "removed hub file"); + +$ipc->send($hid, bless({global => 1}, 'Foo'), 'GLOBAL'); +my @got = $ipc->cull($hid); +ok(@got == 0, "did not get our own global event"); + +my $tmpdir = $ipc->tempdir; +ok(-d $tmpdir, "still have temp dir"); +$ipc = undef; +ok(!-d $tmpdir, "cleaned up temp dir"); + +{ + my $ipc = Test2::IPC::Driver::Files->new(); + + my $tmpdir = $ipc->tempdir; + + my $ipc_thread_clone = bless {%$ipc}, 'Test2::IPC::Driver::Files'; + $ipc_thread_clone->set_tid(100); + $ipc_thread_clone = undef; + ok(-d $tmpdir, "Directory not removed (different thread)"); + + my $ipc_fork_clone = bless {%$ipc}, 'Test2::IPC::Driver::Files'; + $ipc_fork_clone->set_pid($$ + 10); + $ipc_fork_clone = undef; + ok(-d $tmpdir, "Directory not removed (different proc)"); + + + $ipc_thread_clone = bless {%$ipc}, 'Test2::IPC::Driver::Files'; + $ipc_thread_clone->set_tid(undef); + $ipc_thread_clone = undef; + ok(-d $tmpdir, "Directory not removed (no thread)"); + + $ipc_fork_clone = bless {%$ipc}, 'Test2::IPC::Driver::Files'; + $ipc_fork_clone->set_pid(undef); + $ipc_fork_clone = undef; + ok(-d $tmpdir, "Directory not removed (no proc)"); + + $ipc = undef; + ok(!-d $tmpdir, "Directory removed"); +} + +{ + no warnings 'once'; + local *Test2::IPC::Driver::Files::abort = sub { + my $self = shift; + local $self->{no_fatal} = 1; + $self->Test2::IPC::Driver::abort(@_); + die 255; + }; + + my $tmpdir; + my @lines; + my $file = __FILE__; + + my $out = capture { + local $ENV{T2_KEEP_TEMPDIR} = 1; + + my $ipc = Test2::IPC::Driver::Files->new(); + $tmpdir = $ipc->tempdir; + $ipc->add_hub($hid); + eval { $ipc->add_hub($hid) }; push @lines => __LINE__; + $ipc->send($hid, bless({ foo => 1 }, 'Foo')); + $ipc->cull($hid); + $ipc->drop_hub($hid); + eval { $ipc->drop_hub($hid) }; push @lines => __LINE__; + + # Make sure having a hub file sitting around does not throw things off + # in T2_KEEP_TEMPDIR + $ipc->add_hub($hid); + $ipc = undef; + 1; + }; + + is($out->{STDOUT}, "not ok - IPC Fatal Error\nnot ok - IPC Fatal Error\n", "printed "); + + like($out->{STDERR}, qr/IPC Temp Dir: \Q$tmpdir\E/m, "Got temp dir path"); + like($out->{STDERR}, qr/^# Not removing temp dir: \Q$tmpdir\E$/m, "Notice about not closing tempdir"); + + like($out->{STDERR}, qr/^IPC Fatal Error: File for hub '12345' already exists/m, "Got message for duplicate hub"); + like($out->{STDERR}, qr/^IPC Fatal Error: File for hub '12345' does not exist/m, "Cannot remove hub twice"); + + $out = capture { + my $ipc = Test2::IPC::Driver::Files->new(); + $ipc->add_hub($hid); + my $trace = Test2::Util::Trace->new(frame => [__PACKAGE__, __FILE__, __LINE__, 'foo']); + my $e = eval { $ipc->send($hid, bless({glob => \*ok, trace => $trace}, 'Foo')); 1 }; + print STDERR $@ unless $e || $@ =~ m/^255/; + $ipc->drop_hub($hid); + }; + + like($out->{STDERR}, qr/IPC Fatal Error:/, "Got fatal error"); + like($out->{STDERR}, qr/There was an error writing an event/, "Explanation"); + like($out->{STDERR}, qr/Destination: 12345/, "Got dest"); + like($out->{STDERR}, qr/Origin PID:\s+$$/, "Got pid"); + like($out->{STDERR}, qr/Error: Can't store GLOB items/, "Got cause"); + + $out = capture { + my $ipc = Test2::IPC::Driver::Files->new(); + local $@; + eval { $ipc->send($hid, bless({ foo => 1 }, 'Foo')) }; + print STDERR $@ unless $@ =~ m/^255/; + $ipc = undef; + }; + like($out->{STDERR}, qr/IPC Fatal Error: hub '12345' is not available, failed to send event!/, "Cannot send to missing hub"); + + $out = capture { + my $ipc = Test2::IPC::Driver::Files->new(); + $ipc->add_hub($hid); + $ipc->send($hid, bless({ foo => 1 }, 'Foo')); + local $@; + eval { $ipc->drop_hub($hid) }; + print STDERR $@ unless $@ =~ m/^255/; + }; + like($out->{STDERR}, qr/IPC Fatal Error: Not all files from hub '12345' have been collected/, "Leftover files"); + like($out->{STDERR}, qr/IPC Fatal Error: Leftover files in the directory \(.*\.ready\)/, "What file"); + + $out = capture { + my $ipc = Test2::IPC::Driver::Files->new(); + $ipc->add_hub($hid); + + eval { $ipc->send($hid, { foo => 1 }) }; + print STDERR $@ unless $@ =~ m/^255/; + + eval { $ipc->send($hid, bless({ foo => 1 }, 'xxx')) }; + print STDERR $@ unless $@ =~ m/^255/; + }; + like($out->{STDERR}, qr/IPC Fatal Error: 'HASH\(.*\)' is not a blessed object/, "Cannot send unblessed objects"); + like($out->{STDERR}, qr/IPC Fatal Error: 'xxx=HASH\(.*\)' is not an event object!/, "Cannot send non-event objects"); + + + $ipc = Test2::IPC::Driver::Files->new(); + + my ($fh, $fn) = tempfile(); + print $fh "\n"; + close($fh); + + Storable::store({}, $fn); + $out = capture { eval { $ipc->read_event_file($fn) } }; + like( + $out->{STDERR}, + qr/IPC Fatal Error: Got an unblessed object: 'HASH\(.*\)'/, + "Events must actually be events (must be blessed)" + ); + + Storable::store(bless({}, 'Test2::Event::FakeEvent'), $fn); + $out = capture { eval { $ipc->read_event_file($fn) } }; + like( + $out->{STDERR}, + qr{IPC Fatal Error: Event has unknown type \(Test2::Event::FakeEvent\), tried to load 'Test2/Event/FakeEvent\.pm' but failed: Can't locate Test2/Event/FakeEvent\.pm}, + "Events must actually be events (not a real module)" + ); + + Storable::store(bless({}, 'Test2::API'), $fn); + $out = capture { eval { $ipc->read_event_file($fn) } }; + like( + $out->{STDERR}, + qr{'Test2::API=HASH\(.*\)' is not a 'Test2::Event' object}, + "Events must actually be events (not an event type)" + ); + + Storable::store(bless({}, 'Foo'), $fn); + $out = capture { + local @INC; + push @INC => ('t/lib', 'lib'); + eval { $ipc->read_event_file($fn) }; + }; + ok(!$out->{STDERR}, "no problem", $out->{STDERR}); + ok(!$out->{STDOUT}, "no problem", $out->{STDOUT}); + + unlink($fn); +} + +{ + my $ipc = Test2::IPC::Driver::Files->new(); + $ipc->add_hub($hid); + $ipc->send($hid, bless({global => 1}, 'Foo'), 'GLOBAL'); + $ipc->set_globals({}); + my @events = $ipc->cull($hid); + is_deeply( + \@events, + [ {global => 1} ], + "Got global event" + ); + + @events = $ipc->cull($hid); + ok(!@events, "Did not grab it again"); + + $ipc->set_globals({}); + @events = $ipc->cull($hid); + is_deeply( + \@events, + [ {global => 1} ], + "Still there" + ); + + $ipc->drop_hub($hid); + $ipc = undef; +} + +done_testing; + diff --git a/cpan/Test-Simple/t/Test2/modules/Util.t b/cpan/Test-Simple/t/Test2/modules/Util.t new file mode 100644 index 0000000000..1632a95d44 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Util.t @@ -0,0 +1,37 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" }; +use Test2::Util qw/ + try + + get_tid USE_THREADS + + pkg_to_file + + CAN_FORK + CAN_THREAD + CAN_REALLY_FORK +/; + +{ + for my $try (\&try, Test2::Util->can('_manual_try'), Test2::Util->can('_local_try')) { + my ($ok, $err) = $try->(sub { die "xxx" }); + ok(!$ok, "cought exception"); + like($err, qr/xxx/, "expected exception"); + + ($ok, $err) = $try->(sub { 0 }); + ok($ok, "Success"); + ok(!$err, "no error"); + } +} + +is(pkg_to_file('A::Package::Name'), 'A/Package/Name.pm', "Converted package to file"); + +# Make sure running them does not die +# We cannot really do much to test these. +CAN_THREAD(); +CAN_FORK(); +CAN_REALLY_FORK(); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Util/ExternalMeta.t b/cpan/Test-Simple/t/Test2/modules/Util/ExternalMeta.t new file mode 100644 index 0000000000..de6be9963a --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Util/ExternalMeta.t @@ -0,0 +1,70 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; + +{ + package Foo::Bar; + + use Test2::Util::ExternalMeta; + use Test2::Util::HashBase qw/foo bar/; +} + +ok(Foo::Bar->can($_), "Imported '$_'") for qw/meta get_meta set_meta delete_meta/; + +my $one = Foo::Bar->new(foo => 1, bar => 2); +ok($one->isa('Foo::Bar'), "Got instance"); + +is_deeply($one, {foo => 1, bar => 2}, "nothing fishy.. yet"); + +is($one->get_meta('foo'), undef, "no meta-data for foo"); +is($one->get_meta('bar'), undef, "no meta-data for bar"); +is($one->get_meta('baz'), undef, "no meta-data for baz"); + +is($one->meta('foo'), undef, "no meta-data for foo"); +is($one->meta('bar'), undef, "no meta-data for bar"); +is($one->meta('baz'), undef, "no meta-data for baz"); + +is_deeply($one, {foo => 1, bar => 2}, "Still have not modified instance"); + +$one->set_meta('foo' => 123); +is($one->foo, 1, "did not change attribute"); +is($one->meta('foo'), 123, "get meta-data for foo"); +is($one->get_meta('foo'), 123, "get meta-data for foo again"); + +$one->meta('foo', 345); +is($one->foo, 1, "did not change attribute"); +is($one->meta('foo', 678), 123, "did not alter already set meta-attribute"); +is($one->get_meta('foo'), 123, "still did not alter already set meta-attribute"); + +is($one->meta('bar', 789), 789, "used default for bar"); +is($one->bar, 2, "did not change attribute"); + +is_deeply( + $one, + { + foo => 1, + bar => 2, + Test2::Util::ExternalMeta::META_KEY() => { + foo => 123, + bar => 789, + }, + }, + "Stored meta-data" +); + +is($one->delete_meta('foo'), 123, "got old value on delete"); +is($one->meta('foo'), undef, "no more value"); + +is_deeply( + $one, + { + foo => 1, + bar => 2, + Test2::Util::ExternalMeta::META_KEY() => { + bar => 789, + }, + }, + "Deleted the meta key" +); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Util/HashBase.t b/cpan/Test-Simple/t/Test2/modules/Util/HashBase.t new file mode 100644 index 0000000000..0e81e9fcec --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Util/HashBase.t @@ -0,0 +1,105 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; +BEGIN { + $INC{'My/HBase.pm'} = __FILE__; + + package My::HBase; + use Test2::Util::HashBase qw/foo bar baz/; + + main::is(FOO, 'foo', "FOO CONSTANT"); + main::is(BAR, 'bar', "BAR CONSTANT"); + main::is(BAZ, 'baz', "BAZ CONSTANT"); +} + +BEGIN { + package My::HBaseSub; + use base 'My::HBase'; + use Test2::Util::HashBase qw/apple pear/; + + main::is(FOO, 'foo', "FOO CONSTANT"); + main::is(BAR, 'bar', "BAR CONSTANT"); + main::is(BAZ, 'baz', "BAZ CONSTANT"); + main::is(APPLE, 'apple', "APPLE CONSTANT"); + main::is(PEAR, 'pear', "PEAR CONSTANT"); +} + +my $one = My::HBase->new(foo => 'a', bar => 'b', baz => 'c'); +is($one->foo, 'a', "Accessor"); +is($one->bar, 'b', "Accessor"); +is($one->baz, 'c', "Accessor"); +$one->set_foo('x'); +is($one->foo, 'x', "Accessor set"); +$one->set_foo(undef); + +is_deeply( + $one, + { + foo => undef, + bar => 'b', + baz => 'c', + }, + 'hash' +); + +BEGIN { + package My::Const::Test; + use Test2::Util::HashBase qw/foo/; + + sub do_it { + if (FOO()) { + return 'const'; + } + return 'not const' + } +} + +my $pkg = 'My::Const::Test'; +is($pkg->do_it, 'const', "worked as expected"); +{ + local $SIG{__WARN__} = sub { }; + *My::Const::Test::FOO = sub { 0 }; +} +ok(!$pkg->FOO, "overrode const sub"); +is($pkg->do_it, 'const', "worked as expected, const was constant"); + +BEGIN { + $INC{'My/HBase/Wrapped.pm'} = __FILE__; + + package My::HBase::Wrapped; + use Test2::Util::HashBase qw/foo bar/; + + my $foo = __PACKAGE__->can('foo'); + no warnings 'redefine'; + *foo = sub { + my $self = shift; + $self->set_bar(1); + $self->$foo(@_); + }; +} + +BEGIN { + $INC{'My/HBase/Wrapped/Inherit.pm'} = __FILE__; + + package My::HBase::Wrapped::Inherit; + use base 'My::HBase::Wrapped'; + use Test2::Util::HashBase; +} + +my $o = My::HBase::Wrapped::Inherit->new(foo => 1); +my $foo = $o->foo; +is($o->bar, 1, 'parent attribute sub not overridden'); + +{ + package Foo; + + sub new; + + use Test2::Util::HashBase qw/foo bar baz/; + + sub new { 'foo' }; +} + +is(Foo->new, 'foo', "Did not override existing 'new' method"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/modules/Util/Trace.t b/cpan/Test-Simple/t/Test2/modules/Util/Trace.t new file mode 100644 index 0000000000..846c04ff6c --- /dev/null +++ b/cpan/Test-Simple/t/Test2/modules/Util/Trace.t @@ -0,0 +1,41 @@ +use strict; +use warnings; +BEGIN { require "t/tools.pl" }; +use Test2::Util::Trace; + +like( + exception { 'Test2::Util::Trace'->new() }, + qr/The 'frame' attribute is required/, + "got error" +); + +my $one = 'Test2::Util::Trace'->new(frame => ['Foo::Bar', 'foo.t', 5, 'Foo::Bar::foo']); +is_deeply($one->frame, ['Foo::Bar', 'foo.t', 5, 'Foo::Bar::foo'], "Got frame"); +is_deeply([$one->call], ['Foo::Bar', 'foo.t', 5, 'Foo::Bar::foo'], "Got call"); +is($one->package, 'Foo::Bar', "Got package"); +is($one->file, 'foo.t', "Got file"); +is($one->line, 5, "Got line"); +is($one->subname, 'Foo::Bar::foo', "got subname"); + +is($one->debug, "at foo.t line 5", "got trace"); +$one->set_detail("yo momma"); +is($one->debug, "yo momma", "got detail for trace"); +$one->set_detail(undef); + +is( + exception { $one->throw('I died') }, + "I died at foo.t line 5.\n", + "got exception" +); + +is_deeply( + warnings { $one->alert('I cried') }, + [ "I cried at foo.t line 5.\n" ], + "alter() warns" +); + +my $snap = $one->snapshot; +is_deeply($snap, $one, "identical"); +ok($snap != $one, "Not the same instance"); + +done_testing; diff --git a/cpan/Test-Simple/t/Test2/regression/gh_16.t b/cpan/Test-Simple/t/Test2/regression/gh_16.t new file mode 100644 index 0000000000..45e4cd7b76 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/regression/gh_16.t @@ -0,0 +1,23 @@ +use strict; +use warnings; + +# This test is for gh #16 +# Also see https://rt.perl.org/Public/Bug/Display.html?id=127774 + +# Ceate this END before anything else so that $? gets set to 0 +END { $? = 0 } + +BEGIN { + print "\n1..1\n"; + close(STDERR); + open(STDERR, '>&', STDOUT); +} + +use Test2::API; + +eval(' sub { die "xxx" } ')->(); +END { + sub { my $ctx = Test2::API::context(); $ctx->release; }->(); + print "ok 1 - Did not segv\n"; + $? = 0; +} diff --git a/cpan/Test-Simple/t/Test2/regression/ipc_files_abort_exit.t b/cpan/Test-Simple/t/Test2/regression/ipc_files_abort_exit.t new file mode 100644 index 0000000000..1bbc624e31 --- /dev/null +++ b/cpan/Test-Simple/t/Test2/regression/ipc_files_abort_exit.t @@ -0,0 +1,62 @@ +use strict; +use warnings; +use Test2::IPC; +BEGIN { require "t/tools.pl" }; +use Test2::API qw/context test2_stack/; +use Test2::Util qw/CAN_FORK/; + +BEGIN { + skip_all "System cannot fork" unless CAN_FORK; +} + +plan(3); + +pipe(my ($read, $write)); + +test2_stack()->top; +my $hub = test2_stack()->new_hub(); + +my $pid = fork(); +die "Failed to fork" unless defined $pid; + +if ($pid) { + close($read); + test2_stack()->pop($hub); + $hub = undef; + print $write "Go\n"; + close($write); + waitpid($pid, 0); + my $err = $? >> 8; + is($err, 255, "Exit code was not masked"); + ok($err != 100, "Did not hit the safety exit"); +} +else { + close($write); + my $ignore = <$read>; + close($read); + close(STDERR); + close(STDOUT); + open(STDERR, '>', my $x); + my $ctx = context(hub => $hub, level => -1); + my $clone = $ctx->snapshot; + $ctx->release; + $clone->ok(0, "Should not see this"); + print STDERR "\n\nSomething went wrong!!!!\n\n"; + exit 100; # Safety exit +}; + + +# The rest of this is to make sure nothing that happens when reading the event +# messes with $?. + +pipe($read, $write); + +$pid = fork; +die "Failed to fork" unless defined $pid; + +unless($pid) { + my $ignore = <$read>; + ok(1, "Test in forked process"); +} + +print $write "Go\n"; |