File Coverage

blib/lib/Fennec/Runner.pm
Criterion Covered Total %
statement 134 156 85.9
branch 32 60 53.3
condition 8 19 42.1
subroutine 29 31 93.5
pod 0 11 0.0
total 203 277 73.2


line stmt bran cond sub pod time code
1             package Fennec::Runner;
2 139     139   4838 use strict;
  139         252  
  139         4363  
3 139     139   678 use warnings;
  139         192  
  139         3660  
4              
5 139     139   134205 use Fennec::Util qw/verbose_message/;
  139         423  
  139         567  
6              
7             BEGIN {
8 139     139   75718 my @ltime = localtime;
9 139         675 $ltime[5] += 1900;
10 139         194 $ltime[4] += 1; # months start at 0?
11 139         428 for ( 3, 4 ) {
12 278 50       1632 $ltime[4] = "0$ltime[$_]" unless $ltime[$_] > 9;
13             }
14 139   33     2103 my $seed = $ENV{FENNEC_SEED} || join( '', @ltime[5, 4, 3] );
15 139         752 verbose_message("\n*** Seeding random with date ($seed) ***\n");
16 139         5698 srand($seed);
17             }
18              
19 139     139   1101 use Cwd qw/abs_path/;
  139         262  
  139         7398  
20 139     139   891 use Carp qw/carp croak confess/;
  139         200  
  139         8749  
21 139     139   746 use List::Util qw/shuffle/;
  139         283  
  139         18523  
22 139     139   880 use Scalar::Util qw/blessed/;
  139         304  
  139         7301  
23 139     139   679 use Fennec::Util qw/accessors require_module/;
  139         913  
  139         742  
24 139     139   142168 use Fennec::Collector::TB::TempFiles;
  139         460  
  139         4773  
25 139     139   229162 use Parallel::Runner;
  139         4045117  
  139         307871  
26              
27             accessors qw/pid test_classes collector _ran _skip_all/;
28              
29             my $SINGLETON;
30 137 100   137 0 884 sub is_initialized { $SINGLETON ? 1 : 0 }
31              
32 91     91 0 183 sub init { }
33              
34             sub import {
35 2     2   20 my $self = shift->new();
36 2 50       12 return unless @_;
37 2         12 $self->_load_guess($_) for @_;
38 2         14 $self->inject_run( scalar caller );
39             }
40              
41             sub inject_run {
42 50     50 0 100 my $self = shift;
43 50         148 my ( $caller, $sub ) = @_;
44              
45 50   50 50   594 $sub ||= sub { $self->run(@_) };
  50         1798  
46              
47 50         446 require Fennec::Util;
48 50         300 Fennec::Util::inject_sub( $caller, 'run', $sub );
49             }
50              
51             sub new {
52 245     245 0 2136159 my $class = shift;
53 245         2085 my @caller = caller;
54              
55 245 50       3940 croak "listener_class is deprecated, it was thought nobody used it... sorry. See Fennec::Collector now"
56             if $class->can('listener_class');
57              
58 245 50 66     2453 croak "Runner was already initialized!"
59             if $SINGLETON && @_;
60              
61 245 100       1361 return $SINGLETON if $SINGLETON;
62              
63 139         537 my %params = @_;
64              
65 139   50     1148 my $collector_class = $params{collector_class} || 'Fennec::Collector::TB::TempFiles';
66 139         1649 my $collector = $collector_class->new();
67              
68 139         1793 $SINGLETON = bless(
69             {
70             test_classes => [],
71             pid => $$,
72             collector => $collector,
73             },
74             $class
75             );
76              
77 139         1096 $SINGLETON->init(%params);
78              
79 139         854 return $SINGLETON;
80             }
81              
82             sub _load_guess {
83 2     2   6 my $self = shift;
84 2         4 my ($item) = @_;
85              
86 2 50 33     10 if ( ref $item && ref $item eq 'CODE' ) {
87 0         0 $self->_load_guess($_) for ( $self->$item );
88 0         0 return;
89             }
90              
91 2 50 33     24 return $self->load_file($item)
92             if $item =~ m/\.(pm|t|pl|ft)$/i
93             || $item =~ m{/};
94              
95 2 50 33     60 return $self->load_module($item)
96             if $item =~ m/::/
97             || $item =~ m/^\w[\w\d_]+$/;
98              
99 0         0 die "Not sure how to load '$item'\n";
100             }
101              
102             sub load_file {
103 47     47 0 745 my $self = shift;
104 47         972 my ($file) = @_;
105 47         23994 print "Loading: $file\n";
106 47 50       942 eval { require $file; 1 } || $self->exception( $file, $@ );
  47         91733  
  46         528  
107             }
108              
109             sub load_module {
110 2     2 0 4 my $self = shift;
111 2         6 my $module = shift;
112 2         1150 print "Loading: $module\n";
113 2 50       12 eval { require_module $module } || $self->exception( $module, $@ );
  2         12  
114             }
115              
116             sub check_pid {
117 342     342 0 5073 my $self = shift;
118 342 50       2608 return unless $self->pid != $$;
119 0         0 die "PID has changed! Did you forget to exit a child process?\n";
120             }
121              
122             sub exception {
123 0     0 0 0 my $self = shift;
124 0         0 my ( $name, $exception ) = @_;
125              
126 0 0       0 if ( $exception =~ m/^FENNEC_SKIP: (.*)\n/ ) {
127 0         0 $self->collector->ok( 1, "SKIPPING $name: $1" );
128 0         0 $self->_skip_all(1);
129             }
130             else {
131 0         0 $self->collector->ok( 0, $name );
132 0         0 $self->collector->diag($exception);
133             }
134             }
135              
136             sub prunner {
137 182     182 0 587 my $self = shift;
138 182         352058 my ($max) = @_;
139              
140 182         4163 my $runner = Parallel::Runner->new($max);
141              
142             $runner->reap_callback(
143             sub {
144 530     530   49820215 my ( $status, $pid, $pid_again, $proc ) = @_;
145              
146             # Status as returned from system, so 0 is good, 1+ is bad.
147 530 50       4447 $self->exception( "Child process did not exit cleanly", "Status: $status" )
148             if $status;
149             }
150 182         7606 );
151              
152 182     9591   4108 $runner->iteration_callback( sub { $self->collector->collect } );
  9591         932720839  
153              
154 182         2076 return $runner;
155             }
156              
157             sub run {
158 89     89 0 290 my $self = shift;
159 89         517 my ($follow) = @_;
160              
161 89         609 $self->_ran(1);
162              
163 89         181 for my $class ( shuffle @{$self->test_classes} ) {
  89         524  
164 89 50       378 next unless $class;
165 89         758 $self->run_test_class($class);
166 20         204 $self->check_pid;
167             }
168              
169 20 100       193 if ($follow) {
170 1         7 $self->collector->collect;
171 1         16 verbose_message("Entering final follow-up stage\n");
172 1         8 $follow->();
173             }
174              
175 20         184 $self->collector->collect;
176 20         131 $self->collector->finish();
177             }
178              
179             sub run_test_class {
180 134     134 0 340 my $self = shift;
181 134         331 my ($class) = @_;
182              
183 134 50       550 return unless $class;
184              
185 134         1085 verbose_message("Entering workflow stage: $class\n");
186 134 50       2148 return unless $class->can('TEST_WORKFLOW');
187              
188 134 100       1688 my $instance = $class->can('new') ? $class->new : bless( {}, $class );
189 134         923 my $ptests = $self->prunner( $class->FENNEC->parallel );
190 134 100       687 my $pforce = $class->FENNEC->parallel ? 1 : 0;
191 134         711 my $meta = $instance->TEST_WORKFLOW;
192 134         2643 my $orig_cwd = abs_path;
193              
194 134     114   1377 $meta->test_wait( sub { $ptests->finish } );
  114         765  
195             $meta->test_run(
196             sub {
197 526     526   3218 my ($run) = @_;
198             $ptests->run(
199             sub {
200 173         686110 chdir $orig_cwd;
201 173         34268 local %ENV = %ENV;
202 173         5522 $run->();
203 173         2473 $self->collector->end_pid();
204             },
205 526         5425 $pforce
206             );
207             }
208 134         1713 );
209              
210 134         708 Test::Workflow::run_tests($instance);
211 31         188706 $ptests->finish;
212              
213 31 100       2006 if ( my $post = $class->FENNEC->post ) {
214 5         44 $self->collector->collect;
215 5         93 verbose_message("Entering follow-up stage: $class\n");
216 5 50       119 eval { $post->(); 1 } || $self->exception( 'done_testing', $@ );
  5         94  
  5         342  
217             }
218             }
219              
220             sub DESTROY {
221 0     0     my $self = shift;
222 0 0         return unless $self->pid == $$;
223 0 0         return if $self->_ran;
224 0 0         return if $self->_skip_all;
225 0 0         return if $^C; # No warning in syntax check
226              
227 0           my $tests = join "\n" => map { "# * $_" } @{$self->test_classes};
  0            
  0            
228              
229 0           print STDERR <<" EOT";
230              
231             # *****************************************************************************
232             # ERROR: done_testing() was never called!
233             #
234             # This usually means you ran a Fennec test file directly with prove or perl,
235             # but the file does not call done_testing at the end.
236             #
237             # Fennec Tests loaded, but not run:
238             $tests
239             #
240             # *****************************************************************************
241              
242             EOT
243 0           exit(1);
244             }
245              
246             # Set exit code to failed tests
247             my $PID = $$;
248              
249             END {
250 139 50   139   48545 return if $?;
251 139 50       1714 return unless $SINGLETON;
252 139 100       4145 return unless $PID == $$;
253 22         223 my $failed = $SINGLETON->collector->test_failed;
254 22 50       322 return unless $failed;
255 0         0 $? = $failed;
256             }
257              
258             1;
259              
260             __END__