package Dist::Joseki::Command::smoke; use strict; use warnings; use Cwd 'abs_path'; use Config 'myconfig'; use Dist::Joseki; use Dist::Joseki::Find; use File::Basename; use File::Spec; use File::Find; use Template; use Test::TAP::HTMLMatrix; use Test::TAP::Model::Visual; use YAML qw/LoadFile DumpFile/; our $VERSION = '0.01'; use base 'Dist::Joseki::Cmd::Multiplexable'; __PACKAGE__->mk_hash_accessors(qw(dist_errors)); sub options { my ($self, $app, $cmd_config) = @_; return ( $self->SUPER::options($app, $cmd_config), [ 'cover|c', 'also run coverage tests', ], [ 'resume|r', 'skip tests if there is a smoke.html already', ], [ 'summary|s=s', 'location of summary file', { default => $cmd_config->{summary} || sprintf('%s/smoke.html', (Dist::Joseki::Find->new->projroot)[0]), }, ], ); } sub run_smoke_tests { my $self = shift; my $smoke_html_filename = 'smoke.html'; my $smoke_yaml_filename = 'smoke.yaml'; if (-e 'BUILD.SKIP') { warn "Skipping build because of BUILD.SKIP\n"; return; } if ($self->opt_has_value('resume') && -e $smoke_html_filename) { warn "Skipping tests because --resume is given and smoke.html exists\n"; return; } my $dist = Dist::Joseki->get_dist_type; $dist->ACTION_default; # Run smoke tests. Assumes that 'make' has already been run. my $meta = LoadFile('META.yml') or die "can't load META.yml\n"; my $distname = $meta->{name}; local $ENV{HARNESS_VERBOSE} = 1; my $model = Test::TAP::Model::Visual->new_with_tests(glob("t/*.t")); open my $fh, '>', $smoke_html_filename or die "can't open $smoke_html_filename for writing: $!\n"; my $v = Test::TAP::HTMLMatrix->new($model, $distname); $v->has_inline_css(1); print $fh "$v"; close $fh or die "can't close $smoke_html_filename: $!\n"; # force scalar context so localtime() outputs readable string my $start_time = localtime($model->{meat}{start_time}); my $end_time = localtime($model->{meat}{end_time}); my %summary = ( distname => $distname, start_time => $start_time, end_time => $end_time, ); for ( qw(percentage seen todo skipped passed failed unexpectedly_succeeded ratio) ) { my $method = "total_$_"; $summary{$_} = $model->$method; } DumpFile($smoke_yaml_filename, \%summary); # Don't distclean so the generated test files (t/embedded) remain: They # are referenced from smoke.html pages. } sub run_coverage_tests { my $self = shift; return unless $self->opt_has_value('cover'); if (-e 'BUILD.SKIP') { warn "Skipping coverage tests because of BUILD.SKIP\n"; return; } if (-e 'COVER.SKIP') { warn "Skipping coverage tests because of COVER.SKIP\n"; return; } # Run coverage tests. Assumes that 'make' has already been run. $self->safe_system('cover -delete'); $self->safe_system( 'HARNESS_PERL_SWITCHES=-MDevel::Cover=-ignore,^inc/ make test'); $self->safe_system('cover'); } sub create_summary { my $self = shift; my $summary_dir = abs_path(dirname($self->opt('summary'))); my @smoke; find( sub { return unless -f && $_ eq 'smoke.yaml'; return if -e 'BUILD.SKIP'; my $summary = LoadFile($_); unless ($summary->{distname}) { warn "$File::Find::name defines no distname\n"; return; } # assume the summary_dir path is above all of the @basedir paths (my $rel_dir = $File::Find::dir) =~ s!^$summary_dir/!!; $summary->{link} = "$rel_dir/smoke.html"; my $tee = 'smoketee.txt'; if (-e $tee) { $summary->{rawlink} = "$rel_dir/$tee"; my $raw = do { local (@ARGV, $/) = $tee; <> }; $summary->{rawalert}++ if $raw =~ /Use of uninitialized value/ || $raw =~ / at .* line \d+/ || $raw =~ /Failed test / || $raw =~ /Looks like you failed/ || $raw =~ /Looks like you planned \d+ tests but ran \d+ extra/ || $raw =~ /No tests run!/; } my $coverage_html = "$File::Find::dir/cover_db/coverage.html"; if (-e $coverage_html) { $summary->{coverage_link} = "$rel_dir/cover_db/coverage.html"; open my $fh, '<', $coverage_html or die "can't open $coverage_html: $!\n"; my $html = do { local $/; <$fh> }; close $fh or die "can't close $coverage_html: $!\n"; # crude but effective ($summary->{coverage_total}) = $html =~ m!">(\d+\.\d+)\D+$!s; } $summary->{coverage_total} = sprintf "%.2f", defined $summary->{coverage_total} ? $summary->{coverage_total} : 0; push @smoke => $summary; }, Dist::Joseki::Find->new->projroot ); @smoke = map { $_->[0] } sort { $a->[1] cmp $b->[1] } map { [ $_, $_->{distname} ] } @smoke; my $template = $self->get_template; my $tt = Template->new; $tt->process( \$template, { smoke => \@smoke, errors => scalar($self->dist_errors), config => myconfig(), }, $self->opt('summary') ) || die $tt->error; } sub run { my $self = shift; $self->SUPER::run(@_); $self->create_summary; } sub run_single { my $self = shift; $self->SUPER::run_single(@_); $self->assert_is_dist_base_dir; $self->run_smoke_tests; $self->run_coverage_tests; # create summary here as well as in run() so if we're iterating over all # dists we can watch the summary grow $self->create_summary; } sub handle_dist_error { my ($self, $dist, $error) = @_; # we maintain a hash of lists, so get a reference and manipulate it # directly my $dist_errors = $self->dist_errors; push @{ $dist_errors->{$dist} } => $error; } sub get_template { <<'EOTEMPLATE' } Smoke Test Result Summary

Smoke Test Result Summary

[% FOR dist = smoke; IF dist.ratio == 0; tdclass = "allfail"; ELSIF dist.ratio == 1; tdclass = "allpass"; ELSE; tdclass = "partial"; END; %] [% IF dist.rawlink %] [% IF dist.rawalert; rawtdclass = "allfail"; rawtext = "warn"; ELSE; rawtdclass = "allpass"; rawtext = "ok"; END; %] [% ELSE %] [% END %] [% IF dist.coverage_total + 0 == 0; covertdclass = "allfail"; ELSIF dist.coverage_total + 0 == 100; covertdclass = "allpass"; ELSE; covertdclass = "partial"; END; %] [% END %]
Distribution raw coverage tests ok fail todo skip unexp.
success
total time
[% dist.distname %] [% rawtext %] MISSING [% IF dist.coverage_link %] [% END %] [% dist.coverage_total %]% [% IF dist.coverage_link %] [% END %] [% dist.seen %] [% dist.passed %] [% dist.failed %] [% dist.todo %] [% dist.skipped %] [% dist.unexpectedly_succeeded %] [% dist.percentage %] [% dist.end_time %]
[% IF errors.size > 0 %]

Errors

There were problems.

[% FOREACH dist IN errors.keys.sort %]

[% dist %]

[% END %] [% END %]

Config


[% config %]

EOTEMPLATE sub hook_in_dist_loop_begin { my ($self, $dist) = @_; $self->SUPER::hook_in_dist_loop_begin($dist); $self->print_header($dist); } 1; __END__ =head1 NAME Dist::Joseki::Command::smoke - run smoke tests for your distribution =head1 SYNOPSIS # dist smoke =head1 DESCRIPTION This command plugin for L gives you a new command: C runs smoke tests for your distribution. =head1 METHODS =over 4 =back =head1 BUGS AND LIMITATIONS No bugs have been reported. Please report any bugs or feature requests through the web interface at L. =head1 INSTALLATION See perlmodinstall for information and options on installing Perl modules. =head1 AVAILABILITY The latest version of this module is available from the Comprehensive Perl Archive Network (CPAN). Visit L to find a CPAN site near you. Or see L. The development version lives at L. Instead of sending patches, please fork this project using the standard git and github infrastructure. =head1 AUTHORS Marcel GrEnauer, C<< >> =head1 COPYRIGHT AND LICENSE Copyright 2010 by Marcel GrEnauer This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut