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
513 --nocolor don't use colored output
517 The F<soak> utility can be used to test Perl modules with
518 multiple Perl releases or build options. It automates the
519 task of running F<Makefile.PL> and the modules test suite.
521 It is not primarily intended for cross-platform checking,
522 so don't expect it to work on all platforms.
526 To test your favourite module, just change to its root
527 directory (where the F<Makefile.PL> is located) and run:
531 This will automatically look for Perl binaries installed
534 Alternatively, you can explicitly pass F<soak> a list of
537 soak perl5.8.6 perl5.9.2
539 Last but not least, you can pass it a list of directories
540 to recursively search for Perl binaries, for example:
542 soak /tmp/perl/install /usr/bin
544 All of the above examples will run
550 for your module and report success or failure.
552 If your F<Makefile.PL> can take arguments, you may also
553 want to test different configurations for your module.
554 You can do so with the I<--mmargs> option:
556 soak --mmargs=' ' --mmargs='CCFLAGS=-Wextra' --mmargs='enable-debug'
563 perl Makefile.PL CCFLAGS=-Wextra
566 perl Makefile.PL enable-debug
570 for each Perl binary.
572 If you have a directory full of different Perl binaries,
573 but your module isn't expected to work with ancient perls,
574 you can use the I<--min> option to specify the minimum
575 version a Perl binary must have to be chosen for testing:
579 Usually, the output of F<soak> is rather terse, to give
580 you a good overview. If you'd like to see more of what's
581 going on, use the I<--verbose> option:
587 Version 3.x, Copyright (c) 2004-2013, Marcus Holland-Moritz.
589 Version 2.x, Copyright (C) 2001, Paul Marquess.
591 Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
593 This program is free software; you can redistribute it and/or
594 modify it under the same terms as Perl itself.
598 See L<Devel::PPPort>.