File Coverage

blib/lib/POE/Component/CPAN/Reporter.pm
Criterion Covered Total %
statement 150 289 51.9
branch 50 132 37.8
condition 8 39 20.5
subroutine 19 35 54.2
pod 10 10 100.0
total 237 505 46.9


line stmt bran cond sub pod time code
1             package POE::Component::CPAN::Reporter;
2             $POE::Component::CPAN::Reporter::VERSION = '0.08';
3             #ABSTRACT: Bringing the power of POE to CPAN smoke testing.
4              
5 7     7   8333 use strict;
  7         20  
  7         218  
6 7     7   38 use warnings;
  7         92  
  7         301  
7 7     7   42 use POE qw(Wheel::Run);
  7         15  
  7         64  
8 7     7   122848 use Storable;
  7         16910  
  7         545  
9              
10             my $GOT_KILLFAM;
11              
12             BEGIN {
13 7     7   28 $GOT_KILLFAM = 0;
14 7         18 eval {
15 7         18619 require Proc::ProcessTable;
16 0         0 $GOT_KILLFAM = 1;
17             };
18             }
19              
20             sub spawn {
21 7     7 1 4510 my $package = shift;
22 7         32 my %opts = @_;
23 7         56 $opts{lc $_} = delete $opts{$_} for keys %opts;
24 7         22 my $options = delete $opts{options};
25              
26 7 50       35 if ( $^O eq 'MSWin32' ) {
27 0         0 eval { require Win32; };
  0         0  
28 0 0       0 if ($@) { die "Win32 but failed to load:\n$@" }
  0         0  
29 0         0 eval { require Win32::Job; };
  0         0  
30 0 0       0 if ($@) { die "Win32::Job but failed to load:\n$@" }
  0         0  
31 0         0 eval { require Win32::Process; };
  0         0  
32 0 0       0 if ($@) { die "Win32::Process but failed to load:\n$@" }
  0         0  
33             }
34              
35 7         21 my $self = bless \%opts, $package;
36 7 50       144 $self->{session_id} = POE::Session->create(
37             object_states => [
38             $self => { shutdown => '_shutdown',
39             submit => '_command',
40             push => '_command',
41             unshift => '_command',
42             check => '_command',
43             indices => '_command',
44             },
45             $self => [ qw(_start _spawn_wheel _wheel_error _wheel_closed _wheel_stdout _wheel_stderr _wheel_idle _wheel_kill _sig_child _sig_handle) ],
46             ],
47             heap => $self,
48             ( ref($options) eq 'HASH' ? ( options => $options ) : () ),
49             )->ID();
50 7         1305 return $self;
51             }
52              
53             sub session_id {
54 0     0 1 0 return $_[0]->{session_id};
55             }
56              
57             sub pending_jobs {
58 2     2 1 5 return @{ $_[0]->{job_queue} };
  2         11  
59             }
60              
61             sub current_job {
62 0     0 1 0 my $self = shift;
63 0 0       0 return unless $self->{_current_job};
64 0         0 my $item = Storable::dclone( $self->{_current_job} );
65 0         0 return $item;
66             }
67              
68             sub current_log {
69 0     0 1 0 my $self = shift;
70 0 0       0 return unless $self->{_wheel_log};
71 0         0 my $item = Storable::dclone( $self->{_wheel_log} );
72 0         0 return $item;
73             }
74              
75             sub pause_queue {
76 1     1 1 717 my $self = shift;
77 1         6 $self->{paused} = 1;
78             }
79              
80             sub resume_queue {
81 1     1 1 4 my $self = shift;
82 1         5 my $pause = delete $self->{paused};
83 1 50       12 $poe_kernel->post( $self->{session_id}, '_spawn_wheel' ) if $pause;
84             }
85              
86             sub paused {
87 3     3 1 5003994 return $_[0]->{paused};
88             }
89              
90             sub statistics {
91 1     1 1 3990 my $self = shift;
92 1         3 my @stats;
93 1         8 push @stats, $self->{stats}->{$_} for qw(started totaljobs avg_run min_run max_run);
94 1 50       8 return @stats if wantarray;
95 0         0 return \@stats;
96             }
97              
98             sub shutdown {
99 0     0 1 0 my $self = shift;
100 0         0 $poe_kernel->post( $self->{session_id}, 'shutdown' );
101             }
102              
103             sub _start {
104 7     7   3228 my ($kernel,$self) = @_[KERNEL,OBJECT];
105 7         40 $kernel->sig( 'HUP', '_sig_handle' );
106 7         442 $self->{session_id} = $_[SESSION]->ID();
107 7 50       46 if ( $self->{alias} ) {
108 7         33 $kernel->alias_set( $self->{alias} );
109             } else {
110 0         0 $kernel->refcount_increment( $self->{session_id} => __PACKAGE__ );
111             }
112 7         318 $self->{job_queue} = [ ];
113 7 50       34 $self->{idle} = 600 unless $self->{idle};
114 7 50       54 $self->{timeout} = 3600 unless $self->{timeout};
115             $self->{stats} = {
116 7         70 started => time(),
117             totaljobs => 0,
118             avg_run => 0,
119             min_run => 0,
120             max_run => 0,
121             _sum => 0,
122             };
123 7 50       22 $ENV{PERL_CPAN_REPORTER_DIR} = $self->{reporterdir} if $self->{reporterdir};
124 7         28 undef;
125             }
126              
127             sub _sig_handle {
128 1     1   239 $poe_kernel->sig_handled();
129             }
130              
131             sub _shutdown {
132 7     7   4316 my ($kernel,$self) = @_[KERNEL,OBJECT];
133 7         78 $kernel->sig( 'HUP' );
134 7         462 $kernel->sig( 'KILL' );
135 7         308 $kernel->alias_remove( $_ ) for $kernel->alias_list();
136 7 50       665 $kernel->refcount_decrement( $self->{session_id} => __PACKAGE__ ) unless $self->{alias};
137 7         18 $kernel->refcount_decrement( $_->{session}, __PACKAGE__ ) for @{ $self->{job_queue} };
  7         27  
138 7         22 $self->{_shutdown} = 1;
139 7         49 undef;
140             }
141              
142             sub _command {
143 7     7   8936 my ($kernel,$self,$state,$sender) = @_[KERNEL,OBJECT,STATE,SENDER];
144 7 50       36 return if $self->{_shutdown};
145 7         15 my $args;
146 7 50       33 if ( ref( $_[ARG0] ) eq 'HASH' ) {
147 7         14 $args = { %{ $_[ARG0] } };
  7         42  
148             } else {
149 0         0 $args = { @_[ARG0..$#_] };
150             }
151              
152 7 100       31 $state = 'push' if $state eq 'submit';
153              
154 7         19 $args->{lc $_} = delete $args->{$_} for grep { $_ !~ /^_/ } keys %{ $args };
  27         119  
  7         32  
155              
156 7   33     59 my $ref = $kernel->alias_resolve( $args->{session} ) || $sender;
157 7         324 $args->{session} = $ref->ID();
158              
159 7 50 66     64 if ( !$args->{module} and $state !~ /^(check|indices)$/i ) {
160 0         0 warn "No 'module' specified for $state";
161 0         0 return;
162             }
163              
164 7 50       36 unless ( $args->{event} ) {
165 0         0 warn "No 'event' specified for $state";
166 0         0 return;
167             }
168              
169 7 50 33     38 if ( $state =~ /^(package|author)$/ and !$args->{search} ) {
170 0         0 warn "No 'search' criteria specified for $state";
171 0         0 return;
172             }
173              
174 7         25 $args->{submitted} = time();
175              
176 7 100       31 if ( $state eq 'check' ) {
    100          
177 1 50       7 if ( $^O eq 'MSWin32' ) {
178 0         0 $args->{program} = \&_check_reporter;
179 0   0     0 $args->{program_args} = [ $args->{perl} || $self->{perl} || $^X ];
180             }
181             else {
182 1   33     8 my $perl = $args->{perl} || $self->{perl} || $^X;
183 1         6 $args->{program} = [ $perl, '-MCPAN::Reporter', '-e', 1 ];
184             }
185 1         4 $args->{debug} = 1;
186             }
187             elsif ( $state eq 'indices' ) {
188 1 50       5 $args->{prioritise} = 0 unless $args->{prioritise};
189 1 50       4 if ( $^O eq 'MSWin32' ) {
190 0         0 $args->{program} = \&_reload_indices;
191 0   0     0 $args->{program_args} = [ $args->{perl} || $self->{perl} || $^X ];
192             }
193             else {
194 1   0     7 my $perl = $args->{perl} || $self->{perl} || $^X;
195 1         2 my $code = 'CPAN::Index->reload();';
196 1         56 $args->{program} = [ $perl, '-MCPAN', '-e', $code ];
197             }
198             }
199             else {
200 5 50       19 if ( $^O eq 'MSWin32' ) {
201 0         0 $args->{program} = \&_test_module;
202 0   0     0 $args->{program_args} = [ $args->{perl} || $self->{perl} || $^X, $args->{module} ];
203             }
204             else {
205 5   0     23 my $perl = $args->{perl} || $self->{perl} || $^X;
206 5         11 my $code = 'my $module = shift; local $CPAN::Config->{test_report} = 1; if ( $CPAN::Config->{build_dir_reuse} && $CPAN::META->can(q{reset_tested}) ) { CPAN::Index->reload; $CPAN::META->reset_tested; } test( $module );';
207 5         22 $args->{program} = [ $perl, '-MCPAN', '-e', $code, $args->{module} ];
208             }
209             }
210              
211 7         105 $kernel->refcount_increment( $args->{session}, __PACKAGE__ );
212              
213 7         389 $args->{cmd} = $state;
214              
215 7 50 33     65 if ( $state eq 'unshift' or $args->{prioritise} ) {
216 0         0 unshift @{ $self->{job_queue} }, $args;
  0         0  
217             }
218             else {
219 7         19 push @{ $self->{job_queue} }, $args;
  7         24  
220             }
221              
222 7         40 $kernel->yield( '_spawn_wheel' );
223              
224 7         638 undef;
225             }
226              
227             sub _sig_child {
228 7     7   5298 my ($kernel,$self,$thing,$pid,$status) = @_[KERNEL,OBJECT,ARG0..ARG2];
229 7         17 push @{ $self->{_wheel_log} }, "$thing $pid $status";
  7         45  
230 7 100       164 warn "$thing $pid $status\n" if $self->{debug};
231 7         43 $kernel->delay( '_wheel_idle' );
232 7         439 my $job = delete $self->{_current_job};
233 7         35 $job->{status} = $status;
234 7         23 my $log = delete $self->{_wheel_log};
235 7 50       60 if ( $job->{cmd} eq 'recent' ) {
    50          
236 0         0 pop @{ $log };
  0         0  
237 0         0 s/\x0D$// for @{ $log };
  0         0  
238 0         0 $job->{recent} = $log;
239             }
240             elsif ( $job->{cmd} =~ /^(package|author)$/ ) {
241 0         0 pop @{ $log };
  0         0  
242 0         0 s/\x0D$// for @{ $log };
  0         0  
243 0         0 @{ $job->{results} } = grep { $_ !~ /^\[/ } @{ $log };
  0         0  
  0         0  
  0         0  
244             }
245             else {
246 7         30 $job->{log} = $log;
247             }
248 7         24 $job->{end_time} = time();
249 7 100       28 unless ( $self->{debug} ) {
250 4         22 delete $job->{program};
251 4         12 delete $job->{program_args};
252             }
253             # Stats
254 7         23 my $run_time = $job->{end_time} - $job->{start_time};
255 7 50       45 $self->{stats}->{max_run} = $run_time if $run_time > $self->{stats}->{max_run};
256 7 50       36 $self->{stats}->{min_run} = $run_time if $self->{stats}->{min_run} == 0;
257 7 50       32 $self->{stats}->{min_run} = $run_time if $run_time < $self->{stats}->{min_run};
258 7         24 $self->{stats}->{_sum} += $run_time;
259 7         18 $self->{stats}->{totaljobs}++;
260 7         33 $self->{stats}->{avg_run} = $self->{stats}->{_sum} / $self->{stats}->{totaljobs};
261 7         21 $self->{debug} = delete $job->{global_debug};
262 7 50       33 $ENV{PERL_CPAN_REPORTER_DIR} = delete $job->{backup_env} if $job->{reporterdir};
263 7         71 $kernel->post( $job->{session}, $job->{event}, $job );
264 7         873 $kernel->refcount_decrement( $job->{session}, __PACKAGE__ );
265 7         441 $kernel->yield( '_spawn_wheel' );
266 7         747 $kernel->sig_handled();
267             }
268              
269             sub _spawn_wheel {
270 15     15   27061 my ($kernel,$self) = @_[KERNEL,OBJECT];
271 15 50       65 return if $self->{wheel};
272 15 50       56 return if $self->{_shutdown};
273 15 100       100 return if $self->{paused};
274 14         36 my $job = shift @{ $self->{job_queue} };
  14         44  
275 14 100       87 return unless $job;
276 7 50       29 if ( $job->{reporterdir} ) {
277 0         0 $job->{backup_env} = $ENV{PERL_CPAN_REPORTER_DIR};
278 0         0 $ENV{PERL_CPAN_REPORTER_DIR} = $job->{reporterdir};
279             }
280             $self->{wheel} = POE::Wheel::Run->new(
281             Program => $job->{program},
282             ProgramArgs => $job->{program_args},
283 7         89 StdoutEvent => '_wheel_stdout',
284             StderrEvent => '_wheel_stderr',
285             ErrorEvent => '_wheel_error',
286             CloseEvent => '_wheel_close',
287             );
288 7 50       24856 unless ( $self->{wheel} ) {
289 0         0 warn "Couldn\'t spawn a wheel for $job->{module}\n";
290 0         0 $kernel->refcount_decrement( $job->{session}, __PACKAGE__ );
291 0         0 return;
292             }
293 7 100       48 if ( defined $job->{debug} ) {
294 3         18 $job->{global_debug} = delete $self->{debug};
295 3         15 $self->{debug} = $job->{debug};
296             }
297 7         33 $self->{_wheel_log} = [ ];
298 7         32 $self->{_current_job} = $job;
299 7         63 $job->{PID} = $self->{wheel}->PID();
300 7         64 $job->{start_time} = time();
301 7         87 $kernel->sig_child( $job->{PID}, '_sig_child' );
302 7 100       1518 $kernel->delay( '_wheel_idle', 60 ) unless $job->{cmd} eq 'indices';
303 7         935 undef;
304             }
305              
306             sub _wheel_error {
307 7     7   1745 $poe_kernel->delay( '_wheel_idle' );
308 7         850 delete $_[OBJECT]->{wheel};
309 7         3596 undef;
310             }
311              
312             sub _wheel_closed {
313 0     0   0 $poe_kernel->delay( '_wheel_idle' );
314 0         0 delete $_[OBJECT]->{wheel};
315 0         0 undef;
316             }
317              
318             sub _wheel_stdout {
319 0     0   0 my ($self, $input, $wheel_id) = @_[OBJECT, ARG0, ARG1];
320 0         0 $self->{_wheel_time} = time();
321 0         0 push @{ $self->{_wheel_log} }, $input;
  0         0  
322 0 0       0 warn $input, "\n" if $self->{debug};
323 0         0 undef;
324             }
325              
326             sub _wheel_stderr {
327 6     6   5090 my ($self, $input, $wheel_id) = @_[OBJECT, ARG0, ARG1];
328 6         26 $self->{_wheel_time} = time();
329 6 0 33     56 if ( $^O eq 'MSWin32' and !$self->{_current_job}->{GRP_PID} and my ($pid) = $input =~ /(\d+)/ ) {
      33        
330 0         0 $self->{_current_job}->{GRP_PID} = $pid;
331 0 0       0 warn "Grp PID: $pid\n" if $self->{debug};
332 0         0 return;
333             }
334 6 50       33 push @{ $self->{_wheel_log} }, $input unless $self->{_current_job}->{cmd} eq 'recent';
  6         24  
335 6 100       251 warn $input, "\n" if $self->{debug};
336 6         40 undef;
337             }
338              
339             sub _wheel_idle {
340 0     0     my ($kernel,$self) = @_[KERNEL,OBJECT];
341 0           my $now = time();
342 0 0         if ( $now - $self->{_wheel_time} >= $self->{idle} ) {
343 0           $self->{_current_job}->{idle_kill} = 1;
344 0           $kernel->yield( '_wheel_kill', 'Killing current run due to excessive idle' );
345 0           return;
346             }
347 0 0         if ( $now - $self->{_current_job}->{start_time} >= $self->{timeout} ) {
348 0           $self->{_current_job}->{excess_kill} = 1;
349 0           $kernel->yield( '_wheel_kill', 'Killing current run due to excessive run-time' );
350 0           return;
351             }
352 0           $kernel->delay( '_wheel_idle', 60 );
353 0           return;
354             }
355              
356             sub _wheel_kill {
357 0     0     my ($kernel,$self,$reason) = @_[KERNEL,OBJECT,ARG0];
358 0           push @{ $self->{_wheel_log} }, $reason;
  0            
359 0 0         warn $reason, "\n" if $self->{debug};
360 0 0 0       if ( $^O eq 'MSWin32' and $self->{wheel} ) {
361 0           my $grp_pid = $self->{_current_job}->{GRP_PID};
362 0 0         return unless $grp_pid;
363 0 0         warn Win32::FormatMessage( Win32::GetLastError() )
364             unless Win32::Process::KillProcess( $grp_pid, 0 );
365             }
366             else {
367 0 0         if ( !$self->{no_grp_kill} ) {
    0          
368 0 0         $self->{wheel}->kill(-9) if $self->{wheel};
369             }
370             elsif ( $GOT_KILLFAM ) {
371 0 0         _kill_family( 9, $self->{wheel}->PID() ) if $self->{wheel};
372             }
373             else {
374 0 0         $self->{wheel}->kill(9) if $self->{wheel};
375             }
376             }
377 0           return;
378             }
379              
380             sub _check_reporter {
381 0     0     my $perl = shift;
382 0           my $cmdline = $perl . q{ -MCPAN::Reporter -e 1};
383 0 0         my $job = Win32::Job->new()
384             or die Win32::FormatMessage( Win32::GetLastError() );
385 0 0         my $pid = $job->spawn( $perl, $cmdline )
386             or die Win32::FormatMessage( Win32::GetLastError() );
387 0           warn $pid, "\n";
388 0     0     my $ok = $job->watch( sub { 0 }, 60 );
  0            
389 0           my $hashref = $job->status();
390 0           return $hashref->{$pid}->{exitcode};
391             }
392              
393             sub _test_module {
394 0     0     my $perl = shift;
395 0           my $module = shift;
396 0           my $cmdline = $perl . ' -MCPAN -e "my $module = shift; local $CPAN::Config->{test_report} = 1; if ( $CPAN::Config->{build_dir_reuse} && $CPAN::META->can(q{reset_tested}) ) { CPAN::Index->reload; $CPAN::META->reset_tested; } test( $module );" ' . $module;
397 0 0         my $job = Win32::Job->new()
398             or die Win32::FormatMessage( Win32::GetLastError() );
399 0 0         my $pid = $job->spawn( $perl, $cmdline )
400             or die Win32::FormatMessage( Win32::GetLastError() );
401 0           warn $pid, "\n";
402 0     0     my $ok = $job->watch( sub { 0 }, 60 );
  0            
403 0           my $hashref = $job->status();
404 0           return $hashref->{$pid}->{exitcode};
405             }
406              
407             sub _reload_indices {
408 0     0     my $perl = shift;
409 0           my $cmdline = $perl . ' -MCPAN -e "CPAN::Index->reload();"';
410 0 0         my $job = Win32::Job->new()
411             or die Win32::FormatMessage( Win32::GetLastError() );
412 0 0         my $pid = $job->spawn( $perl, $cmdline )
413             or die Win32::FormatMessage( Win32::GetLastError() );
414 0           warn $pid, "\n";
415 0     0     my $ok = $job->watch( sub { 0 }, 60 );
  0            
416 0           my $hashref = $job->status();
417 0           return $hashref->{$pid}->{exitcode};
418             }
419              
420             sub _kill_family {
421 0     0     my ($signal, @pids) = @_;
422 0           my $pt = Proc::ProcessTable->new;
423 0           my (@procs) = @{$pt->table};
  0            
424 0           my (@kids) = _get_pids( \@procs, @pids );
425 0           @pids = (@pids, @kids);
426 0           kill $signal, reverse @pids;
427             }
428              
429             sub _get_pids {
430 0     0     my($procs, @kids) = @_;
431 0           my @pids;
432 0           foreach my $kid (@kids) {
433 0           foreach my $proc (@$procs) {
434 0 0         if ($proc->ppid == $kid) {
435 0           my $pid = $proc->pid;
436 0           push @pids, $pid, _get_pids( $procs, $pid );
437             }
438             }
439             }
440 0           @pids;
441             }
442              
443             1;
444              
445             __END__