<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package TAP::Formatter::Base;

use strict;
use warnings;
use base 'TAP::Base';
use POSIX qw(strftime);

my $MAX_ERRORS = 5;
my %VALIDATION_FOR;

BEGIN {
    %VALIDATION_FOR = (
        directives =&gt; sub { shift; shift },
        verbosity  =&gt; sub { shift; shift },
        normalize  =&gt; sub { shift; shift },
        timer      =&gt; sub { shift; shift },
        failures   =&gt; sub { shift; shift },
        comments   =&gt; sub { shift; shift },
        errors     =&gt; sub { shift; shift },
        color      =&gt; sub { shift; shift },
        jobs       =&gt; sub { shift; shift },
        show_count =&gt; sub { shift; shift },
        stdout     =&gt; sub {
            my ( $self, $ref ) = @_;

            $self-&gt;_croak("option 'stdout' needs a filehandle")
              unless $self-&gt;_is_filehandle($ref);

            return $ref;
        },
    );

    sub _is_filehandle {
        my ( $self, $ref ) = @_;

        return 0 if !defined $ref;

        return 1 if ref $ref eq 'GLOB';    # lexical filehandle
        return 1 if !ref $ref &amp;&amp; ref \$ref eq 'GLOB'; # bare glob like *STDOUT

        return 1 if eval { $ref-&gt;can('print') };

        return 0;
    }

    my @getter_setters = qw(
      _longest
      _printed_summary_header
      _colorizer
    );

    __PACKAGE__-&gt;mk_methods( @getter_setters, keys %VALIDATION_FOR );
}

=head1 NAME

TAP::Formatter::Base - Base class for harness output delegates

=head1 VERSION

Version 3.48

=cut

our $VERSION = '3.48';

=head1 DESCRIPTION

This provides console orientated output formatting for TAP::Harness.

=head1 SYNOPSIS

 use TAP::Formatter::Console;
 my $harness = TAP::Formatter::Console-&gt;new( \%args );

=cut

sub _initialize {
    my ( $self, $arg_for ) = @_;
    $arg_for ||= {};

    $self-&gt;SUPER::_initialize($arg_for);
    my %arg_for = %$arg_for;    # force a shallow copy

    $self-&gt;verbosity(0);

    for my $name ( keys %VALIDATION_FOR ) {
        my $property = delete $arg_for{$name};
        if ( defined $property ) {
            my $validate = $VALIDATION_FOR{$name};
            $self-&gt;$name( $self-&gt;$validate($property) );
        }
    }

    if ( my @props = keys %arg_for ) {
        $self-&gt;_croak(
            "Unknown arguments to " . __PACKAGE__ . "::new (@props)" );
    }

    $self-&gt;stdout( \*STDOUT ) unless $self-&gt;stdout;

    if ( $self-&gt;color ) {
        require TAP::Formatter::Color;
        $self-&gt;_colorizer( TAP::Formatter::Color-&gt;new );
    }

    return $self;
}

sub verbose      { shift-&gt;verbosity &gt;= 1 }
sub quiet        { shift-&gt;verbosity &lt;= -1 }
sub really_quiet { shift-&gt;verbosity &lt;= -2 }
sub silent       { shift-&gt;verbosity &lt;= -3 }

=head1 METHODS

=head2 Class Methods

=head3 C&lt;new&gt;

 my %args = (
    verbose =&gt; 1,
 )
 my $harness = TAP::Formatter::Console-&gt;new( \%args );

The constructor returns a new C&lt;TAP::Formatter::Console&gt; object. If
a L&lt;TAP::Harness&gt; is created with no C&lt;formatter&gt; a
C&lt;TAP::Formatter::Console&gt; is automatically created. If any of the
following options were given to TAP::Harness-&gt;new they well be passed to
this constructor which accepts an optional hashref whose allowed keys are:

=over 4

=item * C&lt;verbosity&gt;

Set the verbosity level.

=item * C&lt;verbose&gt;

Printing individual test results to STDOUT.

=item * C&lt;timer&gt;

Append run time for each test to output. Uses L&lt;Time::HiRes&gt; if available.

=item * C&lt;failures&gt;

Show test failures (this is a no-op if C&lt;verbose&gt; is selected).

=item * C&lt;comments&gt;

Show test comments (this is a no-op if C&lt;verbose&gt; is selected).

=item * C&lt;quiet&gt;

Suppressing some test output (mostly failures while tests are running).

=item * C&lt;really_quiet&gt;

Suppressing everything but the tests summary.

=item * C&lt;silent&gt;

Suppressing all output.

=item * C&lt;errors&gt;

If parse errors are found in the TAP output, a note of this will be made
in the summary report.  To see all of the parse errors, set this argument to
true:

  errors =&gt; 1

=item * C&lt;directives&gt;

If set to a true value, only test results with directives will be displayed.
This overrides other settings such as C&lt;verbose&gt;, C&lt;failures&gt;, or C&lt;comments&gt;.

=item * C&lt;stdout&gt;

A filehandle for catching standard output.

=item * C&lt;color&gt;

If defined specifies whether color output is desired. If C&lt;color&gt; is not
defined it will default to color output if color support is available on
the current platform and output is not being redirected.

=item * C&lt;jobs&gt;

The number of concurrent jobs this formatter will handle.

=item * C&lt;show_count&gt;

Boolean value.  If false, disables the C&lt;X/Y&gt; test count which shows up while
tests are running.

=back

Any keys for which the value is C&lt;undef&gt; will be ignored.

=cut

# new supplied by TAP::Base

=head3 C&lt;prepare&gt;

Called by Test::Harness before any test output is generated. 

This is an advisory and may not be called in the case where tests are
being supplied to Test::Harness by an iterator.

=cut

sub prepare {
    my ( $self, @tests ) = @_;

    my $longest = 0;

    for my $test (@tests) {
        $longest = length $test if length $test &gt; $longest;
    }

    $self-&gt;_longest($longest);
}

sub _format_now { strftime "[%H:%M:%S]", localtime }

sub _format_name {
    my ( $self, $test ) = @_;
    my $name = $test;
    my $periods = '.' x ( $self-&gt;_longest + 2 - length $test );
    $periods = " $periods ";

    if ( $self-&gt;timer ) {
        my $stamp = $self-&gt;_format_now();
        return "$stamp $name$periods";
    }
    else {
        return "$name$periods";
    }

}

=head3 C&lt;open_test&gt;

Called to create a new test session. A test session looks like this:

    my $session = $formatter-&gt;open_test( $test, $parser );
    while ( defined( my $result = $parser-&gt;next ) ) {
        $session-&gt;result($result);
        exit 1 if $result-&gt;is_bailout;
    }
    $session-&gt;close_test;

=cut

sub open_test {
    die "Unimplemented.";
}

sub _output_success {
    my ( $self, $msg ) = @_;
    $self-&gt;_output($msg);
}

=head3 C&lt;summary&gt;

  $harness-&gt;summary( $aggregate );

C&lt;summary&gt; prints the summary report after all tests are run. The first
argument is an aggregate to summarise. An optional second argument may
be set to a true value to indicate that the summary is being output as a
result of an interrupted test run.

=cut

sub summary {
    my ( $self, $aggregate, $interrupted ) = @_;

    return if $self-&gt;silent;

    my @t     = $aggregate-&gt;descriptions;
    my $tests = \@t;

    my $runtime = $aggregate-&gt;elapsed_timestr;

    my $total  = $aggregate-&gt;total;
    my $passed = $aggregate-&gt;passed;

    if ( $self-&gt;timer ) {
        $self-&gt;_output( $self-&gt;_format_now(), "\n" );
    }

    $self-&gt;_failure_output("Test run interrupted!\n")
      if $interrupted;

    # TODO: Check this condition still works when all subtests pass but
    # the exit status is nonzero

    if ( $aggregate-&gt;all_passed ) {
        $self-&gt;_output_success("All tests successful.\n");
    }

    # ~TODO option where $aggregate-&gt;skipped generates reports
    if ( $total != $passed or $aggregate-&gt;has_problems ) {
        $self-&gt;_output("\nTest Summary Report");
        $self-&gt;_output("\n-------------------\n");
        for my $test (@$tests) {
            $self-&gt;_printed_summary_header(0);
            my ($parser) = $aggregate-&gt;parsers($test);
            $self-&gt;_output_summary_failure(
                'failed',
                [ '  Failed test:  ', '  Failed tests:  ' ],
                $test, $parser
            );
            $self-&gt;_output_summary_failure(
                'todo_passed',
                "  TODO passed:   ", $test, $parser
            );

            # ~TODO this cannot be the default
            #$self-&gt;_output_summary_failure( 'skipped', "  Tests skipped: " );

            if ( my $exit = $parser-&gt;exit ) {
                $self-&gt;_summary_test_header( $test, $parser );
                $self-&gt;_failure_output("  Non-zero exit status: $exit\n");
            }
            elsif ( my $wait = $parser-&gt;wait ) {
                $self-&gt;_summary_test_header( $test, $parser );
                $self-&gt;_failure_output("  Non-zero wait status: $wait\n");
            }

            if ( my @errors = $parser-&gt;parse_errors ) {
                my $explain;
                if ( @errors &gt; $MAX_ERRORS &amp;&amp; !$self-&gt;errors ) {
                    $explain
                      = "Displayed the first $MAX_ERRORS of "
                      . scalar(@errors)
                      . " TAP syntax errors.\n"
                      . "Re-run prove with the -p option to see them all.\n";
                    splice @errors, $MAX_ERRORS;
                }
                $self-&gt;_summary_test_header( $test, $parser );
                $self-&gt;_failure_output(
                    sprintf "  Parse errors: %s\n",
                    shift @errors
                );
                for my $error (@errors) {
                    my $spaces = ' ' x 16;
                    $self-&gt;_failure_output("$spaces$error\n");
                }
                $self-&gt;_failure_output($explain) if $explain;
            }
        }
    }
    my $files = @$tests;
    $self-&gt;_output("Files=$files, Tests=$total, $runtime\n");
    my $status = $aggregate-&gt;get_status;
    $self-&gt;_output("Result: $status\n");
}

sub _output_summary_failure {
    my ( $self, $method, $name, $test, $parser ) = @_;

    # ugly hack.  Must rethink this :(
    my $output = $method eq 'failed' ? '_failure_output' : '_output';

    if ( my @r = $parser-&gt;$method() ) {
        $self-&gt;_summary_test_header( $test, $parser );
        my ( $singular, $plural )
          = 'ARRAY' eq ref $name ? @$name : ( $name, $name );
        $self-&gt;$output( @r == 1 ? $singular : $plural );
        my @results = $self-&gt;_balanced_range( 40, @r );
        $self-&gt;$output( sprintf "%s\n" =&gt; shift @results );
        my $spaces = ' ' x 16;
        while (@results) {
            $self-&gt;$output( sprintf "$spaces%s\n" =&gt; shift @results );
        }
    }
}

sub _summary_test_header {
    my ( $self, $test, $parser ) = @_;
    return if $self-&gt;_printed_summary_header;
    my $spaces = ' ' x ( $self-&gt;_longest - length $test );
    $spaces = ' ' unless $spaces;
    my $output = $self-&gt;_get_output_method($parser);
    my $wait   = $parser-&gt;wait;

    if (defined $wait) {
        my $signum = $wait &amp; 0x7f;

        my $description;

        if ($signum) {
            require Config;
            my @names = split ' ', $Config::Config{'sig_name'};
            $description = "Signal: $names[$signum]";

            my $dumped = $wait &amp; 0x80;
            $description .= ', dumped core' if $dumped;
        }
        elsif ($wait != 0) {
            $description = sprintf 'exited %d', ($wait &gt;&gt; 8);
        }

        $wait .= " ($description)" if $wait != 0;
    }
    else {
        $wait = '(none)';
    }

    $self-&gt;$output(
        sprintf "$test$spaces(Wstat: %s Tests: %d Failed: %d)\n",
        $wait, $parser-&gt;tests_run, scalar $parser-&gt;failed
    );
    $self-&gt;_printed_summary_header(1);
}

sub _output {
    my $self = shift;

    print { $self-&gt;stdout } @_;
}

sub _failure_output {
    my $self = shift;

    $self-&gt;_output(@_);
}

sub _balanced_range {
    my ( $self, $limit, @range ) = @_;
    @range = $self-&gt;_range(@range);
    my $line = "";
    my @lines;
    my $curr = 0;
    while (@range) {
        if ( $curr &lt; $limit ) {
            my $range = ( shift @range ) . ", ";
            $line .= $range;
            $curr += length $range;
        }
        elsif (@range) {
            $line =~ s/, $//;
            push @lines =&gt; $line;
            $line = '';
            $curr = 0;
        }
    }
    if ($line) {
        $line =~ s/, $//;
        push @lines =&gt; $line;
    }
    return @lines;
}

sub _range {
    my ( $self, @numbers ) = @_;

    # shouldn't be needed, but subclasses might call this
    @numbers = sort { $a &lt;=&gt; $b } @numbers;
    my ( $min, @range );

    for my $i ( 0 .. $#numbers ) {
        my $num  = $numbers[$i];
        my $next = $numbers[ $i + 1 ];
        if ( defined $next &amp;&amp; $next == $num + 1 ) {
            if ( !defined $min ) {
                $min = $num;
            }
        }
        elsif ( defined $min ) {
            push @range =&gt; "$min-$num";
            undef $min;
        }
        else {
            push @range =&gt; $num;
        }
    }
    return @range;
}

sub _get_output_method {
    my ( $self, $parser ) = @_;
    return $parser-&gt;has_problems ? '_failure_output' : '_output';
}

1;
</pre></body></html>