File Coverage

blib/lib/App/Prove/Plugin/ClusterSlave.pm
Criterion Covered Total %
statement 64 130 49.2
branch 11 42 26.1
condition 5 12 41.6
subroutine 14 19 73.6
pod 0 6 0.0
total 94 209 44.9


line stmt bran cond sub pod time code
1             package App::Prove::Plugin::ClusterSlave;
2 1     1   172355 use strict;
  1         2  
  1         38  
3 1     1   5 use warnings;
  1         2  
  1         27  
4 1     1   1460 use Getopt::Long;
  1         34562  
  1         9  
5 1     1   182 use Carp;
  1         3  
  1         71  
6 1     1   1200 use IO::Handle;
  1         22081  
  1         72  
7 1     1   1070 use IO::Socket;
  1         25918  
  1         5  
8 1     1   2520 use IO::Select;
  1         1797  
  1         64  
9 1     1   922 use IPC::Open3;
  1         4309  
  1         92  
10 1     1   1287 use Sys::Hostname;
  1         1990  
  1         1122  
11              
12             our $TEARDOWN_IN_PROCESS_CALLBACK = sub {};
13              
14             END {
15 1     1   7912 $TEARDOWN_IN_PROCESS_CALLBACK->();
16             };
17              
18             sub parse_additional_options {
19 3     3 0 7 my ($class, $app) = @_;
20              
21 3         7 my @args = @{$app->{argv}};
  3         9  
22 3         25 local @ARGV = @args;
23 3         40 Getopt::Long::Configure(qw(no_ignore_case bundling pass_through));
24              
25 3         273 my ($master_host, $master_port, $credentials, $lsf_startup, $lsf_startup_in_process, $lsf_teardown_in_process, $lsf_test_in_process);
26 3 50       21 GetOptions(
27             'master-host=s' => \$master_host,
28             'master-port=s' => \$master_port,
29             'credentials=s' => \$credentials,
30             'lsf-startup=s' => \$lsf_startup,
31             'lsf-startup-in-process=s' => \$lsf_startup_in_process,
32             'lsf-teardown-in-process=s' => \$lsf_teardown_in_process,
33             'lsf-test-in-process' => \$lsf_test_in_process,
34             ) or croak('Unable to parse parameters');
35              
36 3         5518 $app->{argv} = [@ARGV];
37              
38 3 50       13 if (!defined($master_host)) {
39 0         0 die "Did not specify --master-host";
40             }
41 3 50       12 if (!defined($master_port)) {
42 0         0 die "Did not specify --master-port";
43             }
44 3 50       10 if (!defined($credentials)) {
45 0         0 die "Did not specify --credentials";
46             }
47              
48 3         19 return ($master_host, $master_port, $credentials, $lsf_startup, $lsf_startup_in_process, $lsf_teardown_in_process, $lsf_test_in_process);
49             }
50              
51             sub load {
52 3     3 0 3401 my ($class, $p) = @_;
53 3         9 my $app = $p->{app_prove};
54 3         19 my ($master_host, $master_port, $credentials, $lsf_startup, $lsf_startup_in_process, $lsf_teardown_in_process, $lsf_test_in_process) =
55             $class->parse_additional_options($app);
56              
57 3 50       10 if ($lsf_teardown_in_process) {
58             $TEARDOWN_IN_PROCESS_CALLBACK = sub {
59 0     0   0 $class->eval_perl_script_in_process($lsf_teardown_in_process);
60 0         0 };
61             }
62              
63 3         13 my @sigs = (qw(INT KILL ABRT TERM HUP STOP));
64 3     0   7 local @SIG{@sigs} = map { sub { exit 1 } } @sigs;
  18         660  
  0         0  
65              
66 3 50       12 if ($lsf_startup) {
67 3 100 66     41969 if (system($lsf_startup) || $?) {
68 2         1001 die "Startup failed";
69             }
70             }
71              
72 1 50       20 if ($lsf_startup_in_process) {
73 0         0 my $includes = $app->{includes};
74 0 0       0 if ($includes) {
75 0 0       0 $ENV{PERL5LIB} = (join ':', map {($_ =~ m{^/}) ? $_ : $ENV{PWD} . "/$_"} @$includes) . ':' . $ENV{PERL5LIB};
  0         0  
76 0         0 @INC = ($class->includes($includes), @INC);
77             }
78 0         0 $class->eval_perl_script_in_process($lsf_startup_in_process);
79             }
80              
81 1   50     60 $class->run_client($master_host, $master_port, $credentials, $lsf_test_in_process, $app->{includes}, ($app->{test_args} || []));
82             }
83              
84             sub includes {
85 0     0 0 0 my $class = shift;
86 0   0     0 my $includes = (shift) || [];
87 0 0       0 return map {($_ =~ m{^/}) ? $_ : $ENV{PWD} . "/$_"} @$includes;
  0         0  
88             }
89              
90             sub eval_perl_script_in_process {
91 0     0 0 0 my $class = shift;
92 0         0 my $job_info = shift;
93 0         0 my $args = shift;
94              
95 0         0 my $cwd = File::Spec->rel2abs('.');
96              
97 0         0 local $0 = $job_info; #fixes FindBin (in English $0 means $PROGRAM_NAME)
98 1     1   23 no strict; # default for Perl5
  1         3  
  1         975  
99             {
100              
101 0         0 package main;
102 0 0       0 local @ARGV = $args ? @$args : ();
103 0         0 do $0; # do $0; could be enough for strict scripts
104 0         0 chdir($cwd);
105              
106 0 0       0 if ($@) {
107 0         0 die $@;
108             }
109             }
110             }
111              
112             sub get_test {
113 0     0 0 0 my ($class, $socket, $credentials) = @_;
114              
115 0         0 $socket->print("$credentials\n");
116              
117 0         0 my $begin_line = $socket->getline;
118              
119             # Master prove is finished
120 0 0       0 if (!defined($begin_line)) {
121 0         0 exit(0);
122             }
123              
124 0 0       0 if ($begin_line ne "BEGIN\n") {
125 0         0 die "Master prove sent unknown protocol message: $begin_line";
126             }
127              
128 0         0 my @lines;
129 0         0 while (my $message_line = $socket->getline) {
130 0 0       0 if ($message_line eq "END\n") {
131 0         0 last;
132             }
133 0         0 push @lines, $message_line;
134             }
135              
136 0         0 my $raw_text = join('', @lines);
137 0         0 return eval("$raw_text");
138             }
139              
140             sub run_client {
141 1     1 0 95 my ($class, $master_host, $master_port, $credentials, $test_in_process, $includes, $test_args) = @_;
142 1         3 my $socket;
143 1         155 my $timeout = time + 10;
144              
145 1   66     18 while (!$socket && time < $timeout) {
146 10002         65201 $socket = IO::Socket::INET->new(
147             PeerAddr => $master_host,
148             PeerPort => $master_port,
149             Proto => 'tcp',
150             );
151 10002 50       11038715 if (!$socket) {
152 10002         69108 sleep(0.5);
153             }
154             }
155              
156 1 50       6 if (!$socket) {
157 1         831 die "Could not connect to master prove process";
158             }
159              
160 0           while (1) {
161 0           my $test_info = $class->get_test($socket, $credentials);
162 0           my $test_source = $test_info->{source};
163 0   0       my $switches = $test_info->{switches} || [];
164              
165 0           $socket->print('# Host: ' . hostname . "\n");
166              
167 0 0         if ($test_in_process) {
168             # We need to fork because we want to create separate test plans within "the same process".
169             # Do not close the socket! We need the same socket to Master open.
170 0           my $pid = fork();
171 0 0         if ($pid) {
172 0           waitpid( $pid, 0 );
173             }
174             else {
175 0           eval {
176             # Redirect STDERR and STDOUT
177 0           local *STDERR = $socket;
178 0           local *STDOUT = $socket;
179              
180             # Intercept all output from Test::More. Output all of them at once.
181 0           require Test::More;
182 0           my $builder = Test::More->builder;
183 0           $builder->output($socket);
184 0           $builder->failure_output($socket);
185 0           $builder->todo_output($socket);
186              
187 0           $class->eval_perl_script_in_process($test_source, $test_args);
188             };
189 0 0         if ($@) {
190 0           $socket->print($@);
191 0           exit(1);
192             }
193 0           exit(0);
194             }
195             }
196             else {
197 0           my $pid = open3(undef, ">&".fileno($socket), undef, 'perl', @$switches, (map {('-I', $_)} $class->includes($includes)), $test_source, @$test_args);
  0            
198 0           waitpid($pid, 0);
199             }
200             }
201             }
202              
203             1;