1 package TAP::Formatter::Console;
5 use POSIX qw(strftime);
7 use vars qw($VERSION @ISA);
16 directives => sub { shift; shift },
17 verbosity => sub { shift; shift },
18 timer => sub { shift; shift },
19 failures => sub { shift; shift },
20 errors => sub { shift; shift },
21 color => sub { shift; shift },
22 jobs => sub { shift; shift },
24 my ( $self, $ref ) = @_;
25 $self->_croak("option 'stdout' needs a filehandle")
26 unless ( ref $ref || '' ) eq 'GLOB'
27 or eval { $ref->can('print') };
32 my @getter_setters = qw(
34 _tests_without_extensions
35 _printed_summary_header
39 for my $method ( @getter_setters, keys %VALIDATION_FOR ) {
43 return $self->{$method} unless @_;
44 $self->{$method} = shift;
51 TAP::Formatter::Console - Harness output delegate for default console output
63 This provides console orientated output formatting for TAP::Harness.
67 use TAP::Formatter::Console;
68 my $harness = TAP::Formatter::Console->new( \%args );
73 my ( $self, $arg_for ) = @_;
76 $self->SUPER::_initialize($arg_for);
77 my %arg_for = %$arg_for; # force a shallow copy
81 for my $name ( keys %VALIDATION_FOR ) {
82 my $property = delete $arg_for{$name};
83 if ( defined $property ) {
84 my $validate = $VALIDATION_FOR{$name};
85 $self->$name( $self->$validate($property) );
89 if ( my @props = keys %arg_for ) {
91 "Unknown arguments to " . __PACKAGE__ . "::new (@props)" );
94 $self->stdout( \*STDOUT ) unless $self->stdout;
97 require TAP::Formatter::Color;
98 $self->_colorizer( TAP::Formatter::Color->new );
104 sub verbose { shift->verbosity >= 1 }
105 sub quiet { shift->verbosity <= -1 }
106 sub really_quiet { shift->verbosity <= -2 }
107 sub silent { shift->verbosity <= -3 }
118 my $harness = TAP::Formatter::Console->new( \%args );
120 The constructor returns a new C<TAP::Formatter::Console> object. If
121 a L<TAP::Harness> is created with no C<formatter> a
122 C<TAP::Formatter::Console> is automatically created. If any of the
123 following options were given to TAP::Harness->new they well be passed to
124 this constructor which accepts an optional hashref whose allowed keys are:
130 Set the verbosity level.
134 Printing individual test results to STDOUT.
138 Append run time for each test to output. Uses L<Time::HiRes> if available.
142 Only show test failures (this is a no-op if C<verbose> is selected).
146 Suppressing some test output (mostly failures while tests are running).
148 =item * C<really_quiet>
150 Suppressing everything but the tests summary.
154 Suppressing all output.
158 If parse errors are found in the TAP output, a note of this will be made
159 in the summary report. To see all of the parse errors, set this argument to
164 =item * C<directives>
166 If set to a true value, only test results with directives will be displayed.
167 This overrides other settings such as C<verbose> or C<failures>.
171 A filehandle for catching standard output.
175 If defined specifies whether color output is desired. If C<color> is not
176 defined it will default to color output if color support is available on
177 the current platform and output is not being redirected.
181 The number of concurrent jobs this formatter will handle.
185 Any keys for which the value is C<undef> will be ignored.
189 # new supplied by TAP::Base
193 Called by Test::Harness before any test output is generated.
198 my ( $self, @tests ) = @_;
202 my $tests_without_extensions = 0;
203 foreach my $test (@tests) {
204 $longest = length $test if length $test > $longest;
205 if ( $test !~ /\.\w+$/ ) {
208 $tests_without_extensions = 1;
212 $self->_tests_without_extensions($tests_without_extensions);
213 $self->_longest($longest);
216 sub _format_now { strftime "[%H:%M:%S]", localtime }
219 my ( $self, $test ) = @_;
222 unless ( $self->_tests_without_extensions ) {
223 $name =~ s/(\.\w+)$//; # strip the .t or .pm
226 my $periods = '.' x ( $self->_longest + $extra + 4 - length $test );
228 if ( $self->timer ) {
229 my $stamp = $self->_format_now();
230 return "$stamp $name$periods";
233 return "$name$periods";
240 Called to create a new test session. A test session looks like this:
242 my $session = $formatter->open_test( $test, $parser );
243 while ( defined( my $result = $parser->next ) ) {
244 $session->result($result);
245 exit 1 if $result->is_bailout;
247 $session->close_test;
252 my ( $self, $test, $parser ) = @_;
256 ? 'TAP::Formatter::Console::ParallelSession'
257 : 'TAP::Formatter::Console::Session';
259 eval "require $class";
260 $self->_croak($@) if $@;
262 my $session = $class->new(
276 $harness->summary( $aggregate );
278 C<summary> prints the summary report after all tests are run. The argument is
284 my ( $self, $aggregate ) = @_;
286 return if $self->silent;
288 my @t = $aggregate->descriptions;
291 my $runtime = $aggregate->elapsed_timestr;
293 my $total = $aggregate->total;
294 my $passed = $aggregate->passed;
296 if ( $self->timer ) {
297 $self->_output( $self->_format_now(), "\n" );
300 # TODO: Check this condition still works when all subtests pass but
301 # the exit status is nonzero
303 if ( $aggregate->all_passed ) {
304 $self->_output("All tests successful.\n");
307 # ~TODO option where $aggregate->skipped generates reports
308 if ( $total != $passed or $aggregate->has_problems ) {
309 $self->_output("\nTest Summary Report");
310 $self->_output("\n-------------------\n");
311 foreach my $test (@$tests) {
312 $self->_printed_summary_header(0);
313 my ($parser) = $aggregate->parsers($test);
314 $self->_output_summary_failure(
315 'failed', " Failed test number(s): ",
318 $self->_output_summary_failure(
320 " TODO passed: ", $test, $parser
323 # ~TODO this cannot be the default
324 #$self->_output_summary_failure( 'skipped', " Tests skipped: " );
326 if ( my $exit = $parser->exit ) {
327 $self->_summary_test_header( $test, $parser );
328 $self->_failure_output(" Non-zero exit status: $exit\n");
331 if ( my @errors = $parser->parse_errors ) {
333 if ( @errors > $MAX_ERRORS && !$self->errors ) {
335 = "Displayed the first $MAX_ERRORS of "
337 . " TAP syntax errors.\n"
338 . "Re-run prove with the -p option to see them all.\n";
339 splice @errors, $MAX_ERRORS;
341 $self->_summary_test_header( $test, $parser );
342 $self->_failure_output(
343 sprintf " Parse errors: %s\n",
346 foreach my $error (@errors) {
347 my $spaces = ' ' x 16;
348 $self->_failure_output("$spaces$error\n");
350 $self->_failure_output($explain) if $explain;
355 $self->_output("Files=$files, Tests=$total, $runtime\n");
356 my $status = $aggregate->get_status;
357 $self->_output("Result: $status\n");
360 sub _output_summary_failure {
361 my ( $self, $method, $name, $test, $parser ) = @_;
363 # ugly hack. Must rethink this :(
364 my $output = $method eq 'failed' ? '_failure_output' : '_output';
366 if ( $parser->$method() ) {
367 $self->_summary_test_header( $test, $parser );
368 $self->$output($name);
369 my @results = $self->_balanced_range( 40, $parser->$method() );
370 $self->$output( sprintf "%s\n" => shift @results );
371 my $spaces = ' ' x 16;
373 $self->$output( sprintf "$spaces%s\n" => shift @results );
378 sub _summary_test_header {
379 my ( $self, $test, $parser ) = @_;
380 return if $self->_printed_summary_header;
381 my $spaces = ' ' x ( $self->_longest - length $test );
382 $spaces = ' ' unless $spaces;
383 my $output = $self->_get_output_method($parser);
385 sprintf "$test$spaces(Wstat: %d Tests: %d Failed: %d)\n",
386 $parser->wait, $parser->tests_run, scalar $parser->failed
388 $self->_printed_summary_header(1);
394 print { $self->stdout } @_;
397 # Use _colorizer delegate to set output color. NOP if we have no delegate
399 my ( $self, @colors ) = @_;
400 if ( my $colorizer = $self->_colorizer ) {
401 my $output_func = $self->{_output_func} ||= sub {
404 $colorizer->set_color( $output_func, $_ ) for @colors;
408 sub _failure_output {
410 $self->_set_colors('red');
411 my $out = join '', @_;
412 my $has_newline = chomp $out;
413 $self->_output($out);
414 $self->_set_colors('reset');
419 sub _balanced_range {
420 my ( $self, $limit, @range ) = @_;
421 @range = $self->_range(@range);
426 if ( $curr < $limit ) {
427 my $range = ( shift @range ) . ", ";
429 $curr += length $range;
433 push @lines => $line;
440 push @lines => $line;
446 my ( $self, @numbers ) = @_;
448 # shouldn't be needed, but subclasses might call this
449 @numbers = sort { $a <=> $b } @numbers;
452 foreach my $i ( 0 .. $#numbers ) {
453 my $num = $numbers[$i];
454 my $next = $numbers[ $i + 1 ];
455 if ( defined $next && $next == $num + 1 ) {
456 if ( !defined $min ) {
460 elsif ( defined $min ) {
461 push @range => "$min-$num";
471 sub _get_output_method {
472 my ( $self, $parser ) = @_;
473 return $parser->has_problems ? '_failure_output' : '_output';