File Coverage

blib/lib/Test/BrewBuild.pm
Criterion Covered Total %
statement 158 538 29.3
branch 33 184 17.9
condition 3 24 12.5
subroutine 34 54 62.9
pod 19 19 100.0
total 247 819 30.1


line stmt bran cond sub pod time code
1             package Test::BrewBuild;
2 34     34   298648 use strict;
  34         171  
  34         850  
3 34     34   156 use warnings;
  34         56  
  34         820  
4              
5 34     34   189 use Carp qw(croak);
  34         56  
  34         1667  
6 34     34   185 use Cwd qw(getcwd);
  34         59  
  34         1425  
7 34     34   19287 use Data::Dumper;
  34         218709  
  34         2063  
8 34     34   29759 use File::Copy;
  34         88085  
  34         1808  
9 34     34   16970 use File::Copy::Recursive qw(dircopy);
  34         123247  
  34         2201  
10 34     34   235 use File::Find;
  34         64  
  34         1960  
11 34     34   205 use File::Path qw(remove_tree);
  34         1727  
  34         1799  
12 34     34   13279 use File::Temp;
  34         262964  
  34         2276  
13 34     34   21759 use Getopt::Long qw(GetOptionsFromArray);
  34         324515  
  34         143  
14             Getopt::Long::Configure ("no_ignore_case", "pass_through");
15 34     34   14869 use Logging::Simple;
  34         207441  
  34         1008  
16 34     34   32409 use Module::Load;
  34         32709  
  34         191  
17 34     34   14812 use Plugin::Simple default => 'Test::BrewBuild::Plugin::DefaultExec';
  34         414588  
  34         212  
18 34     34   1795 use POSIX qw(strftime);
  34         61  
  34         310  
19 34     34   15651 use Test::BrewBuild::BrewCommands;
  34         84  
  34         1058  
20 34     34   223 use Test::BrewBuild::Constant qw(:all);
  34         59  
  34         3225  
21 34     34   8447 use Test::BrewBuild::Dispatch;
  34         89  
  34         1440  
22 34     34   191 use Test::BrewBuild::Regex;
  34         278  
  34         1794  
23 34     34   14776 use Test::BrewBuild::Tester;
  34         86  
  34         169126  
24              
25             our $VERSION = '2.22';
26              
27             my $log;
28             my $bcmd;
29              
30             sub new {
31 11     11 1 5425 my ($class, %args) = @_;
32 11         33 my $self = bless { }, $class;
33              
34             # see if we have config file data
35              
36 11 50       41 if ($self->config_file) {
37 11         39 $self->_config;
38             }
39              
40             # override the config file and populate the rest of the args
41              
42 11         52 for (keys %args){
43 9         29 $self->{args}{$_} = $args{$_};
44             }
45              
46 11         53 $log = $self->_create_log($args{debug});
47 11         51 $log->_6("in new(), constructing " . __PACKAGE__ . " object");
48              
49 11         806 $bcmd = Test::BrewBuild::BrewCommands->new($log);
50              
51 11         44 $self->_set_plugin();
52              
53 11         1166 $self->tempdir;
54 11         43 $log->_7("using temp bblog dir: " . $self->tempdir);
55              
56 11         888 return $self;
57             }
58             sub options {
59 0     0 1 0 my ($self, $args) = @_;
60 0         0 my %opts;
61              
62 0         0 my @arg_directives = grep {$_ =~ /^-/} @$args;
  0         0  
63              
64 0         0 my $bad_opt = _validate_opts(\@arg_directives);
65              
66             GetOptionsFromArray(
67             $args,
68             "on=s@" => \$opts{on},
69             "n|new=s" => \$opts{new},
70             "r|remove" => \$opts{remove},
71             "R|revdep" => \$opts{revdep},
72             "plugin=s" => \$opts{plugin},
73             "a|args=s@" => \$opts{args},
74             "d|debug=i" => \$opts{debug},
75             "i|install=s@" => \$opts{install},
76             "N|notest" => \$opts{notest},
77             "S|save" => \$opts{save_reports},
78             "l|legacy" => \$opts{legacy},
79             "T|selftest" => \$opts{selftest},
80             "D|dispatch" => \$opts{dispatch},
81             "t|tester=s@" => \$opts{testers},
82             "X|nocache" => \$opts{nocache},
83             "s|setup" => \$opts{setup},
84             "h|help" => \$opts{help},
85 0         0 );
86              
87 0 0       0 $opts{error} = 1 if $bad_opt;
88              
89 0         0 return %opts;
90             }
91             sub config_file {
92 23     23 1 37 shift;
93 23 50       52 if (is_win()){
94 0 0       0 return $ENV{BB_CONF} if $ENV{BB_CONF};
95 0         0 return "$ENV{USERPROFILE}/brewbuild/brewbuild.conf";
96             }
97             else {
98 23 100       66 return $ENV{BB_CONF} if $ENV{BB_CONF};
99 19         69 return "$ENV{HOME}/brewbuild/brewbuild.conf";
100             }
101             }
102             sub is_win {
103 33 50   33 1 145 my $is_win = ($^O =~ /Win/) ? 1 : 0;
104 33         90 return $is_win;
105             }
106             sub brew_info {
107 3     3 1 8 my $self = shift;
108 3         9 my $log = $log->child('brew_info');
109              
110 3         65 my $brew_info;
111              
112 3 50       8 if ($self->{args}{nocache}){
113             # don't use cached info
114 0         0 $brew_info = $bcmd->info;
115             }
116             else {
117 3         18 $brew_info = $bcmd->info_cache;
118             }
119              
120 3 50       47 $log->_6("brew info set to:\n$brew_info") if $brew_info;
121              
122 3         94 return $brew_info;
123             }
124             sub perls_available {
125 1     1 1 1 my $self = shift;
126 1         3 my $log = $log->child('perls_available');
127 1         21 my @perls_available = $bcmd->available($self->legacy, $self->brew_info);
128 1         8 $log->_6("perls available: " . join ', ', @perls_available);
129 1         60 return @perls_available;
130             }
131             sub perls_installed {
132 2     2 1 5 my $self = shift;
133 2         10 my $log = $log->child('perls_installed');
134 2         57 $log->_6("checking perls installed");
135 2         290 return $bcmd->installed($self->legacy, $self->brew_info);
136             }
137             sub instance_install {
138 1     1 1 768 my ($self, $install, $timeout) = @_;
139              
140             # timeout an install after...
141              
142 1 50       4 if (! $timeout){
143 1 50       4 if ($self->{args}{timeout}){
144 0         0 $timeout = $self->{args}{timeout};
145             }
146             else {
147 1         2 $timeout = INSTANCE_INSTALL_TIMEOUT;
148             }
149             }
150              
151 1         3 my $log = $log->child('instance_install');
152              
153 1         27 my @perls_available = $self->perls_available;
154 1         10 my @perls_installed = $self->perls_installed;
155 1         7 my @new_installs;
156              
157 1 50       42 if (ref $install eq 'ARRAY'){
    50          
    50          
158 0         0 for my $version (@$install){
159 0 0 0     0 $version = "perl-$version" if ! $self->is_win && $version !~ /perl/;
160              
161 0 0       0 if ($self->is_win){
162 0 0       0 if ($version !~ /_/){
163 0         0 $log->_7("MSWin: no bit suffix supplied");
164 0 0       0 if (! grep {$_ =~ /$version/} @perls_available){
  0         0  
165 0         0 $version .= '_64';
166 0         0 $log->_7("MSWin: default to available 64-bit $version");
167             }
168             else {
169 0         0 $version .= '_32';
170 0         0 $log->_7("MSWin: no 64-bit version... using $version");
171             }
172             }
173             }
174 0 0       0 $version =~ s/_.*$// if ! $self->is_win;
175              
176 0 0       0 if (! grep { $version eq $_ } @perls_available){
  0         0  
177 0         0 $log->_0("$version is not a valid perl version");
178 0         0 next;
179             }
180              
181 0 0       0 if (grep { $version eq $_ } @perls_installed){
  0         0  
182 0         0 $log->_6("$version is already installed... skipping");
183 0         0 next;
184             }
185 0         0 push @new_installs, $version;
186             }
187             }
188             elsif ($install == -1) {
189 0         0 $log->_5("installing all available perls");
190              
191 0         0 for my $perl (@perls_available){
192 0 0       0 if (grep { $_ eq $perl } @perls_installed) {
  0         0  
193 0         0 $log->_6( "$perl already installed... skipping" );
194 0         0 next;
195             }
196 0         0 push @new_installs, $perl;
197             }
198             }
199             elsif ($install) {
200 1         16 $log->_5("looking to install $install perl instance(s)");
201              
202 1         63 my %avail = map {$_ => 1} @perls_available;
  0         0  
203              
204 1         8 while ($install > 0){
205 1 50       12 last if ! keys %avail;
206              
207 0         0 my $candidate = (keys %avail)[rand keys %avail];
208 0         0 delete $avail{$candidate};
209              
210 0 0       0 if (grep { $_ eq $candidate } @perls_installed) {
  0         0  
211 0         0 $log->_6( "$candidate already installed... skipping" );
212 0         0 next;
213             }
214              
215 0         0 push @new_installs, $candidate;
216 0         0 $install--;
217             }
218             }
219              
220 1 50       10 if (@new_installs){
221 0         0 $log->_4("preparing to install..." . join ', ', @new_installs);
222              
223 0         0 my $install_cmd = $bcmd->install;
224              
225 0         0 for my $ver (@new_installs) {
226 0         0 $log->_0( "installing $ver..." );
227 0         0 $log->_5( "...using cmd: $install_cmd" );
228 0         0 undef $@;
229 0         0 eval {
230             local $SIG{ALRM} = sub {
231 0     0   0 croak "$ver failed to install... skipping"
232 0         0 };
233 0         0 alarm $timeout;
234 0         0 `$install_cmd $ver`;
235 0         0 alarm 0;
236             };
237 0 0       0 if ($@){
238 0         0 $log->_0($@);
239 0         0 $log->_1("install of $ver failed: uninstalling the remnants..");
240 0         0 $self->instance_remove($ver);
241 0         0 next;
242             }
243             }
244 0 0       0 $bcmd->info_cache(1) if ! $self->{args}{nocache};
245             }
246             else {
247 1         4 $log->_5("using existing versions only, nothing to install");
248             }
249             }
250             sub instance_remove {
251 1     1 1 8 my ($self, $version) = @_;
252              
253 1         3 my $log = $log->child('instance_remove');
254              
255 1         25 my @perls_installed = $self->perls_installed;
256              
257 1         7 $log->_6("perls installed: " . join ', ', @perls_installed);
258              
259 1         615 my $remove_cmd = $bcmd->remove;
260              
261 1         52 $log->_4( "using '$remove_cmd' remove command" );
262              
263 1 50       475 if ($version){
264 1         11 $log->_5("$version supplied, removing...");
265 1 50       306 if ($self->is_win){
266 0         0 `$remove_cmd $version 2`
267             }
268             else {
269 1         14246 `$remove_cmd $version 2>/dev/null`;
270             }
271 1 50       86 $bcmd->info_cache(1) if ! $self->{args}{nocache};
272             }
273             else {
274 0         0 $log->_0("removing previous installs...");
275              
276 0         0 for my $installed_perl (@perls_installed){
277              
278 0         0 my $using = $bcmd->using($self->brew_info);
279              
280 0 0       0 if ($using eq $installed_perl) {
281 0         0 $log->_5( "not removing version we're using: $using" );
282 0         0 next;
283             }
284              
285 0         0 $log->_5( "exec'ing $remove_cmd $installed_perl" );
286              
287 0 0       0 if ($bcmd->is_win) {
288 0         0 `$remove_cmd $installed_perl 2>nul`;
289             }
290             else {
291 0         0 `$remove_cmd $installed_perl 2>/dev/null`;
292             }
293 0 0       0 $bcmd->info_cache(1) if ! $self->{args}{nocache};
294             }
295             }
296              
297 1         23 $log->_4("removal of existing perl installs complete...\n");
298             }
299             sub revdep {
300 0     0 1 0 my ($self, %args) = @_;
301              
302 0         0 delete $self->{args}{args};
303              
304             # these args aren't sent through to test()
305              
306 0         0 delete $args{revdep};
307 0         0 delete $self->{args}{delete};
308 0         0 delete $args{remove};
309 0         0 delete $args{install};
310 0         0 delete $args{new};
311              
312 0         0 $args{plugin} = 'Test::BrewBuild::Plugin::TestAgainst';
313              
314 0         0 my @revdeps = $self->revdeps;
315 0         0 my @ret;
316              
317 0         0 my $rlist = "\nreverse dependencies: " . join ', ', @revdeps;
318 0         0 $rlist .= "\n\n";
319 0         0 push @ret, $rlist;
320              
321 0         0 for (@revdeps){
322 0         0 $args{plugin_arg} = $_;
323 0         0 my $bb = __PACKAGE__->new(%args);
324 0         0 $bb->log()->file($self->log()->file());
325 0         0 push @ret, $bb->test;
326             }
327 0         0 return \@ret;
328             }
329             sub test {
330 0     0 1 0 my $self = shift;
331              
332 0 0       0 exit if $self->{args}{notest};
333              
334 0         0 my $log = $log->child('test');
335 0     0   0 local $SIG{__WARN__} = sub {};
336 0         0 $log->_6("warnings trapped locally");
337              
338 0         0 my $failed = 0;
339              
340 0         0 my @perls_installed = $bcmd->installed(0, $self->brew_info);
341              
342 0         0 my $results = $self->_exec;
343              
344             # add perl version info to top of results if we're in a single-instance
345             # run, and the results don't contain that info
346              
347 0 0       0 if ($results !~ /${ re_brewbuild('check_result') }/){
  0         0  
348 0 0 0     0 if ($self->{args}{on} && @{ $self->{args}{on} } == 1){
  0 0       0  
349 0         0 $results = "perl-$self->{args}{on}[0]\n==========\n" . $results;
350             }
351             elsif (@perls_installed == 1){
352 0         0 $results = "$perls_installed[0]\n==========\n" . $results;
353             }
354             }
355              
356 0         0 $log->_7("\n*****\n$results\n*****");
357              
358 0         0 my @ver_results = $results =~ /${ re_brewbuild('check_result') }/g;
  0         0  
359              
360 0         0 $log->_5("got " . scalar @ver_results . " results");
361              
362 0         0 my (@pass, @fail);
363              
364 0         0 for my $result (@ver_results){
365 0         0 my $ver;
366              
367 0 0       0 if ($result =~ /${ re_brewbuild('extract_perl_version') }/){
  0         0  
368 0         0 $ver = $1;
369 0         0 $ver =~ s/[Pp]erl-//;
370             }
371 0         0 my $res;
372              
373 0 0 0     0 if ($result =~ /Successfully tested / && $result !~ /FAIL/){
374 0         0 $log->_6("$ver PASSED...");
375 0         0 $res = 'PASS';
376              
377 0         0 push @pass, "$ver :: $res\n";
378 0         0 $self->_save_reports($ver, $res, $result);
379             }
380             else {
381 0         0 $log->_6("$ver FAILED...");
382 0         0 $res = 'FAIL';
383 0         0 $failed = 1;
384              
385 0         0 push @fail, "$ver :: $res\n";
386 0         0 $self->_save_reports($ver, $res, $result);
387             }
388             }
389              
390 0         0 $self->_copy_logs;
391              
392 0         0 $log->_5(__PACKAGE__ ." run finished\n");
393              
394 0         0 my $ret = "\n";
395 0 0       0 $ret .= "$self->{args}{plugin_arg}\n" if $self->{args}{plugin_arg};
396 0         0 $ret .= $_ for @pass;
397 0         0 $ret .= $_ for @fail;
398 0         0 $ret .= "\n\n";
399              
400 0         0 return $ret;
401             }
402             sub tempdir {
403 22     22 1 37 my $self = shift;
404 22 100       113 return $self->{tempdir} if $self->{tempdir};
405              
406 11         81 my $dir = File::Temp->newdir;
407 11         5344 my $dir_name = $dir->dirname;
408 11         279 $self->{temp_handle} = $dir;
409 11         24 $self->{tempdir} = $dir_name;
410 11         21 return $self->{tempdir};
411             }
412             sub timestamp {
413 0     0 1 0 my $t = time;
414 0         0 my $date = strftime "%Y-%m-%d %H:%M:%S", localtime $t;
415 0         0 $date .= sprintf ".%03d", ($t-int($t))*1000; # without rounding
416 0         0 return $date;
417             }
418             sub workdir {
419 1     1 1 79 my $self = shift;
420 1 50       4 return is_win()
421             ? "$ENV{USERPROFILE}/brewbuild"
422             : "$ENV{HOME}/brewbuild";
423             }
424             sub log {
425 0     0 1 0 my $self = shift;
426 0         0 $self->{log}->_6(ref($self) ." class/obj accessing the log object");
427 0         0 $self->{log};
428             }
429             sub revdeps {
430 0     0 1 0 my $self = shift;
431              
432 0         0 load 'MetaCPAN::Client';
433 0         0 my $mcpan = MetaCPAN::Client->new;
434              
435 0         0 my $log = $log->child('revdeps');
436 0         0 $log->_6('running --revdep');
437              
438 0         0 my $mod;
439              
440             find(
441             {
442             wanted => sub {
443 0 0   0   0 return if $mod;
444              
445 0 0 0     0 if (-f && $_ =~ /\.pm$/){
446              
447 0         0 $log->_6("processing module '$_'");
448              
449 0         0 s|lib/||;
450 0         0 s|/|-|g;
451 0         0 s|\.pm||;
452              
453 0         0 $log->_6("module file converted to '$_'");
454              
455 0         0 my $dist;
456              
457 0         0 eval {
458 0         0 $dist = $mcpan->distribution($_);
459             };
460 0 0       0 $mod = $_ if ref $dist;
461              
462             }
463             },
464 0         0 no_chdir => 1,
465             },
466             'lib/'
467             );
468              
469 0         0 $log->_7("using '$mod' as the project we're working on");
470              
471 0         0 my @revdeps = $self->_get_revdeps($mod);
472 0         0 return @revdeps;
473             }
474             sub legacy {
475 6     6 1 21 my ($self, $legacy) = @_;
476 6 100 100     37 if (! defined $legacy && defined $self->{args}{legacy}){
477 1         12 return $self->{args}{legacy};
478              
479             }
480 5 100       15 $self->{args}{legacy} = defined $legacy ? $legacy : 0;
481 5         31 return $self->{args}{legacy};
482             }
483             sub setup {
484 0     0 1 0 print "\n";
485 0         0 my @setup = ;
486 0         0 print $_ for @setup;
487 0         0 exit;
488             }
489             sub help {
490 0     0 1 0 print <
491              
492             Usage: brewbuild [OPTIONS]
493              
494             Local usage options:
495              
496             -o | --on Perl version number to run against (can be supplied multiple times). Can not be used on Windows
497             -R | --revdep Run tests, install, then run tests on all CPAN reverse dependency modules
498             -n | --new How many random versions of perl to install (-1 to install all)
499             -r | --remove Remove all installed perls (less the current one)
500             -i | --install Number portion of an available perl version according to "*brew available". Multiple versions can be sent in at once
501             -S | --save By default, we save only FAIL logs. This will also save the PASS logs
502             -N | --notest Do not run tests. Allows you to --remove and --install without testing
503             -X | --nocache By default, we cache the results of 'perlbrew available'. Disable with this flag.
504              
505             Network dispatching options:
506              
507             -D | --dispatch Dispatch a basic run to remote testers
508             -t | --tester Testers to dispatch to. Can be supplied multiple times. Format: "host[:port]"
509              
510             Help options:
511              
512             -s | --setup Display test platform setup instructions
513             -h | --help Print this help message
514              
515             Special options:
516              
517             -p | --plugin Module name of the exec command plugin to use
518             -a | --args List of args to pass into the plugin (one arg per loop)
519             -l | --legacy Operate on perls < 5.8.9. The default plugins won't work with this flag set if a lower version is installed
520             -T | --selftest Testing only: prevent recursive testing on Test::BrewBuild
521             -d | --debug 0-7, sets logging verbosity, default is 0
522              
523             EOF
524 0         0 return 1;
525             }
526             sub _config {
527             # slurp in config file elements
528              
529 11     11   22 my $self = shift;
530              
531 11         26 my $conf_file = $self->config_file;
532              
533 11 100       183 if (-f $conf_file){
534 2         15 my $conf = Config::Tiny->read($conf_file)->{brewbuild};
535 2         368 for (keys %$conf){
536 12         27 $self->{args}{$_} = $conf->{$_};
537             }
538             }
539             }
540             sub _attach_build_log {
541             # attach the cpanm logs to the PASS/FAIL logs
542              
543 0     0   0 my ($self, $bblog) = @_;
544              
545 0         0 my $bbfile;
546             {
547 0         0 local $/ = undef;
  0         0  
548 0 0       0 open my $bblog_fh, '<', $bblog or croak $!;
549 0         0 $bbfile = <$bblog_fh>;
550 0         0 close $bblog_fh;
551             }
552              
553 0 0       0 if ($bbfile =~ /${ re_brewbuild('check_failed') }/){
  0         0  
554 0         0 my $build_log = $1;
555 0 0       0 open my $bblog_wfh, '>>', $bblog or croak $!;
556 0         0 print $bblog_wfh "\n\nCPANM BUILD LOG\n";
557 0         0 print $bblog_wfh "===============\n";
558              
559 0 0       0 open my $build_log_fh, '<', $build_log or croak $!;
560              
561 0         0 while (<$build_log_fh>){
562 0         0 print $bblog_wfh $_;
563             }
564 0         0 close $bblog_wfh;
565             }
566             }
567             sub _copy_logs {
568             # copy the log files out of the temp dir
569              
570 0     0   0 my $self = shift;
571 0 0       0 dircopy $self->{tempdir}, "bblog" if $self->{tempdir};
572 0 0       0 unlink 'bblog/stderr.bblog' if -e 'bblog/stderr.bblog';
573             }
574             sub _create_log {
575             # set up the log object
576              
577 11     11   39 my ($self, $level) = @_;
578              
579 11 100       140 $self->{log} = Logging::Simple->new(
580             name => 'BrewBuild',
581             level => defined $level ? $level : 0,
582             );
583              
584 11         1609 $self->{log}->_7("in _create_log()");
585              
586 11 100       982 if ($self->{log}->level < 6){
587 10         255 $self->{log}->display(0);
588 10         268 $self->{log}->custom_display("-");
589 10 50       135 $self->{log}->_5("set log level to " . defined $level ? $level : 0);
590             }
591              
592 11         588 return $self->{log};
593             }
594             sub _exec {
595             # perform the actual *brew build commands (called by test())
596              
597 0     0   0 my $self = shift;
598              
599 0         0 my $log = $log->child('exec');
600              
601 0 0       0 if ($self->{args}{plugin_arg}) {
602             $log->_5( "" .
603             "fetching instructions from the plugin with arg " .
604             $self->{args}{plugin_arg}
605 0         0 );
606             }
607              
608             my @exec_cmd = $self->{exec_plugin}->(
609             __PACKAGE__,
610             $self->log,
611             $self->{args}{plugin_arg}
612 0         0 );
613              
614 0         0 chomp @exec_cmd;
615              
616 0         0 $log->_6("instructions to be executed:\n" . join "\n", @exec_cmd);
617              
618 0         0 my $brew = $bcmd->brew;
619              
620 0 0       0 if ($self->{args}{on}){
621 0         0 my $vers = join ',', @{ $self->{args}{on} };
  0         0  
622              
623 0         0 $log->_5("versions to run on: $vers");
624              
625 0         0 for my $run_on_version (@{ $self->{args}{on} }){
  0         0  
626 0         0 my $prefixed_version = $run_on_version;
627              
628 0 0       0 if ($run_on_version !~ /^perl-/){
629 0         0 $prefixed_version = "perl-$run_on_version";
630             }
631 0 0       0 if (! grep {$prefixed_version eq $_} $self->perls_installed){
  0         0  
632 0         0 croak "\nversion '$run_on_version' is invalid. Can't continue\n";
633             }
634             }
635              
636 0         0 my $wfh = File::Temp->new(UNLINK => 1);
637 0         0 my $fname = $wfh->filename;
638              
639 0         0 $log->_6("created temp file for storing output: $fname");
640              
641 0 0       0 open $wfh, '>', $fname or croak $!;
642 0         0 for (@exec_cmd){
643 0         0 s/\n//g;
644             }
645 0         0 my $cmd = join ' && ', @exec_cmd;
646 0         0 $cmd = "system(\"$cmd\")";
647 0         0 print $wfh $cmd;
648 0         0 close $wfh;
649              
650 0         0 $self->_dzil_shim($fname);
651 0         0 $log->_5("exec'ing: $brew exec --with $vers " . join ', ', @exec_cmd);
652              
653 0         0 my $ret
654             = `$brew exec --with $vers perl $fname 2>$self->{tempdir}/stderr.bblog`;
655              
656 0         0 $self->_dzil_unshim;
657              
658 0         0 return $ret;
659             }
660             else {
661              
662 0 0       0 if ($bcmd->is_win){
663              
664             # all of this because berrybrew doesn't get the path right
665             # when calling ``berrybrew exec perl ...''
666              
667 0         0 my %res_hash;
668              
669 0         0 $self->_dzil_shim;
670              
671 0         0 for (@exec_cmd){
672 0         0 $log->_5("exec'ing: $brew exec:\n". join ', ', @exec_cmd);
673 0         0 my $res = `$brew exec $_`;
674              
675 0         0 my @results = $res =~ /${ re_brewbuild('check_result') }/gsx;
  0         0  
676              
677 0         0 for (@results){
678 0 0       0 if ($_ =~ /${ re_brewbuild('extract_result') }/gsx){
  0         0  
679 0         0 push @{ $res_hash{$1} }, $2;
  0         0  
680             }
681             }
682             }
683              
684 0         0 $self->_dzil_unshim;
685              
686 0         0 my $result;
687              
688 0         0 for (keys %res_hash){
689 0         0 $result .= $_ . join '', @{ $res_hash{$_} };
  0         0  
690             }
691              
692 0         0 return $result;
693             }
694             else {
695 0         0 my $wfh = File::Temp->new(UNLINK => 1);
696 0         0 my $fname = $wfh->filename;
697              
698 0         0 $log->_6("created temp file for storing output: $fname");
699              
700 0 0       0 open $wfh, '>', $fname or croak $!;
701 0         0 for (@exec_cmd){
702 0         0 s/\n//g;
703             }
704 0         0 my $cmd = join ' && ', @exec_cmd;
705 0         0 $cmd = "system(\"$cmd\")";
706 0         0 print $wfh $cmd;
707 0         0 close $wfh;
708              
709 0         0 $self->_dzil_shim($fname);
710              
711 0         0 my $ret = `$brew exec perl $fname 2>$self->{tempdir}/stderr.bblog`;
712              
713 0         0 $self->_dzil_unshim;
714              
715 0         0 return $ret;
716             }
717             }
718             }
719             sub _dzil_shim {
720             # shim for working on Dist::Zilla modules
721              
722 0     0   0 my ($self, $cmd_file) = @_;
723              
724             # return early if possible
725              
726 0 0 0     0 return if -e 'Build.PL' || -e 'Makefile.PL';
727 0 0       0 return if ! -e 'dist.ini';
728              
729 0         0 my $log = $log->child('_dzil_shim');
730 0         0 $log->_5("dzil dist... loading the shim");
731              
732 0 0       0 my $path_sep = $self->is_win ? ';' : ':';
733              
734 0 0       0 if (! grep {-x "$_/dzil"} split /$path_sep/, $ENV{PATH} ){
  0         0  
735 0         0 $log->fatal(
736             "this appears to be a Dist::Zilla module, but the dzil binary " .
737             "can't be found\n"
738             );
739             }
740              
741 0         0 $self->{is_dzil} = 1;
742              
743 0 0       0 open my $fh, '<', 'dist.ini' or croak $!;
744              
745 0         0 my ($dist, $version);
746              
747 0         0 while (<$fh>){
748 0 0       0 if (/${ re_brewbuild('extract_dzil_dist_name') }/){
  0         0  
749 0         0 $dist = $1;
750             }
751 0 0       0 if (/${ re_brewbuild('extract_dzil_dist_version') }/){
  0         0  
752 0         0 $version = $1;
753             }
754 0 0 0     0 last if $dist && $version;
755             }
756              
757 0         0 $log->_7("running dzil commands: 'dzil authordeps --missing | cpanm', " .
758             "'dzil build'"
759             );
760              
761 0         0 `dzil authordeps --missing | cpanm`;
762 0         0 `dzil clean`;
763 0         0 `dzil build`;
764              
765 0         0 my $dir = "$dist-$version";
766 0 0       0 copy $cmd_file, $dir if defined $cmd_file;
767 0         0 chdir $dir;
768 0         0 $log->_7("entered $dir directory");
769             }
770             sub _dzil_unshim {
771             # unshim after doing dzil work
772              
773 0     0   0 my $self = shift;
774              
775 0         0 my $log = $log->child('_dzil_unshim');
776              
777 0 0       0 if (! $self->{is_dzil}){
778 0         0 $log->_7("not a dzil distribution; nothing to do");
779 0         0 return;
780             }
781              
782 0         0 $log->_5("removing dzil shim");
783              
784 0         0 $self->{is_dzil} = 0;
785 0         0 chdir '..';
786 0         0 $log->_7("changed to '..' dir");
787             }
788             sub _get_revdeps {
789 0     0   0 my ($self, $module) = @_;
790              
791 0         0 load 'MetaCPAN::Client';
792              
793 0         0 my $mcpan = MetaCPAN::Client->new;
794              
795 0         0 my $rs = $mcpan->reverse_dependencies($module);
796              
797 0         0 my @revdeps;
798              
799 0         0 while (my $release = $rs->next){
800 0         0 push @revdeps, $release->distribution;
801             }
802              
803 0         0 @revdeps = grep {$_ ne 'Test-BrewBuild'} @revdeps;
  0         0  
804              
805 0         0 for (@revdeps){
806 0         0 s/-/::/g;
807             }
808              
809 0         0 return @revdeps;
810             }
811             sub _process_stderr {
812             # compile data written to STDERR
813              
814 0     0   0 my $self = shift;
815            
816 0         0 my $errlog = "$self->{tempdir}/stderr.bblog";
817              
818 0 0       0 if (-e $errlog){
819 0 0       0 open my $errlog_fh, '<', $errlog or croak $!;
820            
821 0         0 my $error_contents;
822             {
823 0         0 local $/ = undef;
  0         0  
824 0         0 $error_contents = <$errlog_fh>;
825             }
826 0         0 close $errlog_fh;
827              
828 0         0 my @errors = $error_contents =~ /${ re_brewbuild('extract_errors') }/g;
  0         0  
829              
830 0         0 my %error_map;
831              
832 0         0 for (@errors){
833 0 0       0 if (/${ re_brewbuild('extract_error_perl_ver') }/){
  0         0  
834 0         0 $error_map{$1} = $_;
835             }
836             }
837            
838 0 0       0 if (! keys %error_map){
839 0         0 $error_map{0} = $error_contents;
840             }
841 0         0 return %error_map;
842             }
843             }
844             sub _save_reports {
845             # save FAIL and optionally PASS report logs
846              
847 0     0   0 my ($self, $ver, $status, $result) = @_;
848              
849 0 0 0     0 if ($status ne 'FAIL' && ! $self->{args}{save_reports}){
850 0         0 return;
851             }
852              
853 0         0 my $tested_mod = $self->{args}{plugin_arg};
854              
855 0 0       0 if (defined $tested_mod){
856 0         0 $tested_mod =~ s/::/-/g;
857 0         0 my $report = "$self->{tempdir}/$tested_mod-$ver-$status.bblog";
858 0 0       0 open my $wfh, '>', $report, or croak $!;
859              
860 0         0 print $wfh $result;
861              
862 0 0       0 if (! $self->is_win){
863 0         0 my %errors = $self->_process_stderr;
864              
865 0 0       0 if (defined $errors{0}){
866 0         0 print $wfh "\nCPANM ERROR LOG\n";
867 0         0 print $wfh "===============\n";
868 0         0 print $wfh $errors{0};
869             }
870             else {
871 0         0 for (keys %errors){
872 0 0       0 if (version->parse($_) == version->parse($ver)){
873 0         0 print $wfh "\nCPANM ERROR LOG\n";
874 0         0 print $wfh "===============\n";
875 0         0 print $wfh $errors{$_};
876             }
877             }
878             }
879             }
880 0         0 close $wfh;
881 0         0 $self->_attach_build_log($report);
882             }
883             else {
884 0         0 my $report = "$self->{tempdir}/$ver-$status.bblog";
885 0 0       0 open my $wfh, '>', $report or croak $!;
886 0         0 print $wfh $result;
887              
888 0 0       0 if (! $self->is_win){
889 0         0 my %errors = $self->_process_stderr;
890 0         0 for (keys %errors){
891 0 0       0 if (version->parse($_) == version->parse($ver)){
892 0         0 print $wfh "\nCPANM ERROR LOG\n";
893 0         0 print $wfh "===============\n";
894 0         0 print $wfh $errors{$_};
895             }
896             }
897             }
898 0         0 close $wfh;
899 0 0       0 $self->_attach_build_log($report) if ! $self->is_win;
900             }
901             }
902             sub _set_plugin {
903             # import the exec plugin
904              
905 9     9   16 my $self = shift;
906 9         25 my $log = $log->child('_set_plugin');
907             my $plugin = $self->{args}{plugin}
908             ? $self->{args}{plugin}
909 9 50       194 : $ENV{TBB_PLUGIN};
910              
911 9 50       59 $log->_5("plugin param set to: " . defined $plugin ? $plugin : 'default');
912              
913 9         686 $plugin = $self->plugins($plugin, can => ['brewbuild_exec']);
914              
915 9         563 my $exec_plugin_sub = $plugin .'::brewbuild_exec';
916 9         36 $self->{exec_plugin} = \&$exec_plugin_sub;
917              
918 9         48 $log->_4("successfully loaded $plugin plugin");
919             }
920             sub _validate_opts {
921             # validate command line arguments
922              
923 0     0     my $args = shift;
924              
925 0           my @valid_args = qw(
926             on o new n remove r revdep R plugin p args a debug d install i help h
927             N notest setup s legacy l selftest T t testers S save D dispatch X
928             nocache
929             );
930              
931 0           my $bad_opt = 0;
932 0           my $i;
933 0 0         if (@$args) {
934 0           my @params = grep {++$i % 2 != 0} @$args;
  0            
935 0           for my $arg (@params) {
936 0           $arg =~ s/^-{1,2}//g;
937 0 0         if (!grep { $arg eq $_ } @valid_args) {
  0            
938 0           $bad_opt = 1;
939 0           last;
940             }
941             }
942             }
943 0           return $bad_opt;
944             }
945              
946             1;
947              
948             =head1 NAME
949              
950             Test::BrewBuild - Perl/Berry brew unit testing automation, with remote tester
951             dispatching capabilities.
952              
953             =head1 DESCRIPTION
954              
955             This module is the backend for the C script that is accompanied by
956             this module.
957              
958             For end-user use, see
959             L.
960             You can also read the documentation for the network dispatcher
961             L,
962             the remote test listener
963             L,
964             or browse through the L for network testing.
965              
966             This module provides you the ability to perform your unit tests across all of
967             your L (Unix) or L
968             (Windows) Perl instances.
969              
970             For Windows, you'll need to install B>,
971             and for Unix, you'll need B>.
972              
973             It allows you to remove and reinstall on each test run, install random versions
974             of perl, or install specific versions.
975              
976             All unit tests are run against all installed instances, unless specified
977             otherwise.
978              
979             =head1 SYNOPSIS
980              
981             use Test::BrewBuild;
982              
983             my $bb = Test::BrewBuild->new;
984              
985             my @perls_available = $bb->perls_available;
986             my @perls_installed = $bb->perls_installed;
987              
988             # remove all currently installed instances of perl, less the one you're
989             # using
990              
991             $bb->instance_remove;
992              
993             # install four new random versions of perl
994              
995             $bb->instance_install(4);
996              
997             # install two specific versions
998              
999             $bb->instance_install(['5.10.1', '5.20.3']);
1000              
1001             # install all instances
1002              
1003             $bb->instance_install(-1);
1004              
1005             # find and test against all the current module's reverse CPAN dependencies
1006              
1007             $bb->revdep;
1008              
1009             # run the unit tests of the current module only
1010              
1011             $bb->test;
1012              
1013             =head1 METHODS
1014              
1015              
1016              
1017             =head2 new(%args)
1018              
1019             Returns a new C object. See the documentation for the
1020             L
1021             script to understand what the arguments are and do.
1022              
1023             Many of the options can be saved in a configuration file if you want to set them
1024             permanently, or override defaults. Options passed into the various methods will
1025             override those in the configuration file.
1026             See L.
1027              
1028             =head2 brew_info
1029              
1030             Returns in string form the full output of C<*brew available>.
1031              
1032             =head2 perls_available
1033              
1034             Returns an array containing all perls available, whether already installed or
1035             not.
1036              
1037             =head2 perls_installed
1038              
1039             Returns an array of the names of all perls currently installed under your
1040             C<*brew> setup.
1041              
1042             =head2 instance_install
1043              
1044             If an integer is sent in, we'll install that many random versions of perl. If
1045             the integer is C<-1>, we'll install all available versions. You can also send in
1046             an array reference, where each element is a version of perl, and we'll install
1047             those instead.
1048              
1049             You can send a second parameter, an integer for a time out. On each install,
1050             we'll bail if it takes longer than this time. Default is 300 seconds. If you're
1051             on a fast machine, you should probably lower this value.
1052              
1053             On Windows, where you want to install specific perls, we'll default to
1054             installing 64-bit versions only, if a 64 bit perl is available for the version
1055             desired and you haven't added the C<_64/_32> suffix per C.
1056              
1057             Simply add the C<_32> suffix if you want to install it specifically. Note that
1058             if you're dispatching to Unix and Windows servers, the Unix systems will remove
1059             this invalid portion of the version prior to processing further.
1060              
1061             =head2 instance_remove
1062              
1063             Uninstalls all currently installed perls, less the one you are currently
1064             Ced or Cd to.
1065              
1066             =head2 test
1067              
1068             Processes and returns the test results as a string scalar of the distribution
1069             located in the current working directory.
1070              
1071             =head2 revdeps
1072              
1073             Returns a list of the reverse dependencies (according to CPAN) that the module
1074             you're working on in the current working directory have.
1075              
1076             =head2 revdep
1077              
1078             Loops over all of the current module's reverse dependencies, and executes
1079             C on each one at a time. This helps you confirm whether your new build
1080             won't break your downstream users' modules.
1081              
1082             =head2 legacy
1083              
1084             By default, we don't install perl versions less than v5.8.9. Pass in a true
1085             value to override this default.
1086              
1087             =head2 options(\%args)
1088              
1089             Takes a hash reference of the command-line argument list, and converts it into
1090             a hash of the translated C parameters along with their values.
1091              
1092             Returns the converted hash for passing back into C.
1093              
1094             If an invalid argument is included, we'll set C<$args{error} = 1;>. It is up to
1095             the caller to look for and process an error handling routine.
1096              
1097             =head2 config_file
1098              
1099             Returns a string that contains the path/filename of the configuration file, if
1100             available.
1101              
1102             =head2 plugin('Module::Name')
1103              
1104             Fetches and installs a custom plugin which contains the code that
1105             C will execute. If not used or the module specified
1106             can't be located (or it contains errors), we fall back to the default bundled
1107             L (which is the canonical example for
1108             writing new plugins).
1109              
1110             Note that you can send in a custom plugin C<*.pm> filename to plugin as opposed
1111             to a module name if the module isn't installed. If the file isn't in the
1112             current working directory, send in the relative or full path.
1113              
1114             =head2 is_win
1115              
1116             Helper method, returns true if the current OS is Windows, false if not.
1117              
1118             =head2 log
1119              
1120             Returns an instance of the packages log object for creating child log objects.
1121              
1122             =head2 tempdir
1123              
1124             Sets up the object with a temporary directory used for test logs, that will be
1125             removed after the run.
1126              
1127             =head2 timestamp
1128              
1129             Returns a date/time string for timestamping items. Format:
1130              
1131             YYYY-MM-DD HH:MM:SS.xxx
1132              
1133             =head2 workdir
1134              
1135             Returns the brewbuild working directory.
1136              
1137             =head2 setup
1138              
1139             Prints out detailed information on setting up a testing environment, on Windows
1140             and Unix.
1141              
1142             =head2 help
1143              
1144             Displays the C command line usage information.
1145              
1146             =head1 TROUBLESHOOTING
1147              
1148             =head2 Installation Issues
1149              
1150             On some Linux variants, not all of the software required for SSL is installed.
1151              
1152             If you have install failures (reading the `cpanm` build log often complains
1153             about L failing), try running the following command line
1154             commands, then re-run C:
1155              
1156             sudo apt-get install libssl-dev
1157             sudo apt-get install libz-dev
1158              
1159             =head1 AUTHOR
1160              
1161             Steve Bertrand, C<< >>
1162              
1163             =head1 SEE ALSO
1164              
1165             Berrybrew for Windows:
1166              
1167             L
1168              
1169             Perlbrew for Unixes:
1170              
1171             L
1172              
1173             =head1 LICENSE AND COPYRIGHT
1174              
1175             Copyright 2017 Steve Bertrand.
1176              
1177             This program is free software; you can redistribute it and/or modify it
1178             under the terms of either: the GNU General Public License as published
1179             by the Free Software Foundation; or the Artistic License.
1180              
1181             See L for more information.
1182              
1183             =cut
1184              
1185             1;
1186              
1187             __DATA__