Skip to content

Commit

Permalink
Add ->terminate and ->finalize methods to the Formatter
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
autarch committed Sep 22, 2016
1 parent d61c4ad commit 8117cb4
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 6 deletions.
6 changes: 6 additions & 0 deletions 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
Expand Down
31 changes: 31 additions & 0 deletions lib/Test2/Formatter.pm
Expand Up @@ -16,6 +16,10 @@ sub import {

sub hide_buffered { 1 }

sub terminate { }

sub finalize { }

1;

__END__
Expand Down Expand Up @@ -48,6 +52,10 @@ A formatter is any package or object with a C<write($event, $num)> method.
sub hide_buffered { 1 }
sub terminate { }
sub finalize { }
1;
The C<write> method is a method, so it either gets a class or instance. The two
Expand All @@ -60,6 +68,29 @@ The C<hide_buffered()> 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<Test2::API/"run_subtest(...)"> for more information.
The C<terminate> and C<finalize> 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<terminate> method is called when an event's C<terminate> method returns
true, for example when a L<Test2::Event::Plan> has a C<'skip_all'> plan, or
when a L<Test2::Event::Bail> event is sent. The C<terminate> method is passed
a single argument, the L<Test2::Event> object which triggered the terminate.
The C<finalize> method is always the last thing called on the formatter, I<<
except when C<terminate> 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
Expand Down
18 changes: 14 additions & 4 deletions lib/Test2/Hub.pm
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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}) {
Expand Down Expand Up @@ -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 {
Expand Down
70 changes: 70 additions & 0 deletions 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;
10 changes: 8 additions & 2 deletions t/Test2/behavior/Subtest_buffer_formatter.t
Expand Up @@ -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;
Expand Down

0 comments on commit 8117cb4

Please sign in to comment.