1 package App::Prove::State;
4 use vars qw($VERSION @ISA);
10 use App::Prove::State::Result;
11 use TAP::Parser::YAMLish::Reader ();
12 use TAP::Parser::YAMLish::Writer ();
15 @ISA = qw( TAP::Base );
17 use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
18 use constant NEED_GLOB => IS_WIN32;
22 App::Prove::State - State storage for the C<prove> command.
34 The C<prove> command supports a C<--state> option that instructs it to
35 store persistent state across runs. This module implements that state
36 and the operations that may be performed on it.
41 $ prove --state=fail,save -rbv
53 # override TAP::Base::new:
56 my %args = %{ shift || {} };
59 _ => $class->result_class->new(
66 store => delete $args{store},
67 extension => delete $args{extension} || '.t',
70 my $store = $self->{store};
72 if defined $store && -f $store;
77 =head2 C<result_class>
79 Returns the name of the class used for tracking test results. This class
80 should either subclass from C<App::Prove::State::Result> or provide an
86 return 'App::Prove::State::Result';
91 Get or set the extension files must have in order to be considered
92 tests. Defaults to '.t'.
98 $self->{extension} = shift if @_;
99 return $self->{extension};
104 Get the results of the last test run. Returns a C<result_class()> instance.
110 $self->{_} || $self->result_class->new
115 Save the test results. Should be called after all tests have run.
121 if ( $self->{should_save} && defined( my $store = $self->{store} ) ) {
126 =head2 Instance Methods
128 =head3 C<apply_switch>
130 Apply a list of switch options to the state.
136 Run in the same order as last time
140 Run only the failed tests from last time
144 Run only the passed tests from last time
148 Run all tests in normal order
152 Run the tests that most recently failed first
156 Run the tests ordered by number of todos.
160 Run the tests in slowest to fastest order.
164 Run test tests in fastest to slowest order.
168 Run the tests in newest to oldest order.
172 Run the tests in oldest to newest order.
176 Save the state on exit.
186 my $last_gen = $self->results->generation - 1;
187 my $last_run_time = $self->results->last_run_time;
188 my $now = $self->get_time;
190 my @switches = map { split /,/ } @opts;
195 where => sub { $_->generation >= $last_gen },
196 order => sub { $_->sequence }
201 where => sub { $_->result != 0 },
202 order => sub { -$_->result }
206 $self->_select( where => sub { $_->result == 0 } );
213 where => sub { $_->num_todo != 0 },
214 order => sub { -$_->num_todo; }
219 where => sub { defined $_->last_fail_time },
220 order => sub { $now - $_->last_fail_time }
224 $self->_select( order => sub { -$_->elapsed } );
227 $self->_select( order => sub { $_->elapsed } );
230 $self->_select( order => sub { -$_->mtime } );
233 $self->_select( order => sub { $_->mtime } );
236 $self->_select( where => sub { $_->mtime >= $last_run_time } );
239 $self->{should_save}++;
242 unshift @switches, qw( hot all save );
246 while ( defined( my $ele = shift @switches ) ) {
248 = ( $ele =~ /^([^:]+):(.*)/ )
251 my $code = $handler{$opt}
252 || croak "Illegal state option: $opt";
258 my ( $self, %spec ) = @_;
259 push @{ $self->{select} }, \%spec;
264 Given a list of args get the names of tests that should run
274 my @selected = $self->_query;
276 unless ( @argv || @{ $self->{select} } ) {
277 @argv = $recurse ? '.' : 't';
278 croak qq{No tests named and '@argv' directory not found}
282 push @selected, $self->_get_raw_tests( $recurse, @argv ) if @argv;
283 return grep { !$seen{$_}++ } @selected;
288 if ( my @sel = @{ $self->{select} } ) {
289 warn "No saved state, selection will be empty\n"
290 unless $self->results->num_tests;
291 return map { $self->_query_clause($_) } @sel;
297 my ( $self, $clause ) = @_;
299 my $results = $self->results;
300 my $where = $clause->{where} || sub {1};
303 for my $name ( $results->test_names ) {
304 next unless -f $name;
305 local $_ = $results->test($name);
306 push @got, $name if $where->();
310 if ( my $order = $clause->{order} ) {
311 @got = map { $_->[0] }
313 ( defined $b->[1] <=> defined $a->[1] )
314 || ( ( $a->[1] || 0 ) <=> ( $b->[1] || 0 ) )
317 do { local $_ = $results->test($_); $order->() }
331 # Do globbing on Win32.
332 @argv = map { glob "$_" } @argv if NEED_GLOB;
333 my $extension = $self->{extension};
335 for my $arg (@argv) {
337 push @argv => <STDIN>;
345 ? $self->_expand_dir_recursive( $arg, $extension )
346 : glob( File::Spec->catfile( $arg, "*$extension" ) )
352 sub _expand_dir_recursive {
353 my ( $self, $dir, $extension ) = @_;
357 { follow => 1, #21938
362 && push @tests => $File::Find::name;
370 =head3 C<observe_test>
372 Store the results of a test.
377 my ( $self, $test, $parser ) = @_;
380 scalar( $parser->failed ) + ( $parser->has_problems ? 1 : 0 ),
381 scalar( $parser->todo ), $parser->start_time, $parser->end_time,
396 my ( $self, $name, $fail, $todo, $start_time, $end_time ) = @_;
397 my $test = $self->results->test($name);
399 $test->sequence( $self->{seq}++ );
400 $test->generation( $self->results->generation );
402 $test->run_time($end_time);
403 $test->result($fail);
404 $test->num_todo($todo);
405 $test->elapsed( $end_time - $start_time );
408 $test->total_failures( $test->total_failures + 1 );
409 $test->last_fail_time($end_time);
412 $test->total_passes( $test->total_passes + 1 );
413 $test->last_pass_time($end_time);
419 Write the state to a file.
424 my ( $self, $name ) = @_;
426 $self->results->last_run_time( $self->get_time );
428 my $writer = TAP::Parser::YAMLish::Writer->new;
430 open FH, ">$name" or croak "Can't write $name ($!)";
431 $writer->write( $self->results->raw, \*FH );
437 Load the state from a file
442 my ( $self, $name ) = @_;
443 my $reader = TAP::Parser::YAMLish::Reader->new;
445 open FH, "<$name" or croak "Can't read $name ($!)";
447 # XXX this is temporary
448 $self->{_} = $self->result_class->new(
452 defined $line && chomp $line;
458 # $writer->write( $self->{tests} || {}, \*FH );
461 $self->_prune_and_stamp;
462 $self->results->generation( $self->results->generation + 1 );
465 sub _prune_and_stamp {
468 my $results = $self->results;
469 my @tests = $self->results->tests;
470 for my $test (@tests) {
471 my $name = $test->name;
472 if ( my @stat = stat $name ) {
473 $test->mtime( $stat[9] );
476 $results->remove($name);
483 for my $test ( $self->results->tests ) {
484 $self->{seq} = $test->sequence + 1
485 if defined $test->sequence && $test->sequence >= $self->{seq};