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(
316 [ ' Failed test: ', ' Failed tests: ' ],
319 $self->_output_summary_failure(
321 " TODO passed: ", $test, $parser
324 # ~TODO this cannot be the default
325 #$self->_output_summary_failure( 'skipped', " Tests skipped: " );
327 if ( my $exit = $parser->exit ) {
328 $self->_summary_test_header( $test, $parser );
329 $self->_failure_output(" Non-zero exit status: $exit\n");
332 if ( my @errors = $parser->parse_errors ) {
334 if ( @errors > $MAX_ERRORS && !$self->errors ) {
336 = "Displayed the first $MAX_ERRORS of "
338 . " TAP syntax errors.\n"
339 . "Re-run prove with the -p option to see them all.\n";
340 splice @errors, $MAX_ERRORS;
342 $self->_summary_test_header( $test, $parser );
343 $self->_failure_output(
344 sprintf " Parse errors: %s\n",
347 foreach my $error (@errors) {
348 my $spaces = ' ' x 16;
349 $self->_failure_output("$spaces$error\n");
351 $self->_failure_output($explain) if $explain;
356 $self->_output("Files=$files, Tests=$total, $runtime\n");
357 my $status = $aggregate->get_status;
358 $self->_output("Result: $status\n");
361 sub _output_summary_failure {
362 my ( $self, $method, $name, $test, $parser ) = @_;
364 # ugly hack. Must rethink this :(
365 my $output = $method eq 'failed' ? '_failure_output' : '_output';
367 if ( my @r = $parser->$method() ) {
368 $self->_summary_test_header( $test, $parser );
369 my ( $singular, $plural )
370 = 'ARRAY' eq ref $name ? @$name : ( $name, $name );
371 $self->$output( @r == 1 ? $singular : $plural );
372 my @results = $self->_balanced_range( 40, @r );
373 $self->$output( sprintf "%s\n" => shift @results );
374 my $spaces = ' ' x 16;
376 $self->$output( sprintf "$spaces%s\n" => shift @results );
381 sub _summary_test_header {
382 my ( $self, $test, $parser ) = @_;
383 return if $self->_printed_summary_header;
384 my $spaces = ' ' x ( $self->_longest - length $test );
385 $spaces = ' ' unless $spaces;
386 my $output = $self->_get_output_method($parser);
388 sprintf "$test$spaces(Wstat: %d Tests: %d Failed: %d)\n",
389 $parser->wait, $parser->tests_run, scalar $parser->failed
391 $self->_printed_summary_header(1);
397 print { $self->stdout } @_;
400 # Use _colorizer delegate to set output color. NOP if we have no delegate
402 my ( $self, @colors ) = @_;
403 if ( my $colorizer = $self->_colorizer ) {
404 my $output_func = $self->{_output_func} ||= sub {
407 $colorizer->set_color( $output_func, $_ ) for @colors;
411 sub _failure_output {
413 $self->_set_colors('red');
414 my $out = join '', @_;
415 my $has_newline = chomp $out;
416 $self->_output($out);
417 $self->_set_colors('reset');
422 sub _balanced_range {
423 my ( $self, $limit, @range ) = @_;
424 @range = $self->_range(@range);
429 if ( $curr < $limit ) {
430 my $range = ( shift @range ) . ", ";
432 $curr += length $range;
436 push @lines => $line;
443 push @lines => $line;
449 my ( $self, @numbers ) = @_;
451 # shouldn't be needed, but subclasses might call this
452 @numbers = sort { $a <=> $b } @numbers;
455 foreach my $i ( 0 .. $#numbers ) {
456 my $num = $numbers[$i];
457 my $next = $numbers[ $i + 1 ];
458 if ( defined $next && $next == $num + 1 ) {
459 if ( !defined $min ) {
463 elsif ( defined $min ) {
464 push @range => "$min-$num";
474 sub _get_output_method {
475 my ( $self, $parser ) = @_;
476 return $parser->has_problems ? '_failure_output' : '_output';