📄 state.pm
字号:
package App::Prove::State;use strict;use File::Find;use File::Spec;use Carp;use TAP::Parser::YAMLish::Reader ();use TAP::Parser::YAMLish::Writer ();use TAP::Base;use vars qw($VERSION @ISA);@ISA = qw( TAP::Base );use constant IS_WIN32 => ( $^O =~ /^(MS)?Win32$/ );use constant NEED_GLOB => IS_WIN32;=head1 NAMEApp::Prove::State - State storage for the C<prove> command.=head1 VERSIONVersion 3.07=cut$VERSION = '3.07';=head1 DESCRIPTIONThe C<prove> command supports a C<--state> option that instructs it tostore persistent state across runs. This module implements that stateand the operations that may be performed on it.=head1 SYNOPSIS # Re-run failed tests $ prove --state=fail,save -rbv=cut=head1 METHODS=head2 Class Methods=head3 C<new>=cutsub new { my $class = shift; my %args = %{ shift || {} }; my $self = bless { _ => { tests => {}, generation => 1 }, select => [], seq => 1, store => delete $args{store}, }, $class; my $store = $self->{store}; $self->load($store) if defined $store && -f $store; return $self;}sub DESTROY { my $self = shift; if ( $self->{should_save} && defined( my $store = $self->{store} ) ) { $self->save($store); }}=head2 Instance Methods=head3 C<apply_switch>Apply a list of switch options to the state.=over=item C<last>Run in the same order as last time=item C<failed>Run only the failed tests from last time=item C<passed>Run only the passed tests from last time=item C<all>Run all tests in normal order=item C<hot>Run the tests that most recently failed first=item C<todo>Run the tests ordered by number of todos.=item C<slow>Run the tests in slowest to fastest order.=item C<fast>Run test tests in fastest to slowest order.=item C<new>Run the tests in newest to oldest order.=item C<old>Run the tests in oldest to newest order.=item C<save>Save the state on exit.=back=cutsub apply_switch { my $self = shift; my @opts = @_; my $last_gen = $self->{_}->{generation} - 1; my $now = $self->get_time; my @switches = map { split /,/ } @opts; my %handler = ( last => sub { $self->_select( where => sub { $_->{gen} >= $last_gen }, order => sub { $_->{seq} } ); }, failed => sub { $self->_select( where => sub { $_->{last_result} != 0 }, order => sub { -$_->{last_result} } ); }, passed => sub { $self->_select( where => sub { $_->{last_result} == 0 } ); }, all => sub { $self->_select(); }, todo => sub { $self->_select( where => sub { $_->{last_todo} != 0 }, order => sub { -$_->{last_todo}; } ); }, hot => sub { $self->_select( where => sub { defined $_->{last_fail_time} }, order => sub { $now - $_->{last_fail_time} } ); }, slow => sub { $self->_select( order => sub { -$_->{elapsed} } ); }, fast => sub { $self->_select( order => sub { $_->{elapsed} } ); }, new => sub { $self->_select( order => sub { -$_->{mtime} } ); }, old => sub { $self->_select( order => sub { $_->{mtime} } ); }, save => sub { $self->{should_save}++; }, adrian => sub { unshift @switches, qw( hot all save ); }, ); while ( defined( my $ele = shift @switches ) ) { my ( $opt, $arg ) = ( $ele =~ /^([^:]+):(.*)/ ) ? ( $1, $2 ) : ( $ele, undef ); my $code = $handler{$opt} || croak "Illegal state option: $opt"; $code->($arg); }}sub _select { my ( $self, %spec ) = @_; push @{ $self->{select} }, \%spec;}=head3 C<get_tests>Given a list of args get the names of tests that should run=cutsub get_tests { my $self = shift; my $recurse = shift; my @argv = @_; my %seen; my @selected = $self->_query; unless ( @argv || @{ $self->{select} } ) { croak q{No tests named and 't' directory not found} unless -d 't'; @argv = 't'; } push @selected, $self->_get_raw_tests( $recurse, @argv ) if @argv; return grep { !$seen{$_}++ } @selected;}sub _query { my $self = shift; if ( my @sel = @{ $self->{select} } ) { warn "No saved state, selection will be empty\n" unless keys %{ $self->{_}->{tests} }; return map { $self->_query_clause($_) } @sel; } return;}sub _query_clause { my ( $self, $clause ) = @_; my @got; my $tests = $self->{_}->{tests}; my $where = $clause->{where} || sub {1}; # Select for my $test ( sort keys %$tests ) { next unless -f $test; local $_ = $tests->{$test}; push @got, $test if $where->(); } # Sort if ( my $order = $clause->{order} ) { @got = map { $_->[0] } sort { ( defined $b->[1] <=> defined $a->[1] ) || ( ( $a->[1] || 0 ) <=> ( $b->[1] || 0 ) ) } map { [ $_, do { local $_ = $tests->{$_}; $order->() } ] } @got; } return @got;}sub _get_raw_tests { my $self = shift; my $recurse = shift; my @argv = @_; my @tests; # Do globbing on Win32. @argv = map { glob "$_" } @argv if NEED_GLOB; for my $arg (@argv) { if ( '-' eq $arg ) { push @argv => <STDIN>; chomp(@argv); next; } push @tests, sort -d $arg ? $recurse ? $self->_expand_dir_recursive($arg) : glob( File::Spec->catfile( $arg, '*.t' ) ) : $arg; } return @tests;}sub _expand_dir_recursive { my ( $self, $dir ) = @_; my @tests; find( { follow => 1, #21938 wanted => sub { -f && /\.t$/ && push @tests => $File::Find::name; } }, $dir ); return @tests;}=head3 C<observe_test>Store the results of a test.=cutsub observe_test { my ( $self, $test, $parser ) = @_; $self->_record_test( $test, scalar( $parser->failed ) + ( $parser->has_problems ? 1 : 0 ), scalar( $parser->todo ), $parser->start_time, $parser->end_time );}# Store:# last fail time# last pass time# last run time# most recent result# most recent todos# total failures# total passes# state generationsub _record_test { my ( $self, $test, $fail, $todo, $start_time, $end_time ) = @_; my $rec = $self->{_}->{tests}->{ $test->[0] } ||= {}; $rec->{seq} = $self->{seq}++; $rec->{gen} = $self->{_}->{generation}; $rec->{last_run_time} = $end_time; $rec->{last_result} = $fail; $rec->{last_todo} = $todo; $rec->{elapsed} = $end_time - $start_time; if ($fail) { $rec->{total_failures}++; $rec->{last_fail_time} = $end_time; } else { $rec->{total_passes}++; $rec->{last_pass_time} = $end_time; }}=head3 C<save>Write the state to a file.=cutsub save { my ( $self, $name ) = @_; my $writer = TAP::Parser::YAMLish::Writer->new; local *FH; open FH, ">$name" or croak "Can't write $name ($!)"; $writer->write( $self->{_} || {}, \*FH ); close FH;}=head3 C<load>Load the state from a file=cutsub load { my ( $self, $name ) = @_; my $reader = TAP::Parser::YAMLish::Reader->new; local *FH; open FH, "<$name" or croak "Can't read $name ($!)"; $self->{_} = $reader->read( sub { my $line = <FH>; defined $line && chomp $line; return $line; } ); # $writer->write( $self->{tests} || {}, \*FH ); close FH; $self->_regen_seq; $self->_prune_and_stamp; $self->{_}->{generation}++;}sub _prune_and_stamp { my $self = shift; for my $name ( keys %{ $self->{_}->{tests} || {} } ) { if ( my @stat = stat $name ) { $self->{_}->{tests}->{$name}->{mtime} = $stat[9]; } else { delete $self->{_}->{tests}->{$name}; } }}sub _regen_seq { my $self = shift; for my $rec ( values %{ $self->{_}->{tests} || {} } ) { $self->{seq} = $rec->{seq} + 1 if defined $rec->{seq} && $rec->{seq} >= $self->{seq}; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -