From 8117cb4054303fd3463d198a80ae4cf2ef8831c6 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Thu, 22 Sep 2016 11:05:36 -0500 Subject: [PATCH] Add ->terminate and ->finalize methods to the Formatter By default, these do nothing, but they're necessary to implement any sort of document-oriented format. Otherwise there's no chance for the formatter to "close" the output. For example, if you're generating XML you need a chance to close all open tags. --- Changes | 6 ++ lib/Test2/Formatter.pm | 31 +++++++++ lib/Test2/Hub.pm | 18 ++++-- t/Test2/behavior/Formatter.t | 70 +++++++++++++++++++++ t/Test2/behavior/Subtest_buffer_formatter.t | 10 ++- 5 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 t/Test2/behavior/Formatter.t diff --git a/Changes b/Changes index bf9022e72..aa241e457 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ {{$NEXT}} + - Formatters now have terminate() and finalize() methods. These are called + when there is a skip_all or bail event (terminate) or when a test suite + is exiting normally (finalize). This allows formatters to finalize their + output, which is important for any sort of document-oriented format (as + opposed to a stream format like TAP). (#723)g + 1.302058 2016-09-21 10:46:13-07:00 America/Los_Angeles (TRIAL RELEASE) - Mask warning when comparing $@ in Test2::API::Context diff --git a/lib/Test2/Formatter.pm b/lib/Test2/Formatter.pm index d1ef12a37..63fceabf8 100644 --- a/lib/Test2/Formatter.pm +++ b/lib/Test2/Formatter.pm @@ -16,6 +16,10 @@ sub import { sub hide_buffered { 1 } +sub terminate { } + +sub finalize { } + 1; __END__ @@ -48,6 +52,10 @@ A formatter is any package or object with a C method. sub hide_buffered { 1 } + sub terminate { } + + sub finalize { } + 1; The C method is a method, so it either gets a class or instance. The two @@ -60,6 +68,29 @@ The C method must return a boolean. This is used to tell buffered subtests whether or not to send it events as they are being buffered. See L for more information. +The C and C methods are optional methods called that you +can implement if the format you're generating needs to handle these cases, for +example if you are generating XML and need close open tags. + +The C method is called when an event's C method returns +true, for example when a L has a C<'skip_all'> plan, or +when a L event is sent. The C method is passed +a single argument, the L object which triggered the terminate. + +The C method is always the last thing called on the formatter, I<< +except when C is called >>. It is passed the following arguments: + +=over 4 + +=item * The number of tests that were planned + +=item * The number of tests actually seen + +=item * The number of tests which failed + +=item * A boolean indicating whether or not the test suite passed + +=back =head1 SOURCE diff --git a/lib/Test2/Hub.pm b/lib/Test2/Hub.pm index bd05bcbd4..293947a10 100644 --- a/lib/Test2/Hub.pm +++ b/lib/Test2/Hub.pm @@ -300,7 +300,10 @@ sub process { return $e if $is_ok || $no_fail; my $code = $e->terminate; - $self->terminate($code, $e) if defined $code; + if (defined $code) { + $self->{+_FORMATTER}->terminate($e) if $self->{+_FORMATTER}; + $self->terminate($code, $e); + } return $e; } @@ -332,8 +335,11 @@ sub finalize { my $failed = $self->{+FAILED}; my $active = $self->{+ACTIVE}; - # return if NOTHING was done. - return unless $active || $do_plan || defined($plan) || $count || $failed; + # return if NOTHING was done. + unless ($active || $do_plan || defined($plan) || $count || $failed) { + $self->{+_FORMATTER}->finalize($plan, $count, $failed, 0) if $self->{+_FORMATTER}; + return; + } unless ($self->{+ENDED}) { if ($self->{+_FOLLOW_UPS}) { @@ -369,7 +375,11 @@ Second End: $sfile line $sline } $self->{+ENDED} = $frame; - $self->is_passing(); # Generate the final boolean. + my $pass = $self->is_passing(); # Generate the final boolean. + + $self->{+_FORMATTER}->finalize($plan, $count, $failed, $pass) if $self->{+_FORMATTER}; + + return $pass; } sub is_passing { diff --git a/t/Test2/behavior/Formatter.t b/t/Test2/behavior/Formatter.t new file mode 100644 index 000000000..19ca2ea35 --- /dev/null +++ b/t/Test2/behavior/Formatter.t @@ -0,0 +1,70 @@ +use strict; +use warnings; + +BEGIN { require "t/tools.pl" } + +use Test2::API qw/intercept run_subtest test2_stack/; +use Test2::Event::Bail; + +{ + + package Formatter::Subclass; + use parent 'Test2::Formatter'; + use Test2::Util::HashBase qw{f t}; + + sub init { + my $self = shift; + $self->{+F} = []; + $self->{+T} = []; + } + + sub write { } + sub hide_buffered { 1 } + + sub terminate { + my $s = shift; + push @{$s->{+T}}, [@_]; + } + + sub finalize { + my $s = shift; + push @{$s->{+F}}, [@_]; + } +} + +{ + my $f = Formatter::Subclass->new; + intercept { + my $hub = test2_stack->top; + $hub->format($f); + is(1, 1, 'test event 1'); + is(2, 2, 'test event 2'); + is(3, 2, 'test event 3'); + done_testing; + }; + + is(scalar @{$f->f}, 1, 'finalize method was called on formatter'); + is_deeply( + $f->f->[0], + [3, 3, 1, 0], + 'finalize method received expected arguments' + ); + + ok(!@{$f->t}, 'terminate method was not called on formatter'); +} + +{ + my $f = Formatter::Subclass->new; + + intercept { + my $hub = test2_stack->top; + $hub->format($f); + $hub->send(Test2::Event::Bail->new(reason => 'everything is terrible')); + done_testing; + }; + + is(scalar @{$f->t}, 1, 'terminate method was called because of bail event'); + ok(!@{$f->f}, 'finalize method was not called on formatter'); +} + +done_testing; diff --git a/t/Test2/behavior/Subtest_buffer_formatter.t b/t/Test2/behavior/Subtest_buffer_formatter.t index 6aa0ffb6b..2fc58ef5f 100644 --- a/t/Test2/behavior/Subtest_buffer_formatter.t +++ b/t/Test2/behavior/Subtest_buffer_formatter.t @@ -8,14 +8,20 @@ use Test2::API qw/run_subtest intercept test2_stack/; { package Formatter::Hide; sub write { } - sub hide_buffered { 1 }; + sub hide_buffered { 1 } + sub terminate { } + sub finalize { } package Formatter::Show; sub write { } - sub hide_buffered { 0 }; + sub hide_buffered { 0 } + sub terminate { } + sub finalize { } package Formatter::NA; sub write { } + sub terminate { } + sub finalize { } } my %HAS_FORMATTER;