1b39c5158Smillertpackage App::Prove::State; 2b39c5158Smillert 3b39c5158Smillertuse strict; 46fb12b70Safresh1use warnings; 5b39c5158Smillert 6b39c5158Smillertuse File::Find; 7b39c5158Smillertuse File::Spec; 8b39c5158Smillertuse Carp; 9b39c5158Smillert 10b39c5158Smillertuse App::Prove::State::Result; 11b39c5158Smillertuse TAP::Parser::YAMLish::Reader (); 12b39c5158Smillertuse TAP::Parser::YAMLish::Writer (); 136fb12b70Safresh1use base 'TAP::Base'; 14b39c5158Smillert 15b39c5158SmillertBEGIN { 16b39c5158Smillert __PACKAGE__->mk_methods('result_class'); 17b39c5158Smillert} 18b39c5158Smillert 19b39c5158Smillertuse constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ ); 20b39c5158Smillertuse constant NEED_GLOB => IS_WIN32; 21b39c5158Smillert 22b39c5158Smillert=head1 NAME 23b39c5158Smillert 24b39c5158SmillertApp::Prove::State - State storage for the C<prove> command. 25b39c5158Smillert 26b39c5158Smillert=head1 VERSION 27b39c5158Smillert 28*3d61058aSafresh1Version 3.48 29b39c5158Smillert 30b39c5158Smillert=cut 31b39c5158Smillert 32*3d61058aSafresh1our $VERSION = '3.48'; 33b39c5158Smillert 34b39c5158Smillert=head1 DESCRIPTION 35b39c5158Smillert 36b39c5158SmillertThe C<prove> command supports a C<--state> option that instructs it to 37b39c5158Smillertstore persistent state across runs. This module implements that state 38b39c5158Smillertand the operations that may be performed on it. 39b39c5158Smillert 40b39c5158Smillert=head1 SYNOPSIS 41b39c5158Smillert 42b39c5158Smillert # Re-run failed tests 436fb12b70Safresh1 $ prove --state=failed,save -rbv 44b39c5158Smillert 45b39c5158Smillert=cut 46b39c5158Smillert 47b39c5158Smillert=head1 METHODS 48b39c5158Smillert 49b39c5158Smillert=head2 Class Methods 50b39c5158Smillert 51b39c5158Smillert=head3 C<new> 52b39c5158Smillert 53b39c5158SmillertAccepts a hashref with the following key/value pairs: 54b39c5158Smillert 55b39c5158Smillert=over 4 56b39c5158Smillert 57b39c5158Smillert=item * C<store> 58b39c5158Smillert 59b39c5158SmillertThe filename of the data store holding the data that App::Prove::State reads. 60b39c5158Smillert 61898184e3Ssthen=item * C<extensions> (optional) 62b39c5158Smillert 63898184e3SsthenThe test name extensions. Defaults to C<.t>. 64b39c5158Smillert 65b39c5158Smillert=item * C<result_class> (optional) 66b39c5158Smillert 67b39c5158SmillertThe name of the C<result_class>. Defaults to C<App::Prove::State::Result>. 68b39c5158Smillert 69b39c5158Smillert=back 70b39c5158Smillert 71b39c5158Smillert=cut 72b39c5158Smillert 73b39c5158Smillert# override TAP::Base::new: 74b39c5158Smillertsub new { 75b39c5158Smillert my $class = shift; 76b39c5158Smillert my %args = %{ shift || {} }; 77b39c5158Smillert 78b39c5158Smillert my $self = bless { 79b39c5158Smillert select => [], 80b39c5158Smillert seq => 1, 81b39c5158Smillert store => delete $args{store}, 82898184e3Ssthen extensions => ( delete $args{extensions} || ['.t'] ), 8391f110e0Safresh1 result_class => 8491f110e0Safresh1 ( delete $args{result_class} || 'App::Prove::State::Result' ), 85b39c5158Smillert }, $class; 86b39c5158Smillert 87b39c5158Smillert $self->{_} = $self->result_class->new( 88b39c5158Smillert { tests => {}, 89b39c5158Smillert generation => 1, 90b39c5158Smillert } 91b39c5158Smillert ); 92b39c5158Smillert my $store = $self->{store}; 93b39c5158Smillert $self->load($store) 94b39c5158Smillert if defined $store && -f $store; 95b39c5158Smillert 96b39c5158Smillert return $self; 97b39c5158Smillert} 98b39c5158Smillert 99b39c5158Smillert=head2 C<result_class> 100b39c5158Smillert 101b39c5158SmillertGetter/setter for the name of the class used for tracking test results. This 102b39c5158Smillertclass should either subclass from C<App::Prove::State::Result> or provide an 103b39c5158Smillertidentical interface. 104b39c5158Smillert 105b39c5158Smillert=cut 106b39c5158Smillert 107898184e3Ssthen=head2 C<extensions> 108b39c5158Smillert 109898184e3SsthenGet or set the list of extensions that files must have in order to be 110898184e3Ssthenconsidered tests. Defaults to ['.t']. 111b39c5158Smillert 112b39c5158Smillert=cut 113b39c5158Smillert 114898184e3Ssthensub extensions { 115b39c5158Smillert my $self = shift; 116898184e3Ssthen $self->{extensions} = shift if @_; 117898184e3Ssthen return $self->{extensions}; 118b39c5158Smillert} 119b39c5158Smillert 120b39c5158Smillert=head2 C<results> 121b39c5158Smillert 122b39c5158SmillertGet the results of the last test run. Returns a C<result_class()> instance. 123b39c5158Smillert 124b39c5158Smillert=cut 125b39c5158Smillert 126b39c5158Smillertsub results { 127b39c5158Smillert my $self = shift; 128b39c5158Smillert $self->{_} || $self->result_class->new; 129b39c5158Smillert} 130b39c5158Smillert 131b39c5158Smillert=head2 C<commit> 132b39c5158Smillert 133b39c5158SmillertSave the test results. Should be called after all tests have run. 134b39c5158Smillert 135b39c5158Smillert=cut 136b39c5158Smillert 137b39c5158Smillertsub commit { 138b39c5158Smillert my $self = shift; 139b39c5158Smillert if ( $self->{should_save} ) { 140b39c5158Smillert $self->save; 141b39c5158Smillert } 142b39c5158Smillert} 143b39c5158Smillert 144b39c5158Smillert=head2 Instance Methods 145b39c5158Smillert 146b39c5158Smillert=head3 C<apply_switch> 147b39c5158Smillert 148b39c5158Smillert $self->apply_switch('failed,save'); 149b39c5158Smillert 150b39c5158SmillertApply a list of switch options to the state, updating the internal 151b39c5158Smillertobject state as a result. Nothing is returned. 152b39c5158Smillert 153b39c5158SmillertDiagnostics: 154b39c5158Smillert - "Illegal state option: %s" 155b39c5158Smillert 156b39c5158Smillert=over 157b39c5158Smillert 158b39c5158Smillert=item C<last> 159b39c5158Smillert 160b39c5158SmillertRun in the same order as last time 161b39c5158Smillert 162b39c5158Smillert=item C<failed> 163b39c5158Smillert 164b39c5158SmillertRun only the failed tests from last time 165b39c5158Smillert 166b39c5158Smillert=item C<passed> 167b39c5158Smillert 168b39c5158SmillertRun only the passed tests from last time 169b39c5158Smillert 170b39c5158Smillert=item C<all> 171b39c5158Smillert 172b39c5158SmillertRun all tests in normal order 173b39c5158Smillert 174b39c5158Smillert=item C<hot> 175b39c5158Smillert 176b39c5158SmillertRun the tests that most recently failed first 177b39c5158Smillert 178b39c5158Smillert=item C<todo> 179b39c5158Smillert 180b39c5158SmillertRun the tests ordered by number of todos. 181b39c5158Smillert 182b39c5158Smillert=item C<slow> 183b39c5158Smillert 184b39c5158SmillertRun the tests in slowest to fastest order. 185b39c5158Smillert 186b39c5158Smillert=item C<fast> 187b39c5158Smillert 188b39c5158SmillertRun test tests in fastest to slowest order. 189b39c5158Smillert 190b39c5158Smillert=item C<new> 191b39c5158Smillert 192b39c5158SmillertRun the tests in newest to oldest order. 193b39c5158Smillert 194b39c5158Smillert=item C<old> 195b39c5158Smillert 196b39c5158SmillertRun the tests in oldest to newest order. 197b39c5158Smillert 198b39c5158Smillert=item C<save> 199b39c5158Smillert 200b39c5158SmillertSave the state on exit. 201b39c5158Smillert 202b39c5158Smillert=back 203b39c5158Smillert 204b39c5158Smillert=cut 205b39c5158Smillert 206b39c5158Smillertsub apply_switch { 207b39c5158Smillert my $self = shift; 208b39c5158Smillert my @opts = @_; 209b39c5158Smillert 210b39c5158Smillert my $last_gen = $self->results->generation - 1; 211b39c5158Smillert my $last_run_time = $self->results->last_run_time; 212b39c5158Smillert my $now = $self->get_time; 213b39c5158Smillert 214b39c5158Smillert my @switches = map { split /,/ } @opts; 215b39c5158Smillert 216b39c5158Smillert my %handler = ( 217b39c5158Smillert last => sub { 218b39c5158Smillert $self->_select( 21991f110e0Safresh1 limit => shift, 220b39c5158Smillert where => sub { $_->generation >= $last_gen }, 221b39c5158Smillert order => sub { $_->sequence } 222b39c5158Smillert ); 223b39c5158Smillert }, 224b39c5158Smillert failed => sub { 225b39c5158Smillert $self->_select( 22691f110e0Safresh1 limit => shift, 227b39c5158Smillert where => sub { $_->result != 0 }, 228b39c5158Smillert order => sub { -$_->result } 229b39c5158Smillert ); 230b39c5158Smillert }, 231b39c5158Smillert passed => sub { 23291f110e0Safresh1 $self->_select( 23391f110e0Safresh1 limit => shift, 23491f110e0Safresh1 where => sub { $_->result == 0 } 23591f110e0Safresh1 ); 236b39c5158Smillert }, 237b39c5158Smillert all => sub { 23891f110e0Safresh1 $self->_select( limit => shift ); 239b39c5158Smillert }, 240b39c5158Smillert todo => sub { 241b39c5158Smillert $self->_select( 24291f110e0Safresh1 limit => shift, 243b39c5158Smillert where => sub { $_->num_todo != 0 }, 244b39c5158Smillert order => sub { -$_->num_todo; } 245b39c5158Smillert ); 246b39c5158Smillert }, 247b39c5158Smillert hot => sub { 248b39c5158Smillert $self->_select( 24991f110e0Safresh1 limit => shift, 250b39c5158Smillert where => sub { defined $_->last_fail_time }, 251b39c5158Smillert order => sub { $now - $_->last_fail_time } 252b39c5158Smillert ); 253b39c5158Smillert }, 254b39c5158Smillert slow => sub { 25591f110e0Safresh1 $self->_select( 25691f110e0Safresh1 limit => shift, 25791f110e0Safresh1 order => sub { -$_->elapsed } 25891f110e0Safresh1 ); 259b39c5158Smillert }, 260b39c5158Smillert fast => sub { 26191f110e0Safresh1 $self->_select( 26291f110e0Safresh1 limit => shift, 26391f110e0Safresh1 order => sub { $_->elapsed } 26491f110e0Safresh1 ); 265b39c5158Smillert }, 266b39c5158Smillert new => sub { 26791f110e0Safresh1 $self->_select( 26891f110e0Safresh1 limit => shift, 26991f110e0Safresh1 order => sub { -$_->mtime } 27091f110e0Safresh1 ); 271b39c5158Smillert }, 272b39c5158Smillert old => sub { 27391f110e0Safresh1 $self->_select( 27491f110e0Safresh1 limit => shift, 27591f110e0Safresh1 order => sub { $_->mtime } 27691f110e0Safresh1 ); 277b39c5158Smillert }, 278b39c5158Smillert fresh => sub { 27991f110e0Safresh1 $self->_select( 28091f110e0Safresh1 limit => shift, 28191f110e0Safresh1 where => sub { $_->mtime >= $last_run_time } 28291f110e0Safresh1 ); 283b39c5158Smillert }, 284b39c5158Smillert save => sub { 285b39c5158Smillert $self->{should_save}++; 286b39c5158Smillert }, 287b39c5158Smillert adrian => sub { 288b39c5158Smillert unshift @switches, qw( hot all save ); 289b39c5158Smillert }, 290b39c5158Smillert ); 291b39c5158Smillert 292b39c5158Smillert while ( defined( my $ele = shift @switches ) ) { 293b39c5158Smillert my ( $opt, $arg ) 294b39c5158Smillert = ( $ele =~ /^([^:]+):(.*)/ ) 295b39c5158Smillert ? ( $1, $2 ) 296b39c5158Smillert : ( $ele, undef ); 297b39c5158Smillert my $code = $handler{$opt} 298b39c5158Smillert || croak "Illegal state option: $opt"; 299b39c5158Smillert $code->($arg); 300b39c5158Smillert } 301b39c5158Smillert return; 302b39c5158Smillert} 303b39c5158Smillert 304b39c5158Smillertsub _select { 305b39c5158Smillert my ( $self, %spec ) = @_; 306b39c5158Smillert push @{ $self->{select} }, \%spec; 307b39c5158Smillert} 308b39c5158Smillert 309b39c5158Smillert=head3 C<get_tests> 310b39c5158Smillert 311b39c5158SmillertGiven a list of args get the names of tests that should run 312b39c5158Smillert 313b39c5158Smillert=cut 314b39c5158Smillert 315b39c5158Smillertsub get_tests { 316b39c5158Smillert my $self = shift; 317b39c5158Smillert my $recurse = shift; 318b39c5158Smillert my @argv = @_; 319b39c5158Smillert my %seen; 320b39c5158Smillert 321b39c5158Smillert my @selected = $self->_query; 322b39c5158Smillert 323b39c5158Smillert unless ( @argv || @{ $self->{select} } ) { 324b39c5158Smillert @argv = $recurse ? '.' : 't'; 325b39c5158Smillert croak qq{No tests named and '@argv' directory not found} 326b39c5158Smillert unless -d $argv[0]; 327b39c5158Smillert } 328b39c5158Smillert 329b39c5158Smillert push @selected, $self->_get_raw_tests( $recurse, @argv ) if @argv; 330b39c5158Smillert return grep { !$seen{$_}++ } @selected; 331b39c5158Smillert} 332b39c5158Smillert 333b39c5158Smillertsub _query { 334b39c5158Smillert my $self = shift; 335b39c5158Smillert if ( my @sel = @{ $self->{select} } ) { 336b39c5158Smillert warn "No saved state, selection will be empty\n" 337b39c5158Smillert unless $self->results->num_tests; 338b39c5158Smillert return map { $self->_query_clause($_) } @sel; 339b39c5158Smillert } 340b39c5158Smillert return; 341b39c5158Smillert} 342b39c5158Smillert 343b39c5158Smillertsub _query_clause { 344b39c5158Smillert my ( $self, $clause ) = @_; 345b39c5158Smillert my @got; 346b39c5158Smillert my $results = $self->results; 347b39c5158Smillert my $where = $clause->{where} || sub {1}; 348b39c5158Smillert 349b39c5158Smillert # Select 350b39c5158Smillert for my $name ( $results->test_names ) { 351b39c5158Smillert next unless -f $name; 352b39c5158Smillert local $_ = $results->test($name); 353b39c5158Smillert push @got, $name if $where->(); 354b39c5158Smillert } 355b39c5158Smillert 356b39c5158Smillert # Sort 357b39c5158Smillert if ( my $order = $clause->{order} ) { 358b39c5158Smillert @got = map { $_->[0] } 359b39c5158Smillert sort { 360b39c5158Smillert ( defined $b->[1] <=> defined $a->[1] ) 361b39c5158Smillert || ( ( $a->[1] || 0 ) <=> ( $b->[1] || 0 ) ) 362b39c5158Smillert } map { 363b39c5158Smillert [ $_, 364b39c5158Smillert do { local $_ = $results->test($_); $order->() } 365b39c5158Smillert ] 366b39c5158Smillert } @got; 367b39c5158Smillert } 368b39c5158Smillert 36991f110e0Safresh1 if ( my $limit = $clause->{limit} ) { 37091f110e0Safresh1 @got = splice @got, 0, $limit if @got > $limit; 37191f110e0Safresh1 } 37291f110e0Safresh1 373b39c5158Smillert return @got; 374b39c5158Smillert} 375b39c5158Smillert 376b39c5158Smillertsub _get_raw_tests { 377b39c5158Smillert my $self = shift; 378b39c5158Smillert my $recurse = shift; 379b39c5158Smillert my @argv = @_; 380b39c5158Smillert my @tests; 381b39c5158Smillert 382b39c5158Smillert # Do globbing on Win32. 38391f110e0Safresh1 if (NEED_GLOB) { 38491f110e0Safresh1 eval "use File::Glob::Windows"; # [49732] 38591f110e0Safresh1 @argv = map { glob "$_" } @argv; 38691f110e0Safresh1 } 387898184e3Ssthen my $extensions = $self->{extensions}; 388b39c5158Smillert 389b39c5158Smillert for my $arg (@argv) { 390b39c5158Smillert if ( '-' eq $arg ) { 391b39c5158Smillert push @argv => <STDIN>; 392b39c5158Smillert chomp(@argv); 393b39c5158Smillert next; 394b39c5158Smillert } 395b39c5158Smillert 396b39c5158Smillert push @tests, 397b39c5158Smillert sort -d $arg 398b39c5158Smillert ? $recurse 399898184e3Ssthen ? $self->_expand_dir_recursive( $arg, $extensions ) 40091f110e0Safresh1 : map { glob( File::Spec->catfile( $arg, "*$_" ) ) } 40191f110e0Safresh1 @{$extensions} 402b39c5158Smillert : $arg; 403b39c5158Smillert } 404b39c5158Smillert return @tests; 405b39c5158Smillert} 406b39c5158Smillert 407b39c5158Smillertsub _expand_dir_recursive { 408898184e3Ssthen my ( $self, $dir, $extensions ) = @_; 409b39c5158Smillert 410b39c5158Smillert my @tests; 411898184e3Ssthen my $ext_string = join( '|', map {quotemeta} @{$extensions} ); 412898184e3Ssthen 413b39c5158Smillert find( 414b39c5158Smillert { follow => 1, #21938 415b39c5158Smillert follow_skip => 2, 416b39c5158Smillert wanted => sub { 417b39c5158Smillert -f 418898184e3Ssthen && /(?:$ext_string)$/ 419b39c5158Smillert && push @tests => $File::Find::name; 420b39c5158Smillert } 421b39c5158Smillert }, 422b39c5158Smillert $dir 423b39c5158Smillert ); 424b39c5158Smillert return @tests; 425b39c5158Smillert} 426b39c5158Smillert 427b39c5158Smillert=head3 C<observe_test> 428b39c5158Smillert 429b39c5158SmillertStore the results of a test. 430b39c5158Smillert 431b39c5158Smillert=cut 432b39c5158Smillert 433b39c5158Smillert# Store: 434b39c5158Smillert# last fail time 435b39c5158Smillert# last pass time 436b39c5158Smillert# last run time 437b39c5158Smillert# most recent result 438b39c5158Smillert# most recent todos 439b39c5158Smillert# total failures 440b39c5158Smillert# total passes 441b39c5158Smillert# state generation 442b39c5158Smillert# parser 443b39c5158Smillert 444b39c5158Smillertsub observe_test { 445b39c5158Smillert 446b39c5158Smillert my ( $self, $test_info, $parser ) = @_; 447b39c5158Smillert my $name = $test_info->[0]; 448b39c5158Smillert my $fail = scalar( $parser->failed ) + ( $parser->has_problems ? 1 : 0 ); 449b39c5158Smillert my $todo = scalar( $parser->todo ); 450b39c5158Smillert my $start_time = $parser->start_time; 451b39c5158Smillert my $end_time = $parser->end_time, 452b39c5158Smillert 453b39c5158Smillert my $test = $self->results->test($name); 454b39c5158Smillert 455b39c5158Smillert $test->sequence( $self->{seq}++ ); 456b39c5158Smillert $test->generation( $self->results->generation ); 457b39c5158Smillert 458b39c5158Smillert $test->run_time($end_time); 459b39c5158Smillert $test->result($fail); 460b39c5158Smillert $test->num_todo($todo); 461b39c5158Smillert $test->elapsed( $end_time - $start_time ); 462b39c5158Smillert 463b39c5158Smillert $test->parser($parser); 464b39c5158Smillert 465b39c5158Smillert if ($fail) { 466b39c5158Smillert $test->total_failures( $test->total_failures + 1 ); 467b39c5158Smillert $test->last_fail_time($end_time); 468b39c5158Smillert } 469b39c5158Smillert else { 470b39c5158Smillert $test->total_passes( $test->total_passes + 1 ); 471b39c5158Smillert $test->last_pass_time($end_time); 472b39c5158Smillert } 473b39c5158Smillert} 474b39c5158Smillert 475b39c5158Smillert=head3 C<save> 476b39c5158Smillert 477b39c5158SmillertWrite the state to a file. 478b39c5158Smillert 479b39c5158Smillert=cut 480b39c5158Smillert 481b39c5158Smillertsub save { 482b39c5158Smillert my ($self) = @_; 483b39c5158Smillert 484b39c5158Smillert my $store = $self->{store} or return; 485b39c5158Smillert $self->results->last_run_time( $self->get_time ); 486b39c5158Smillert 487b39c5158Smillert my $writer = TAP::Parser::YAMLish::Writer->new; 488b39c5158Smillert local *FH; 489b39c5158Smillert open FH, ">$store" or croak "Can't write $store ($!)"; 490b39c5158Smillert $writer->write( $self->results->raw, \*FH ); 491b39c5158Smillert close FH; 492b39c5158Smillert} 493b39c5158Smillert 494b39c5158Smillert=head3 C<load> 495b39c5158Smillert 496b39c5158SmillertLoad the state from a file 497b39c5158Smillert 498b39c5158Smillert=cut 499b39c5158Smillert 500b39c5158Smillertsub load { 501b39c5158Smillert my ( $self, $name ) = @_; 502b39c5158Smillert my $reader = TAP::Parser::YAMLish::Reader->new; 503b39c5158Smillert local *FH; 504b39c5158Smillert open FH, "<$name" or croak "Can't read $name ($!)"; 505b39c5158Smillert 506b39c5158Smillert # XXX this is temporary 507b39c5158Smillert $self->{_} = $self->result_class->new( 508b39c5158Smillert $reader->read( 509b39c5158Smillert sub { 510b39c5158Smillert my $line = <FH>; 511b39c5158Smillert defined $line && chomp $line; 512b39c5158Smillert return $line; 513b39c5158Smillert } 514b39c5158Smillert ) 515b39c5158Smillert ); 516b39c5158Smillert 517b39c5158Smillert # $writer->write( $self->{tests} || {}, \*FH ); 518b39c5158Smillert close FH; 519b39c5158Smillert $self->_regen_seq; 520b39c5158Smillert $self->_prune_and_stamp; 521b39c5158Smillert $self->results->generation( $self->results->generation + 1 ); 522b39c5158Smillert} 523b39c5158Smillert 524b39c5158Smillertsub _prune_and_stamp { 525b39c5158Smillert my $self = shift; 526b39c5158Smillert 527b39c5158Smillert my $results = $self->results; 528b39c5158Smillert my @tests = $self->results->tests; 529b39c5158Smillert for my $test (@tests) { 530b39c5158Smillert my $name = $test->name; 531b39c5158Smillert if ( my @stat = stat $name ) { 532b39c5158Smillert $test->mtime( $stat[9] ); 533b39c5158Smillert } 534b39c5158Smillert else { 535b39c5158Smillert $results->remove($name); 536b39c5158Smillert } 537b39c5158Smillert } 538b39c5158Smillert} 539b39c5158Smillert 540b39c5158Smillertsub _regen_seq { 541b39c5158Smillert my $self = shift; 542b39c5158Smillert for my $test ( $self->results->tests ) { 543b39c5158Smillert $self->{seq} = $test->sequence + 1 544b39c5158Smillert if defined $test->sequence && $test->sequence >= $self->{seq}; 545b39c5158Smillert } 546b39c5158Smillert} 547b39c5158Smillert 548b39c5158Smillert1; 549