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 },
23 show_count => sub { shift; shift },
25 my ( $self, $ref ) = @_;
26 $self->_croak("option 'stdout' needs a filehandle")
27 unless ( ref $ref || '' ) eq 'GLOB'
28 or eval { $ref->can('print') };
33 my @getter_setters = qw(
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.
183 =item * C<show_count>
185 Boolean value. If false, disables the C<X/Y> test count which shows up while
190 Any keys for which the value is C<undef> will be ignored.
194 # new supplied by TAP::Base
198 Called by Test::Harness before any test output is generated.
200 This is an advisory and may not be called in the case where tests are
201 being supplied to Test::Harness by an iterator.
206 my ( $self, @tests ) = @_;
210 foreach my $test (@tests) {
211 $longest = length $test if length $test > $longest;
214 $self->_longest($longest);
217 sub _format_now { strftime "[%H:%M:%S]", localtime }
220 my ( $self, $test ) = @_;
222 my $periods = '.' x ( $self->_longest + 4 - length $test );
224 if ( $self->timer ) {
225 my $stamp = $self->_format_now();
226 return "$stamp $name$periods";
229 return "$name$periods";
236 Called to create a new test session. A test session looks like this:
238 my $session = $formatter->open_test( $test, $parser );
239 while ( defined( my $result = $parser->next ) ) {
240 $session->result($result);
241 exit 1 if $result->is_bailout;
243 $session->close_test;
248 my ( $self, $test, $parser ) = @_;
252 ? 'TAP::Formatter::Console::ParallelSession'
253 : 'TAP::Formatter::Console::Session';
255 eval "require $class";
256 $self->_croak($@) if $@;
258 my $session = $class->new(
262 show_count => $self->show_count,
273 $harness->summary( $aggregate );
275 C<summary> prints the summary report after all tests are run. The argument is
281 my ( $self, $aggregate ) = @_;
283 return if $self->silent;
285 my @t = $aggregate->descriptions;
288 my $runtime = $aggregate->elapsed_timestr;
290 my $total = $aggregate->total;
291 my $passed = $aggregate->passed;
293 if ( $self->timer ) {
294 $self->_output( $self->_format_now(), "\n" );
297 # TODO: Check this condition still works when all subtests pass but
298 # the exit status is nonzero
300 if ( $aggregate->all_passed ) {
301 $self->_output("All tests successful.\n");
304 # ~TODO option where $aggregate->skipped generates reports
305 if ( $total != $passed or $aggregate->has_problems ) {
306 $self->_output("\nTest Summary Report");
307 $self->_output("\n-------------------\n");
308 foreach my $test (@$tests) {
309 $self->_printed_summary_header(0);
310 my ($parser) = $aggregate->parsers($test);
311 $self->_output_summary_failure(
313 [ ' Failed test: ', ' Failed tests: ' ],
316 $self->_output_summary_failure(
318 " TODO passed: ", $test, $parser
321 # ~TODO this cannot be the default
322 #$self->_output_summary_failure( 'skipped', " Tests skipped: " );
324 if ( my $exit = $parser->exit ) {
325 $self->_summary_test_header( $test, $parser );
326 $self->_failure_output(" Non-zero exit status: $exit\n");
329 if ( my @errors = $parser->parse_errors ) {
331 if ( @errors > $MAX_ERRORS && !$self->errors ) {
333 = "Displayed the first $MAX_ERRORS of "
335 . " TAP syntax errors.\n"
336 . "Re-run prove with the -p option to see them all.\n";
337 splice @errors, $MAX_ERRORS;
339 $self->_summary_test_header( $test, $parser );
340 $self->_failure_output(
341 sprintf " Parse errors: %s\n",
344 foreach my $error (@errors) {
345 my $spaces = ' ' x 16;
346 $self->_failure_output("$spaces$error\n");
348 $self->_failure_output($explain) if $explain;
353 $self->_output("Files=$files, Tests=$total, $runtime\n");
354 my $status = $aggregate->get_status;
355 $self->_output("Result: $status\n");
358 sub _output_summary_failure {
359 my ( $self, $method, $name, $test, $parser ) = @_;
361 # ugly hack. Must rethink this :(
362 my $output = $method eq 'failed' ? '_failure_output' : '_output';
364 if ( my @r = $parser->$method() ) {
365 $self->_summary_test_header( $test, $parser );
366 my ( $singular, $plural )
367 = 'ARRAY' eq ref $name ? @$name : ( $name, $name );
368 $self->$output( @r == 1 ? $singular : $plural );
369 my @results = $self->_balanced_range( 40, @r );
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';