File Coverage

blib/lib/Fennec/Runner.pm
Criterion Covered Total %
statement 133 155 85.8
branch 32 60 53.3
condition 8 19 42.1
subroutine 29 31 93.5
pod 0 11 0.0
total 202 276 73.1


line stmt bran cond sub pod time code
1             package Fennec::Runner;
2 139     139   3652 use strict;
  139         187  
  139         2835  
3 139     139   458 use warnings;
  139         191  
  139         3346  
4              
5 139     139   41811 use Fennec::Util qw/verbose_message/;
  139         261  
  139         353  
6              
7             BEGIN {
8 139     139   51561 my @ltime = localtime;
9 139         464 $ltime[5] += 1900;
10 139         164 $ltime[4] += 1; # months start at 0?
11 139         296 for ( 3, 4 ) {
12 278 50       1041 $ltime[4] = "0$ltime[$_]" unless $ltime[$_] > 9;
13             }
14 139   33     1007 my $seed = $ENV{FENNEC_SEED} || join( '', @ltime[5, 4, 3] );
15 139         598 verbose_message("\n*** Seeding random with date ($seed) ***\n");
16 139         3472 srand($seed);
17             }
18              
19 139     139   711 use Cwd qw/abs_path/;
  139         203  
  139         5260  
20 139     139   572 use Carp qw/carp croak confess/;
  139         196  
  139         5787  
21 139     139   641 use List::Util qw/shuffle/;
  139         277  
  139         12237  
22 139     139   747 use Scalar::Util qw/blessed/;
  139         155  
  139         4976  
23 139     139   580 use Fennec::Util qw/accessors require_module/;
  139         165  
  139         396  
24 139     139   87013 use Fennec::Collector::TB::TempFiles;
  139         392  
  139         3650  
25 139     139   51886 use Parallel::Runner;
  139         1718369  
  139         157598  
26              
27             accessors qw/pid test_classes collector _ran _skip_all/;
28              
29             my $SINGLETON;
30 137 100   137 0 438 sub is_initialized { $SINGLETON ? 1 : 0 }
31              
32       91 0   sub init { }
33              
34             sub import {
35 2     2   18 my $self = shift->new();
36 2 50       8 return unless @_;
37 2         10 $self->_load_guess($_) for @_;
38 2         14 $self->inject_run( scalar caller );
39             }
40              
41             sub inject_run {
42 50     50 0 148 my $self = shift;
43 50         100 my ( $caller, $sub ) = @_;
44              
45 50   50 50   400 $sub ||= sub { $self->run(@_) };
  50         2930  
46              
47 50         250 require Fennec::Util;
48 50         152 Fennec::Util::inject_sub( $caller, 'run', $sub );
49             }
50              
51             sub new {
52 245     245 0 923828 my $class = shift;
53 245         1751 my @caller = caller;
54              
55 245 50       2769 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     1748 croak "Runner was already initialized!"
59             if $SINGLETON && @_;
60              
61 245 100       851 return $SINGLETON if $SINGLETON;
62              
63 139         527 my %params = @_;
64              
65 139   50     717 my $collector_class = $params{collector_class} || 'Fennec::Collector::TB::TempFiles';
66 139         1119 my $collector = $collector_class->new();
67              
68 139         1182 $SINGLETON = bless(
69             {
70             test_classes => [],
71             pid => $$,
72             collector => $collector,
73             },
74             $class
75             );
76              
77 139         828 $SINGLETON->init(%params);
78              
79 139         562 return $SINGLETON;
80             }
81              
82             sub _load_guess {
83 2     2   4 my $self = shift;
84 2         6 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     18 return $self->load_file($item)
92             if $item =~ m/\.(pm|t|pl|ft)$/i
93             || $item =~ m{/};
94              
95 2 50 33     26 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 1126 my $self = shift;
104 47         1092 my ($file) = @_;
105 47         4619 print "Loading: $file\n";
106 47 50       577 eval { require $file; 1 } || $self->exception( $file, $@ );
  47         58646  
  46         392  
107             }
108              
109             sub load_module {
110 2     2 0 4 my $self = shift;
111 2         4 my $module = shift;
112 2         96 print "Loading: $module\n";
113 2 50       8 eval { require_module $module } || $self->exception( $module, $@ );
  2         10  
114             }
115              
116             sub check_pid {
117 341     341 0 2827 my $self = shift;
118 341 50       6687 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 464 my $self = shift;
138 182         436 my ($max) = @_;
139              
140 182         1684 my $runner = Parallel::Runner->new($max);
141              
142             $runner->reap_callback(
143             sub {
144 546     546   46250564 my ( $status, $pid, $pid_again, $proc ) = @_;
145              
146             # Status as returned from system, so 0 is good, 1+ is bad.
147 546 50       3283 $self->exception( "Child process did not exit cleanly", "Status: $status" )
148             if $status;
149             }
150 182         5108 );
151              
152 182     4029   2700 $runner->iteration_callback( sub { $self->collector->collect } );
  4029         358732328  
153              
154 182         1107 return $runner;
155             }
156              
157             sub run {
158 89     89 0 246 my $self = shift;
159 89         257 my ($follow) = @_;
160              
161 89         431 $self->_ran(1);
162              
163 89         181 for my $class ( shuffle @{$self->test_classes} ) {
  89         444  
164 89 50       315 next unless $class;
165 89         340 $self->run_test_class($class);
166 20         247 $self->check_pid;
167             }
168              
169 20 100       192 if ($follow) {
170 1         6 $self->collector->collect;
171 1         17 verbose_message("Entering final follow-up stage\n");
172 1         10 $follow->();
173             }
174              
175 20         270 $self->collector->collect;
176 20         150 $self->collector->finish();
177             }
178              
179             sub run_test_class {
180 134     134 0 279 my $self = shift;
181 134         327 my ($class) = @_;
182              
183 134 50       529 return unless $class;
184              
185 134         1020 verbose_message("Entering workflow stage: $class\n");
186 134 50       1232 return unless $class->can('TEST_WORKFLOW');
187              
188 134 100       1005 my $instance = $class->can('new') ? $class->new : bless( {}, $class );
189 134         655 my $ptests = $self->prunner( $class->FENNEC->parallel );
190 134 100       506 my $pforce = $class->FENNEC->parallel ? 1 : 0;
191 134         573 my $meta = $instance->TEST_WORKFLOW;
192 134         1537 my $orig_cwd = abs_path;
193              
194 134     122   1097 $meta->test_wait( sub { $ptests->finish } );
  122         676  
195             $meta->test_run(
196             sub {
197 526     526   1306 my ($run) = @_;
198             $ptests->run(
199             sub {
200 173         297571 chdir $orig_cwd;
201 173         30022 local %ENV = %ENV;
202 173         3563 $run->();
203 173         2091 $self->collector->end_pid();
204             },
205 526         4777 $pforce
206             );
207             }
208 134         1329 );
209              
210 134         526 Test::Workflow::run_tests($instance);
211 31         32801 $ptests->finish;
212              
213 31 100       1861 if ( my $post = $class->FENNEC->post ) {
214 5         75 $self->collector->collect;
215 5         121 verbose_message("Entering follow-up stage: $class\n");
216 5 50       26 eval { $post->(); 1 } || $self->exception( 'done_testing', $@ );
  5         63  
  5         633  
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   55109 return if $?;
251 139 50       933 return unless $SINGLETON;
252 139 100       2754 return unless $PID == $$;
253 22         124 my $failed = $SINGLETON->collector->test_failed;
254 22 50       306 return unless $failed;
255 0         0 $? = $failed;
256             }
257              
258             1;
259              
260             __END__