1 package App::Prove::State;
7 use TAP::Parser::YAMLish::Reader ();
8 use TAP::Parser::YAMLish::Writer ();
11 use vars qw($VERSION @ISA);
12 @ISA = qw( TAP::Base );
14 use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );
15 use constant NEED_GLOB => IS_WIN32;
19 App::Prove::State - State storage for the C<prove> command.
31 The C<prove> command supports a C<--state> option that instructs it to
32 store persistent state across runs. This module implements that state
33 and the operations that may be performed on it.
38 $ prove --state=fail,save -rbv
52 my %args = %{ shift || {} };
61 store => delete $args{store},
64 my $store = $self->{store};
66 if defined $store && -f $store;
73 if ( $self->{should_save} && defined( my $store = $self->{store} ) ) {
78 =head2 Instance Methods
80 =head3 C<apply_switch>
82 Apply a list of switch options to the state.
88 Run in the same order as last time
92 Run only the failed tests from last time
96 Run only the passed tests from last time
100 Run all tests in normal order
104 Run the tests that most recently failed first
108 Run the tests ordered by number of todos.
112 Run the tests in slowest to fastest order.
116 Run test tests in fastest to slowest order.
120 Run the tests in newest to oldest order.
124 Run the tests in oldest to newest order.
128 Save the state on exit.
138 my $last_gen = $self->{_}->{generation} - 1;
139 my $now = $self->get_time;
141 my @switches = map { split /,/ } @opts;
146 where => sub { $_->{gen} >= $last_gen },
147 order => sub { $_->{seq} }
152 where => sub { $_->{last_result} != 0 },
153 order => sub { -$_->{last_result} }
157 $self->_select( where => sub { $_->{last_result} == 0 } );
164 where => sub { $_->{last_todo} != 0 },
165 order => sub { -$_->{last_todo}; }
170 where => sub { defined $_->{last_fail_time} },
171 order => sub { $now - $_->{last_fail_time} }
175 $self->_select( order => sub { -$_->{elapsed} } );
178 $self->_select( order => sub { $_->{elapsed} } );
181 $self->_select( order => sub { -$_->{mtime} } );
184 $self->_select( order => sub { $_->{mtime} } );
187 $self->{should_save}++;
190 unshift @switches, qw( hot all save );
194 while ( defined( my $ele = shift @switches ) ) {
196 = ( $ele =~ /^([^:]+):(.*)/ )
199 my $code = $handler{$opt}
200 || croak "Illegal state option: $opt";
206 my ( $self, %spec ) = @_;
207 push @{ $self->{select} }, \%spec;
212 Given a list of args get the names of tests that should run
222 my @selected = $self->_query;
224 unless ( @argv || @{ $self->{select} } ) {
225 croak q{No tests named and 't' directory not found}
230 push @selected, $self->_get_raw_tests( $recurse, @argv ) if @argv;
231 return grep { !$seen{$_}++ } @selected;
236 if ( my @sel = @{ $self->{select} } ) {
237 warn "No saved state, selection will be empty\n"
238 unless keys %{ $self->{_}->{tests} };
239 return map { $self->_query_clause($_) } @sel;
245 my ( $self, $clause ) = @_;
247 my $tests = $self->{_}->{tests};
248 my $where = $clause->{where} || sub {1};
251 for my $test ( sort keys %$tests ) {
252 next unless -f $test;
253 local $_ = $tests->{$test};
254 push @got, $test if $where->();
258 if ( my $order = $clause->{order} ) {
259 @got = map { $_->[0] }
261 ( defined $b->[1] <=> defined $a->[1] )
262 || ( ( $a->[1] || 0 ) <=> ( $b->[1] || 0 ) )
265 do { local $_ = $tests->{$_}; $order->() }
279 # Do globbing on Win32.
280 @argv = map { glob "$_" } @argv if NEED_GLOB;
282 for my $arg (@argv) {
284 push @argv => <STDIN>;
292 ? $self->_expand_dir_recursive($arg)
293 : glob( File::Spec->catfile( $arg, '*.t' ) )
299 sub _expand_dir_recursive {
300 my ( $self, $dir ) = @_;
304 { follow => 1, #21938
308 && push @tests => $File::Find::name;
316 =head3 C<observe_test>
318 Store the results of a test.
323 my ( $self, $test, $parser ) = @_;
325 $test, scalar( $parser->failed ) + ( $parser->has_problems ? 1 : 0 ),
326 scalar( $parser->todo ), $parser->start_time, $parser->end_time
341 my ( $self, $test, $fail, $todo, $start_time, $end_time ) = @_;
342 my $rec = $self->{_}->{tests}->{ $test->[0] } ||= {};
344 $rec->{seq} = $self->{seq}++;
345 $rec->{gen} = $self->{_}->{generation};
347 $rec->{last_run_time} = $end_time;
348 $rec->{last_result} = $fail;
349 $rec->{last_todo} = $todo;
350 $rec->{elapsed} = $end_time - $start_time;
353 $rec->{total_failures}++;
354 $rec->{last_fail_time} = $end_time;
357 $rec->{total_passes}++;
358 $rec->{last_pass_time} = $end_time;
364 Write the state to a file.
369 my ( $self, $name ) = @_;
370 my $writer = TAP::Parser::YAMLish::Writer->new;
372 open FH, ">$name" or croak "Can't write $name ($!)";
373 $writer->write( $self->{_} || {}, \*FH );
379 Load the state from a file
384 my ( $self, $name ) = @_;
385 my $reader = TAP::Parser::YAMLish::Reader->new;
387 open FH, "<$name" or croak "Can't read $name ($!)";
388 $self->{_} = $reader->read(
391 defined $line && chomp $line;
396 # $writer->write( $self->{tests} || {}, \*FH );
399 $self->_prune_and_stamp;
400 $self->{_}->{generation}++;
403 sub _prune_and_stamp {
405 for my $name ( keys %{ $self->{_}->{tests} || {} } ) {
406 if ( my @stat = stat $name ) {
407 $self->{_}->{tests}->{$name}->{mtime} = $stat[9];
410 delete $self->{_}->{tests}->{$name};
417 for my $rec ( values %{ $self->{_}->{tests} || {} } ) {
418 $self->{seq} = $rec->{seq} + 1
419 if defined $rec->{seq} && $rec->{seq} >= $self->{seq};