Add built local::lib
[catagits/Gitalist.git] / local-lib5 / lib / perl5 / TAP / Formatter / Base.pm
CommitLineData
3fea05b9 1package TAP::Formatter::Base;
2
3use strict;
4use TAP::Base ();
5use POSIX qw(strftime);
6
7use vars qw($VERSION @ISA);
8
9my $MAX_ERRORS = 5;
10my %VALIDATION_FOR;
11
12BEGIN {
13 @ISA = qw(TAP::Base);
14
15 %VALIDATION_FOR = (
16 directives => sub { shift; shift },
17 verbosity => sub { shift; shift },
18 normalize => sub { shift; shift },
19 timer => sub { shift; shift },
20 failures => sub { shift; shift },
21 comments => sub { shift; shift },
22 errors => sub { shift; shift },
23 color => sub { shift; shift },
24 jobs => sub { shift; shift },
25 show_count => sub { shift; shift },
26 stdout => sub {
27 my ( $self, $ref ) = @_;
28 $self->_croak("option 'stdout' needs a filehandle")
29 unless ( ref $ref || '' ) eq 'GLOB'
30 or eval { $ref->can('print') };
31 return $ref;
32 },
33 );
34
35 my @getter_setters = qw(
36 _longest
37 _printed_summary_header
38 _colorizer
39 );
40
41 __PACKAGE__->mk_methods( @getter_setters, keys %VALIDATION_FOR );
42}
43
44=head1 NAME
45
46TAP::Formatter::Console - Harness output delegate for default console output
47
48=head1 VERSION
49
50Version 3.17
51
52=cut
53
54$VERSION = '3.17';
55
56=head1 DESCRIPTION
57
58This provides console orientated output formatting for TAP::Harness.
59
60=head1 SYNOPSIS
61
62 use TAP::Formatter::Console;
63 my $harness = TAP::Formatter::Console->new( \%args );
64
65=cut
66
67sub _initialize {
68 my ( $self, $arg_for ) = @_;
69 $arg_for ||= {};
70
71 $self->SUPER::_initialize($arg_for);
72 my %arg_for = %$arg_for; # force a shallow copy
73
74 $self->verbosity(0);
75
76 for my $name ( keys %VALIDATION_FOR ) {
77 my $property = delete $arg_for{$name};
78 if ( defined $property ) {
79 my $validate = $VALIDATION_FOR{$name};
80 $self->$name( $self->$validate($property) );
81 }
82 }
83
84 if ( my @props = keys %arg_for ) {
85 $self->_croak(
86 "Unknown arguments to " . __PACKAGE__ . "::new (@props)" );
87 }
88
89 $self->stdout( \*STDOUT ) unless $self->stdout;
90
91 if ( $self->color ) {
92 require TAP::Formatter::Color;
93 $self->_colorizer( TAP::Formatter::Color->new );
94 }
95
96 return $self;
97}
98
99sub verbose { shift->verbosity >= 1 }
100sub quiet { shift->verbosity <= -1 }
101sub really_quiet { shift->verbosity <= -2 }
102sub silent { shift->verbosity <= -3 }
103
104=head1 METHODS
105
106=head2 Class Methods
107
108=head3 C<new>
109
110 my %args = (
111 verbose => 1,
112 )
113 my $harness = TAP::Formatter::Console->new( \%args );
114
115The constructor returns a new C<TAP::Formatter::Console> object. If
116a L<TAP::Harness> is created with no C<formatter> a
117C<TAP::Formatter::Console> is automatically created. If any of the
118following options were given to TAP::Harness->new they well be passed to
119this constructor which accepts an optional hashref whose allowed keys are:
120
121=over 4
122
123=item * C<verbosity>
124
125Set the verbosity level.
126
127=item * C<verbose>
128
129Printing individual test results to STDOUT.
130
131=item * C<timer>
132
133Append run time for each test to output. Uses L<Time::HiRes> if available.
134
135=item * C<failures>
136
137Show test failures (this is a no-op if C<verbose> is selected).
138
139=item * C<comments>
140
141Show test comments (this is a no-op if C<verbose> is selected).
142
143=item * C<quiet>
144
145Suppressing some test output (mostly failures while tests are running).
146
147=item * C<really_quiet>
148
149Suppressing everything but the tests summary.
150
151=item * C<silent>
152
153Suppressing all output.
154
155=item * C<errors>
156
157If parse errors are found in the TAP output, a note of this will be made
158in the summary report. To see all of the parse errors, set this argument to
159true:
160
161 errors => 1
162
163=item * C<directives>
164
165If set to a true value, only test results with directives will be displayed.
166This overrides other settings such as C<verbose>, C<failures>, or C<comments>.
167
168=item * C<stdout>
169
170A filehandle for catching standard output.
171
172=item * C<color>
173
174If defined specifies whether color output is desired. If C<color> is not
175defined it will default to color output if color support is available on
176the current platform and output is not being redirected.
177
178=item * C<jobs>
179
180The number of concurrent jobs this formatter will handle.
181
182=item * C<show_count>
183
184Boolean value. If false, disables the C<X/Y> test count which shows up while
185tests are running.
186
187=back
188
189Any keys for which the value is C<undef> will be ignored.
190
191=cut
192
193# new supplied by TAP::Base
194
195=head3 C<prepare>
196
197Called by Test::Harness before any test output is generated.
198
199This is an advisory and may not be called in the case where tests are
200being supplied to Test::Harness by an iterator.
201
202=cut
203
204sub prepare {
205 my ( $self, @tests ) = @_;
206
207 my $longest = 0;
208
209 foreach my $test (@tests) {
210 $longest = length $test if length $test > $longest;
211 }
212
213 $self->_longest($longest);
214}
215
216sub _format_now { strftime "[%H:%M:%S]", localtime }
217
218sub _format_name {
219 my ( $self, $test ) = @_;
220 my $name = $test;
221 my $periods = '.' x ( $self->_longest + 2 - length $test );
222 $periods = " $periods ";
223
224 if ( $self->timer ) {
225 my $stamp = $self->_format_now();
226 return "$stamp $name$periods";
227 }
228 else {
229 return "$name$periods";
230 }
231
232}
233
234=head3 C<open_test>
235
236Called to create a new test session. A test session looks like this:
237
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;
242 }
243 $session->close_test;
244
245=cut
246
247sub open_test {
248 die "Unimplemented.";
249}
250
251sub _output_success {
252 my ( $self, $msg ) = @_;
253 $self->_output($msg);
254}
255
256=head3 C<summary>
257
258 $harness->summary( $aggregate );
259
260C<summary> prints the summary report after all tests are run. The argument is
261an aggregate.
262
263=cut
264
265sub summary {
266 my ( $self, $aggregate ) = @_;
267
268 return if $self->silent;
269
270 my @t = $aggregate->descriptions;
271 my $tests = \@t;
272
273 my $runtime = $aggregate->elapsed_timestr;
274
275 my $total = $aggregate->total;
276 my $passed = $aggregate->passed;
277
278 if ( $self->timer ) {
279 $self->_output( $self->_format_now(), "\n" );
280 }
281
282 # TODO: Check this condition still works when all subtests pass but
283 # the exit status is nonzero
284
285 if ( $aggregate->all_passed ) {
286 $self->_output_success("All tests successful.\n");
287 }
288
289 # ~TODO option where $aggregate->skipped generates reports
290 if ( $total != $passed or $aggregate->has_problems ) {
291 $self->_output("\nTest Summary Report");
292 $self->_output("\n-------------------\n");
293 foreach my $test (@$tests) {
294 $self->_printed_summary_header(0);
295 my ($parser) = $aggregate->parsers($test);
296 $self->_output_summary_failure(
297 'failed',
298 [ ' Failed test: ', ' Failed tests: ' ],
299 $test, $parser
300 );
301 $self->_output_summary_failure(
302 'todo_passed',
303 " TODO passed: ", $test, $parser
304 );
305
306 # ~TODO this cannot be the default
307 #$self->_output_summary_failure( 'skipped', " Tests skipped: " );
308
309 if ( my $exit = $parser->exit ) {
310 $self->_summary_test_header( $test, $parser );
311 $self->_failure_output(" Non-zero exit status: $exit\n");
312 }
313 elsif ( my $wait = $parser->wait ) {
314 $self->_summary_test_header( $test, $parser );
315 $self->_failure_output(" Non-zero wait status: $wait\n");
316 }
317
318 if ( my @errors = $parser->parse_errors ) {
319 my $explain;
320 if ( @errors > $MAX_ERRORS && !$self->errors ) {
321 $explain
322 = "Displayed the first $MAX_ERRORS of "
323 . scalar(@errors)
324 . " TAP syntax errors.\n"
325 . "Re-run prove with the -p option to see them all.\n";
326 splice @errors, $MAX_ERRORS;
327 }
328 $self->_summary_test_header( $test, $parser );
329 $self->_failure_output(
330 sprintf " Parse errors: %s\n",
331 shift @errors
332 );
333 foreach my $error (@errors) {
334 my $spaces = ' ' x 16;
335 $self->_failure_output("$spaces$error\n");
336 }
337 $self->_failure_output($explain) if $explain;
338 }
339 }
340 }
341 my $files = @$tests;
342 $self->_output("Files=$files, Tests=$total, $runtime\n");
343 my $status = $aggregate->get_status;
344 $self->_output("Result: $status\n");
345}
346
347sub _output_summary_failure {
348 my ( $self, $method, $name, $test, $parser ) = @_;
349
350 # ugly hack. Must rethink this :(
351 my $output = $method eq 'failed' ? '_failure_output' : '_output';
352
353 if ( my @r = $parser->$method() ) {
354 $self->_summary_test_header( $test, $parser );
355 my ( $singular, $plural )
356 = 'ARRAY' eq ref $name ? @$name : ( $name, $name );
357 $self->$output( @r == 1 ? $singular : $plural );
358 my @results = $self->_balanced_range( 40, @r );
359 $self->$output( sprintf "%s\n" => shift @results );
360 my $spaces = ' ' x 16;
361 while (@results) {
362 $self->$output( sprintf "$spaces%s\n" => shift @results );
363 }
364 }
365}
366
367sub _summary_test_header {
368 my ( $self, $test, $parser ) = @_;
369 return if $self->_printed_summary_header;
370 my $spaces = ' ' x ( $self->_longest - length $test );
371 $spaces = ' ' unless $spaces;
372 my $output = $self->_get_output_method($parser);
373 $self->$output(
374 sprintf "$test$spaces(Wstat: %d Tests: %d Failed: %d)\n",
375 $parser->wait, $parser->tests_run, scalar $parser->failed
376 );
377 $self->_printed_summary_header(1);
378}
379
380sub _output {
381 my $self = shift;
382
383 print { $self->stdout } @_;
384}
385
386sub _failure_output {
387 my $self = shift;
388
389 $self->_output(@_);
390}
391
392sub _balanced_range {
393 my ( $self, $limit, @range ) = @_;
394 @range = $self->_range(@range);
395 my $line = "";
396 my @lines;
397 my $curr = 0;
398 while (@range) {
399 if ( $curr < $limit ) {
400 my $range = ( shift @range ) . ", ";
401 $line .= $range;
402 $curr += length $range;
403 }
404 elsif (@range) {
405 $line =~ s/, $//;
406 push @lines => $line;
407 $line = '';
408 $curr = 0;
409 }
410 }
411 if ($line) {
412 $line =~ s/, $//;
413 push @lines => $line;
414 }
415 return @lines;
416}
417
418sub _range {
419 my ( $self, @numbers ) = @_;
420
421 # shouldn't be needed, but subclasses might call this
422 @numbers = sort { $a <=> $b } @numbers;
423 my ( $min, @range );
424
425 foreach my $i ( 0 .. $#numbers ) {
426 my $num = $numbers[$i];
427 my $next = $numbers[ $i + 1 ];
428 if ( defined $next && $next == $num + 1 ) {
429 if ( !defined $min ) {
430 $min = $num;
431 }
432 }
433 elsif ( defined $min ) {
434 push @range => "$min-$num";
435 undef $min;
436 }
437 else {
438 push @range => $num;
439 }
440 }
441 return @range;
442}
443
444sub _get_output_method {
445 my ( $self, $parser ) = @_;
446 return $parser->has_problems ? '_failure_output' : '_output';
447}
448
4491;