2 ################################################################################
4 # soak -- Test Perl modules with multiple Perl releases.
6 # Original Author: Paul Marquess
8 ################################################################################
10 # Version 3.x, Copyright (C) 2004-2013, Marcus Holland-Moritz.
11 # Version 2.x, Copyright (C) 2001, Paul Marquess.
12 # Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
14 # This program is free software; you can redistribute it and/or
15 # modify it under the same terms as Perl itself.
17 ################################################################################
23 use ExtUtils::MakeMaker;
27 use List::Util qw(max);
35 make => $Config{make} || 'make',
40 GetOptions(\%OPT, qw(verbose make=s min=s mmargs=s@ color!)) or pod2usage(2);
42 $OPT{mmargs} = [''] unless exists $OPT{mmargs};
43 $OPT{min} = parse_version($OPT{min}) - 1e-10;
45 sub cs($;$$) { my $x = shift; my($s, $p) = @_ ? @_ : ('', 's'); ($x, $x == 1 ? $s : $p) }
47 my @GoodPerls = map { $_->[0] }
48 sort { $a->[1] <=> $b->[1] or $a->[0] cmp $b->[0] }
49 grep { $_->[1] >= $OPT{min} }
50 map { [$_ => perl_version($_)] }
51 @ARGV ? SearchPerls(@ARGV) : FindPerls();
54 print "Sorry, got no Perl binaries for testing.\n\n";
58 my $maxlen = max(map length, @GoodPerls) + 3;
59 my $mmalen = max(map length, @{$OPT{mmargs}});
60 $maxlen += $mmalen+3 if $mmalen > 0;
62 my $rep = Soak::Reporter->new( verbose => $OPT{verbose}
63 , color => $OPT{color}
67 $SIG{__WARN__} = sub { $rep->warn(@_) };
68 $SIG{__DIE__} = sub { $rep->die(@_) };
70 # prime the pump, so the first "make realclean" will work.
71 runit("$^X Makefile.PL") && runit("$OPT{make} realclean")
72 or $rep->die("Cannot run $^X Makefile.PL && $OPT{make} realclean\n");
74 my $tot = @GoodPerls*@{$OPT{mmargs}};
76 $rep->set(tests => $tot);
78 $rep->status(sprintf("Testing %d version%s / %d configuration%s (%d combination%s)...\n",
79 cs(@GoodPerls), cs(@{$OPT{mmargs}}), cs($tot)));
81 for my $perl (@GoodPerls) {
82 for my $mm (@{$OPT{mmargs}}) {
83 $rep->set(perl => $perl, config => $mm);
91 my $ok = runit("$perl Makefile.PL $mm", \@warn_mfpl) &&
92 runit("$OPT{make}", \@warn_make) &&
93 runit("$OPT{make} test", \@warn_test);
95 $rep->warnings(['Makefile.PL' => \@warn_mfpl],
96 ['make' => \@warn_make],
97 ['make test' => \@warn_test]);
106 runit("$OPT{make} realclean");
114 # TODO -- portability alert!!
116 my($cmd, $warn) = @_;
117 $rep->vsay("\n Running [$cmd]");
118 my $output = `$cmd 2>&1`;
119 $output = "\n" unless defined $output;
120 $output =~ s/^/ > /gm;
121 $rep->say("\n Output:\n$output") if $OPT{verbose} || $?;
123 $rep->warn(" Running '$cmd' failed: $?\n");
126 push @$warn, $output =~ /(warning: .*)/ig;
132 # TODO -- need to decide how far back we go.
133 # TODO -- get list of user releases prior to 5.004
134 # TODO -- does not work on Windows (at least)
136 # find versions of Perl that are available
137 my @PerlBinaries = qw(
142 5.004 5.00401 5.00402 5.00403 5.00404 5.00405
143 5.005 5.00501 5.00502 5.00503 5.00504
145 5.7.0 5.7.1 5.7.2 5.7.3
146 5.8.0 5.8.1 5.8.2 5.8.3 5.8.4 5.8.5 5.8.6 5.8.7 5.8.8
147 5.9.0 5.9.1 5.9.2 5.9.3
150 print "Searching for Perl binaries...\n";
152 # find_perl will send a warning to STDOUT if it can't find
153 # the requested perl, so need to temporarily silence STDOUT.
154 tie *STDOUT, 'NoSTDOUT';
156 my $mm = MM->new( { NAME => 'dummy' });
157 my @path = $mm->path;
160 for my $perl (@PerlBinaries) {
161 if (my $abs = $mm->find_perl($perl, ["perl$perl"], \@path, 0)) {
162 push @GoodPerls, $abs;
168 print "\nFound:\n", (map " $_\n", @GoodPerls), "\n";
178 for my $arg (@args) {
181 print "Searching for Perl binaries in '$arg'...\n";
182 find({ wanted => sub {
183 $File::Find::name =~ m!perl5[\w._]+$!
184 and -f $File::Find::name
185 and -x $File::Find::name
186 and perl_version($File::Find::name)
187 and push @found, $File::Find::name;
188 }, follow => 1 }, $arg);
189 printf "Found %d Perl binar%s in '%s'.\n\n", cs(@found, 'y', 'ies'), $arg;
203 my $ver = `$perl -e 'print \$]' 2>&1`;
204 return $? == 0 && $ver =~ /^\d+\.\d+/ && $ver >= 5 ? $ver : 0;
211 if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) {
212 return $1 + 1e-3*$2 + 1e-6*$3;
214 elsif ($ver =~ /^\d+\.[\d_]+$/) {
219 die "cannot parse version '$ver'\n";
225 our @ISA = qw(Tie::Handle);
227 sub TIEHANDLE { bless \(my $s = ''), shift }
231 package Soak::Reporter;
235 sub cs($;$$) { my $x = shift; my($s, $p) = @_ ? @_ : ('', 's'); ($x, $x == 1 ? $s : $p) }
257 if ($self->{color}) {
259 require Term::ANSIColor;
260 Term::ANSIColor::colored(@_);
277 return $self->{config} =~ /\S+/ ? " ($self->{config})" : '';
283 return '' unless defined $self->{tests};
284 my $tlen = length $self->{tests};
285 my $text = sprintf "[%${tlen}d/%${tlen}d] ", $self->{_cur}, $self->{tests};
286 return $self->colored($text, 'bold');
292 return $self->_progress . "Testing "
293 . $self->colored($self->{perl}, 'blue')
294 . $self->colored($self->_config, 'green');
300 return length("Testing " . $self->{perl} . $self->_config);
306 return '.' x $self->_dotslen;
312 return $self->{width} - length($self->{perl} . $self->_config);
319 $self->print($self->colored('-'x$width, 'bold'), "\n");
324 goto &_sep if $_[0]->{verbose};
331 my($k, $v) = splice @_, 0, 2;
340 $self->_vsep($self->_testlen);
341 $self->print($self->_test, $self->{verbose} ? "\n" : ' ' . $self->_dots . ' ');
342 $self->_vsep($self->_testlen);
347 my($self, $mode) = @_;
352 for my $w (@{$self->{_warnings}}) {
354 $warnings += @{$w->[1]};
362 if ($mode eq 'summary') {
363 $rv .= sprintf " (%d warning%s", cs($warnings);
369 for my $w (@{$self->{_warnings}}) {
371 if ($mode eq 'detail') {
372 $rv .= " Warnings during '$w->[0]':\n";
374 for my $msg (@{$w->[1]}) {
375 $rv .= sprintf " [%d] %s", $cnt++, $msg;
380 unless ($self->{verbose}) {
381 $rv .= $differ == 1 ? " during " . $w->[0]
382 : sprintf(", %d during %s", scalar @{$w->[1]}, $w->[0]);
388 if ($mode eq 'summary') {
398 my($self, $text, $color) = @_;
399 my $sum = $self->_warnings('summary');
400 my $len = $self->_testlen + $self->_dotslen + length($text) + length($sum) + 2;
403 $self->print($self->_test, ' ', $self->_dots, ' ') if $self->{verbose} || $self->{_atbol};
404 $self->print($self->colored($text, $color));
405 $self->print($self->colored($sum, 'red'));
408 $self->print($self->_warnings('detail')) if $self->{verbose};
415 $self->_result(@_, 'ok', 'bold green');
416 push @{$self->{_good}}, [$self->{perl}, $self->{config}];
422 $self->_result(@_, 'not ok', 'bold red');
423 push @{$self->{_bad}}, [$self->{perl}, $self->{config}];
429 $self->{_warnings} = \@_;
435 print "\n" unless $self->{_atbol};
442 my $text = join '', @_;
444 $self->{_atbol} = $text =~ /[\r\n]$/;
451 $self->print(@_, "\n");
456 goto &say if $_[0]->{verbose};
462 $self->say($self->colored(join('', @_), 'red'));
468 $self->say($self->colored(join('', 'FATAL: ', @_), 'bold red'));
474 my($self, $text) = @_;
476 $self->print($self->colored($text, 'bold'), "\n");
483 if (@{$self->{_bad}}) {
484 $self->status("\nFailed with:");
485 for my $fail (@{$self->{_bad}}) {
486 my($perl, $cfg) = @$fail;
487 $self->set(config => $cfg);
488 $self->say(" ", $self->colored($perl, 'blue'), $self->colored($self->_config, 'green'));
492 $self->status(sprintf("\nPassed with %d of %d combination%s.\n",
493 scalar @{$self->{_good}}, cs($self->{_total})));
495 return scalar @{$self->{_bad}};
502 soak - Test Perl modules with multiple Perl releases
506 soak [options] [perl ...]
508 --make=program override name of make program ($Config{make})
509 --min=version use at least this version of perl
510 --mmargs=options pass options to Makefile.PL (multiple --mmargs possible)
512 --nocolor don't use colored output
516 The F<soak> utility can be used to test Perl modules with
517 multiple Perl releases or build options. It automates the
518 task of running F<Makefile.PL> and the modules test suite.
520 It is not primarily intended for cross-platform checking,
521 so don't expect it to work on all platforms.
525 To test your favourite module, just change to its root
526 directory (where the F<Makefile.PL> is located) and run:
530 This will automatically look for Perl binaries installed
533 Alternatively, you can explicitly pass F<soak> a list of
536 soak perl5.8.6 perl5.9.2
538 Last but not least, you can pass it a list of directories
539 to recursively search for Perl binaries, for example:
541 soak /tmp/perl/install /usr/bin
543 All of the above examples will run
549 for your module and report success or failure.
551 If your F<Makefile.PL> can take arguments, you may also
552 want to test different configurations for your module.
553 You can do so with the I<--mmargs> option:
555 soak --mmargs=' ' --mmargs='CCFLAGS=-Wextra' --mmargs='enable-debug'
562 perl Makefile.PL CCFLAGS=-Wextra
565 perl Makefile.PL enable-debug
569 for each Perl binary.
571 If you have a directory full of different Perl binaries,
572 but your module isn't expected to work with ancient perls,
573 you can use the I<--min> option to specify the minimum
574 version a Perl binary must have to be chosen for testing:
578 Usually, the output of F<soak> is rather terse, to give
579 you a good overview. If you'd like to see more of what's
580 going on, use the I<--verbose> option:
586 Version 3.x, Copyright (c) 2004-2013, Marcus Holland-Moritz.
588 Version 2.x, Copyright (C) 2001, Paul Marquess.
590 Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
592 This program is free software; you can redistribute it and/or
593 modify it under the same terms as Perl itself.
597 See L<Devel::PPPort>.