File Coverage

blib/lib/POE/Component/CPAN/Reporter.pm
Criterion Covered Total %
statement 153 292 52.4
branch 50 132 37.8
condition 10 39 25.6
subroutine 20 36 55.5
pod 10 10 100.0
total 243 509 47.7


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