1b39c5158Smillertpackage TAP::Parser::Aggregator; 2b39c5158Smillert 3b39c5158Smillertuse strict; 46fb12b70Safresh1use warnings; 5b39c5158Smillertuse Benchmark; 6b39c5158Smillert 76fb12b70Safresh1use base 'TAP::Object'; 8b39c5158Smillert 9b39c5158Smillert=head1 NAME 10b39c5158Smillert 11b39c5158SmillertTAP::Parser::Aggregator - Aggregate TAP::Parser results 12b39c5158Smillert 13b39c5158Smillert=head1 VERSION 14b39c5158Smillert 15*3d61058aSafresh1Version 3.48 16b39c5158Smillert 17b39c5158Smillert=cut 18b39c5158Smillert 19*3d61058aSafresh1our $VERSION = '3.48'; 20b39c5158Smillert 21b39c5158Smillert=head1 SYNOPSIS 22b39c5158Smillert 23b39c5158Smillert use TAP::Parser::Aggregator; 24b39c5158Smillert 25b39c5158Smillert my $aggregate = TAP::Parser::Aggregator->new; 26b39c5158Smillert $aggregate->add( 't/00-load.t', $load_parser ); 27b39c5158Smillert $aggregate->add( 't/10-lex.t', $lex_parser ); 28b39c5158Smillert 29b39c5158Smillert my $summary = <<'END_SUMMARY'; 30b39c5158Smillert Passed: %s 31b39c5158Smillert Failed: %s 32b39c5158Smillert Unexpectedly succeeded: %s 33b39c5158Smillert END_SUMMARY 34b39c5158Smillert printf $summary, 35b39c5158Smillert scalar $aggregate->passed, 36b39c5158Smillert scalar $aggregate->failed, 37b39c5158Smillert scalar $aggregate->todo_passed; 38b39c5158Smillert 39b39c5158Smillert=head1 DESCRIPTION 40b39c5158Smillert 41b39c5158SmillertC<TAP::Parser::Aggregator> collects parser objects and allows 42b39c5158Smillertreporting/querying their aggregate results. 43b39c5158Smillert 44b39c5158Smillert=head1 METHODS 45b39c5158Smillert 46b39c5158Smillert=head2 Class Methods 47b39c5158Smillert 48b39c5158Smillert=head3 C<new> 49b39c5158Smillert 50b39c5158Smillert my $aggregate = TAP::Parser::Aggregator->new; 51b39c5158Smillert 52b39c5158SmillertReturns a new C<TAP::Parser::Aggregator> object. 53b39c5158Smillert 54b39c5158Smillert=cut 55b39c5158Smillert 56b39c5158Smillert# new() implementation supplied by TAP::Object 57b39c5158Smillert 58b39c5158Smillertmy %SUMMARY_METHOD_FOR; 59b39c5158Smillert 60b39c5158SmillertBEGIN { # install summary methods 61b39c5158Smillert %SUMMARY_METHOD_FOR = map { $_ => $_ } qw( 62b39c5158Smillert failed 63b39c5158Smillert parse_errors 64b39c5158Smillert passed 65b39c5158Smillert skipped 66b39c5158Smillert todo 67b39c5158Smillert todo_passed 68b39c5158Smillert total 69b39c5158Smillert wait 70b39c5158Smillert exit 71b39c5158Smillert ); 72b39c5158Smillert $SUMMARY_METHOD_FOR{total} = 'tests_run'; 73b39c5158Smillert $SUMMARY_METHOD_FOR{planned} = 'tests_planned'; 74b39c5158Smillert 75898184e3Ssthen for my $method ( keys %SUMMARY_METHOD_FOR ) { 76b39c5158Smillert next if 'total' eq $method; 77b39c5158Smillert no strict 'refs'; 78b39c5158Smillert *$method = sub { 79b39c5158Smillert my $self = shift; 80b39c5158Smillert return wantarray 81b39c5158Smillert ? @{ $self->{"descriptions_for_$method"} } 82b39c5158Smillert : $self->{$method}; 83b39c5158Smillert }; 84b39c5158Smillert } 85b39c5158Smillert} # end install summary methods 86b39c5158Smillert 87b39c5158Smillertsub _initialize { 88b39c5158Smillert my ($self) = @_; 89b39c5158Smillert $self->{parser_for} = {}; 90b39c5158Smillert $self->{parse_order} = []; 91898184e3Ssthen for my $summary ( keys %SUMMARY_METHOD_FOR ) { 92b39c5158Smillert $self->{$summary} = 0; 93b39c5158Smillert next if 'total' eq $summary; 94b39c5158Smillert $self->{"descriptions_for_$summary"} = []; 95b39c5158Smillert } 96b39c5158Smillert return $self; 97b39c5158Smillert} 98b39c5158Smillert 99b39c5158Smillert############################################################################## 100b39c5158Smillert 101b39c5158Smillert=head2 Instance Methods 102b39c5158Smillert 103b39c5158Smillert=head3 C<add> 104b39c5158Smillert 105b39c5158Smillert $aggregate->add( $description => $parser ); 106b39c5158Smillert 107b39c5158SmillertThe C<$description> is usually a test file name (but only by 108b39c5158Smillertconvention.) It is used as a unique identifier (see e.g. 109b39c5158SmillertL<"parsers">.) Reusing a description is a fatal error. 110b39c5158Smillert 111b39c5158SmillertThe C<$parser> is a L<TAP::Parser|TAP::Parser> object. 112b39c5158Smillert 113b39c5158Smillert=cut 114b39c5158Smillert 115b39c5158Smillertsub add { 116b39c5158Smillert my ( $self, $description, $parser ) = @_; 117b39c5158Smillert if ( exists $self->{parser_for}{$description} ) { 118b39c5158Smillert $self->_croak( "You already have a parser for ($description)." 119b39c5158Smillert . " Perhaps you have run the same test twice." ); 120b39c5158Smillert } 121b39c5158Smillert push @{ $self->{parse_order} } => $description; 122b39c5158Smillert $self->{parser_for}{$description} = $parser; 123b39c5158Smillert 124b39c5158Smillert while ( my ( $summary, $method ) = each %SUMMARY_METHOD_FOR ) { 125b39c5158Smillert 126b39c5158Smillert # Slightly nasty. Instead we should maybe have 'cooked' accessors 127b39c5158Smillert # for results that may be masked by the parser. 128b39c5158Smillert next 129b39c5158Smillert if ( $method eq 'exit' || $method eq 'wait' ) 130b39c5158Smillert && $parser->ignore_exit; 131b39c5158Smillert 132b39c5158Smillert if ( my $count = $parser->$method() ) { 133b39c5158Smillert $self->{$summary} += $count; 134b39c5158Smillert push @{ $self->{"descriptions_for_$summary"} } => $description; 135b39c5158Smillert } 136b39c5158Smillert } 137b39c5158Smillert 138b39c5158Smillert return $self; 139b39c5158Smillert} 140b39c5158Smillert 141b39c5158Smillert############################################################################## 142b39c5158Smillert 143b39c5158Smillert=head3 C<parsers> 144b39c5158Smillert 145b39c5158Smillert my $count = $aggregate->parsers; 146b39c5158Smillert my @parsers = $aggregate->parsers; 147b39c5158Smillert my @parsers = $aggregate->parsers(@descriptions); 148b39c5158Smillert 149b39c5158SmillertIn scalar context without arguments, this method returns the number of parsers 150b39c5158Smillertaggregated. In list context without arguments, returns the parsers in the 151b39c5158Smillertorder they were added. 152b39c5158Smillert 153b39c5158SmillertIf C<@descriptions> is given, these correspond to the keys used in each 154b39c5158Smillertcall to the add() method. Returns an array of the requested parsers (in 155b39c5158Smillertthe requested order) in list context or an array reference in scalar 156b39c5158Smillertcontext. 157b39c5158Smillert 158b39c5158SmillertRequesting an unknown identifier is a fatal error. 159b39c5158Smillert 160b39c5158Smillert=cut 161b39c5158Smillert 162b39c5158Smillertsub parsers { 163b39c5158Smillert my $self = shift; 164b39c5158Smillert return $self->_get_parsers(@_) if @_; 165b39c5158Smillert my $descriptions = $self->{parse_order}; 166b39c5158Smillert my @parsers = @{ $self->{parser_for} }{@$descriptions}; 167b39c5158Smillert 168b39c5158Smillert # Note: Because of the way context works, we must assign the parsers to 169b39c5158Smillert # the @parsers array or else this method does not work as documented. 170b39c5158Smillert return @parsers; 171b39c5158Smillert} 172b39c5158Smillert 173b39c5158Smillertsub _get_parsers { 174b39c5158Smillert my ( $self, @descriptions ) = @_; 175b39c5158Smillert my @parsers; 176898184e3Ssthen for my $description (@descriptions) { 177b39c5158Smillert $self->_croak("A parser for ($description) could not be found") 178b39c5158Smillert unless exists $self->{parser_for}{$description}; 179b39c5158Smillert push @parsers => $self->{parser_for}{$description}; 180b39c5158Smillert } 181b39c5158Smillert return wantarray ? @parsers : \@parsers; 182b39c5158Smillert} 183b39c5158Smillert 184b39c5158Smillert=head3 C<descriptions> 185b39c5158Smillert 186b39c5158SmillertGet an array of descriptions in the order in which they were added to 187b39c5158Smillertthe aggregator. 188b39c5158Smillert 189b39c5158Smillert=cut 190b39c5158Smillert 191b39c5158Smillertsub descriptions { @{ shift->{parse_order} || [] } } 192b39c5158Smillert 193b39c5158Smillert=head3 C<start> 194b39c5158Smillert 195b39c5158SmillertCall C<start> immediately before adding any results to the aggregator. 196b39c5158SmillertAmong other times it records the start time for the test run. 197b39c5158Smillert 198b39c5158Smillert=cut 199b39c5158Smillert 200b39c5158Smillertsub start { 201b39c5158Smillert my $self = shift; 202b39c5158Smillert $self->{start_time} = Benchmark->new; 203b39c5158Smillert} 204b39c5158Smillert 205b39c5158Smillert=head3 C<stop> 206b39c5158Smillert 207b39c5158SmillertCall C<stop> immediately after adding all test results to the aggregator. 208b39c5158Smillert 209b39c5158Smillert=cut 210b39c5158Smillert 211b39c5158Smillertsub stop { 212b39c5158Smillert my $self = shift; 213b39c5158Smillert $self->{end_time} = Benchmark->new; 214b39c5158Smillert} 215b39c5158Smillert 216b39c5158Smillert=head3 C<elapsed> 217b39c5158Smillert 218b39c5158SmillertElapsed returns a L<Benchmark> object that represents the running time 219b39c5158Smillertof the aggregated tests. In order for C<elapsed> to be valid you must 220b39c5158Smillertcall C<start> before running the tests and C<stop> immediately 221b39c5158Smillertafterwards. 222b39c5158Smillert 223b39c5158Smillert=cut 224b39c5158Smillert 225b39c5158Smillertsub elapsed { 226b39c5158Smillert my $self = shift; 227b39c5158Smillert 228b39c5158Smillert require Carp; 229b39c5158Smillert Carp::croak 230b39c5158Smillert q{Can't call elapsed without first calling start and then stop} 231b39c5158Smillert unless defined $self->{start_time} && defined $self->{end_time}; 232b39c5158Smillert return timediff( $self->{end_time}, $self->{start_time} ); 233b39c5158Smillert} 234b39c5158Smillert 235b39c5158Smillert=head3 C<elapsed_timestr> 236b39c5158Smillert 237b39c5158SmillertReturns a formatted string representing the runtime returned by 238b39c5158SmillertC<elapsed()>. This lets the caller not worry about Benchmark. 239b39c5158Smillert 240b39c5158Smillert=cut 241b39c5158Smillert 242b39c5158Smillertsub elapsed_timestr { 243b39c5158Smillert my $self = shift; 244b39c5158Smillert 245b39c5158Smillert my $elapsed = $self->elapsed; 246b39c5158Smillert 247b39c5158Smillert return timestr($elapsed); 248b39c5158Smillert} 249b39c5158Smillert 250b39c5158Smillert=head3 C<all_passed> 251b39c5158Smillert 252b39c5158SmillertReturn true if all the tests passed and no parse errors were detected. 253b39c5158Smillert 254b39c5158Smillert=cut 255b39c5158Smillert 256b39c5158Smillertsub all_passed { 257b39c5158Smillert my $self = shift; 258b39c5158Smillert return 259b39c5158Smillert $self->total 260b39c5158Smillert && $self->total == $self->passed 261b39c5158Smillert && !$self->has_errors; 262b39c5158Smillert} 263b39c5158Smillert 264b39c5158Smillert=head3 C<get_status> 265b39c5158Smillert 266b39c5158SmillertGet a single word describing the status of the aggregated tests. 267b39c5158SmillertDepending on the outcome of the tests returns 'PASS', 'FAIL' or 268b39c5158Smillert'NOTESTS'. This token is understood by L<CPAN::Reporter>. 269b39c5158Smillert 270b39c5158Smillert=cut 271b39c5158Smillert 272b39c5158Smillertsub get_status { 273b39c5158Smillert my $self = shift; 274b39c5158Smillert 275b39c5158Smillert my $total = $self->total; 276b39c5158Smillert my $passed = $self->passed; 277b39c5158Smillert 278b39c5158Smillert return 279b39c5158Smillert ( $self->has_errors || $total != $passed ) ? 'FAIL' 280b39c5158Smillert : $total ? 'PASS' 281b39c5158Smillert : 'NOTESTS'; 282b39c5158Smillert} 283b39c5158Smillert 284b39c5158Smillert############################################################################## 285b39c5158Smillert 286b39c5158Smillert=head2 Summary methods 287b39c5158Smillert 288b39c5158SmillertEach of the following methods will return the total number of corresponding 289b39c5158Smillerttests if called in scalar context. If called in list context, returns the 290b39c5158Smillertdescriptions of the parsers which contain the corresponding tests (see C<add> 291b39c5158Smillertfor an explanation of description. 292b39c5158Smillert 293b39c5158Smillert=over 4 294b39c5158Smillert 295b39c5158Smillert=item * failed 296b39c5158Smillert 297b39c5158Smillert=item * parse_errors 298b39c5158Smillert 299b39c5158Smillert=item * passed 300b39c5158Smillert 301b39c5158Smillert=item * planned 302b39c5158Smillert 303b39c5158Smillert=item * skipped 304b39c5158Smillert 305b39c5158Smillert=item * todo 306b39c5158Smillert 307b39c5158Smillert=item * todo_passed 308b39c5158Smillert 309b39c5158Smillert=item * wait 310b39c5158Smillert 311b39c5158Smillert=item * exit 312b39c5158Smillert 313b39c5158Smillert=back 314b39c5158Smillert 315b39c5158SmillertFor example, to find out how many tests unexpectedly succeeded (TODO tests 316b39c5158Smillertwhich passed when they shouldn't): 317b39c5158Smillert 318b39c5158Smillert my $count = $aggregate->todo_passed; 319b39c5158Smillert my @descriptions = $aggregate->todo_passed; 320b39c5158Smillert 321b39c5158SmillertNote that C<wait> and C<exit> are the totals of the wait and exit 322b39c5158Smillertstatuses of each of the tests. These values are totalled only to provide 323b39c5158Smillerta true value if any of them are non-zero. 324b39c5158Smillert 325b39c5158Smillert=cut 326b39c5158Smillert 327b39c5158Smillert############################################################################## 328b39c5158Smillert 329b39c5158Smillert=head3 C<total> 330b39c5158Smillert 331b39c5158Smillert my $tests_run = $aggregate->total; 332b39c5158Smillert 333b39c5158SmillertReturns the total number of tests run. 334b39c5158Smillert 335b39c5158Smillert=cut 336b39c5158Smillert 337b39c5158Smillertsub total { shift->{total} } 338b39c5158Smillert 339b39c5158Smillert############################################################################## 340b39c5158Smillert 341b39c5158Smillert=head3 C<has_problems> 342b39c5158Smillert 343b39c5158Smillert if ( $parser->has_problems ) { 344b39c5158Smillert ... 345b39c5158Smillert } 346b39c5158Smillert 347b39c5158SmillertIdentical to C<has_errors>, but also returns true if any TODO tests 348b39c5158Smillertunexpectedly succeeded. This is more akin to "warnings". 349b39c5158Smillert 350b39c5158Smillert=cut 351b39c5158Smillert 352b39c5158Smillertsub has_problems { 353b39c5158Smillert my $self = shift; 354b39c5158Smillert return $self->todo_passed 355b39c5158Smillert || $self->has_errors; 356b39c5158Smillert} 357b39c5158Smillert 358b39c5158Smillert############################################################################## 359b39c5158Smillert 360b39c5158Smillert=head3 C<has_errors> 361b39c5158Smillert 362b39c5158Smillert if ( $parser->has_errors ) { 363b39c5158Smillert ... 364b39c5158Smillert } 365b39c5158Smillert 366b39c5158SmillertReturns true if I<any> of the parsers failed. This includes: 367b39c5158Smillert 368b39c5158Smillert=over 4 369b39c5158Smillert 370b39c5158Smillert=item * Failed tests 371b39c5158Smillert 372b39c5158Smillert=item * Parse errors 373b39c5158Smillert 374b39c5158Smillert=item * Bad exit or wait status 375b39c5158Smillert 376b39c5158Smillert=back 377b39c5158Smillert 378b39c5158Smillert=cut 379b39c5158Smillert 380b39c5158Smillertsub has_errors { 381b39c5158Smillert my $self = shift; 382b39c5158Smillert return 383b39c5158Smillert $self->failed 384b39c5158Smillert || $self->parse_errors 385b39c5158Smillert || $self->exit 386b39c5158Smillert || $self->wait; 387b39c5158Smillert} 388b39c5158Smillert 389b39c5158Smillert############################################################################## 390b39c5158Smillert 391b39c5158Smillert=head3 C<todo_failed> 392b39c5158Smillert 393b39c5158Smillert # deprecated in favor of 'todo_passed'. This method was horribly misnamed. 394b39c5158Smillert 395b39c5158SmillertThis was a badly misnamed method. It indicates which TODO tests unexpectedly 396b39c5158Smillertsucceeded. Will now issue a warning and call C<todo_passed>. 397b39c5158Smillert 398b39c5158Smillert=cut 399b39c5158Smillert 400b39c5158Smillertsub todo_failed { 401b39c5158Smillert warn 402b39c5158Smillert '"todo_failed" is deprecated. Please use "todo_passed". See the docs.'; 403b39c5158Smillert goto &todo_passed; 404b39c5158Smillert} 405b39c5158Smillert 406b39c5158Smillert=head1 See Also 407b39c5158Smillert 408b39c5158SmillertL<TAP::Parser> 409b39c5158Smillert 410b39c5158SmillertL<TAP::Harness> 411b39c5158Smillert 412b39c5158Smillert=cut 413b39c5158Smillert 414b39c5158Smillert1; 415