Upgrade to Devel::PPPort 3.09_02
[p5sagit/p5-mst-13.2.git] / ext / Devel / PPPort / soak
CommitLineData
adfe19db 1#!/usr/bin/perl -w
2################################################################################
3#
4a582685 4# soak -- Test Perl modules with multiple Perl releases.
adfe19db 5#
6# Original Author: Paul Marquess
7#
8################################################################################
9#
56093a11 10# $Revision: 13 $
adfe19db 11# $Author: mhx $
56093a11 12# $Date: 2006/07/08 16:58:56 +0200 $
44284200 13#
adfe19db 14################################################################################
44284200 15#
0d0f8426 16# Version 3.x, Copyright (C) 2004-2006, Marcus Holland-Moritz.
adfe19db 17# Version 2.x, Copyright (C) 2001, Paul Marquess.
18# Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
19#
20# This program is free software; you can redistribute it and/or
21# modify it under the same terms as Perl itself.
22#
23################################################################################
44284200 24
25require 5.006001;
dbda3434 26
adfe19db 27use strict;
28use warnings;
dbda3434 29use ExtUtils::MakeMaker;
44284200 30use Getopt::Long;
4a582685 31use Pod::Usage;
c07deaaf 32use File::Find;
4a582685 33use List::Util qw(max);
34use Config;
44284200 35
aef0a14c 36my $VERSION = do { my @r = '$Snapshot: /Devel-PPPort/3.09_02 $' =~ /(\d+\.\d+(?:_\d+)?)/; @r ? $r[0] : '9.99' };
0a7c7f4f 37
4a582685 38$| = 1;
4a582685 39my %OPT = (
40 verbose => 0,
41 make => $Config{make} || 'make',
c07deaaf 42 min => '5.000',
56093a11 43 color => 1,
4a582685 44);
0a7c7f4f 45
56093a11 46GetOptions(\%OPT, qw(verbose make=s min=s mmargs=s@ color!)) or pod2usage(2);
dbda3434 47
4a582685 48$OPT{mmargs} = [''] unless exists $OPT{mmargs};
c07deaaf 49$OPT{min} = parse_version($OPT{min}) - 1e-10;
dbda3434 50
56093a11 51sub cs($;$$) { my $x = shift; my($s, $p) = @_ ? @_ : ('', 's'); ($x, $x == 1 ? $s : $p) }
52
0c96388f 53my @GoodPerls = map { $_->[0] }
54 sort { $a->[1] <=> $b->[1] or $a->[0] cmp $b->[0] }
55 grep { $_->[1] >= $OPT{min} }
56 map { [$_ => perl_version($_)] }
c07deaaf 57 @ARGV ? SearchPerls(@ARGV) : FindPerls();
0c96388f 58
59unless (@GoodPerls) {
60 print "Sorry, got no Perl binaries for testing.\n\n";
61 exit 0;
62}
63
4a582685 64my $maxlen = max(map length, @GoodPerls) + 3;
65my $mmalen = max(map length, @{$OPT{mmargs}});
66$maxlen += $mmalen+3 if $mmalen > 0;
0a7c7f4f 67
56093a11 68my $rep = Soak::Reporter->new( verbose => $OPT{verbose}
69 , color => $OPT{color}
70 , width => $maxlen
71 );
72
73$SIG{__WARN__} = sub { $rep->warn(@_) };
74$SIG{__DIE__} = sub { $rep->die(@_) };
0a7c7f4f 75
adfe19db 76# prime the pump, so the first "make realclean" will work.
56093a11 77runit("$^X Makefile.PL") && runit("$OPT{make} realclean")
78 or $rep->die("Cannot run $^X Makefile.PL && $OPT{make} realclean\n");
dbda3434 79
56093a11 80$rep->status(sprintf("Testing %d version%s / %d configuration%s (%d combination%s)...\n",
81 cs(@GoodPerls), cs(@{$OPT{mmargs}}), cs(@GoodPerls*@{$OPT{mmargs}})));
c07deaaf 82
4a582685 83for my $perl (@GoodPerls) {
84 for my $mm (@{$OPT{mmargs}}) {
56093a11 85 $rep->set(perl => $perl, config => $mm);
86
87 $rep->test;
88
89 my @warn_mfpl;
90 my @warn_make;
91 my @warn_test;
92
93 my $ok = runit("$perl Makefile.PL $mm", \@warn_mfpl) &&
94 runit("$OPT{make}", \@warn_make) &&
95 runit("$OPT{make} test", \@warn_test);
dbda3434 96
56093a11 97 $rep->warnings(['Makefile.PL' => \@warn_mfpl],
98 ['make' => \@warn_make],
99 ['make test' => \@warn_test]);
0a7c7f4f 100
0a7c7f4f 101 if ($ok) {
56093a11 102 $rep->passed;
0a7c7f4f 103 }
104 else {
56093a11 105 $rep->failed;
0a7c7f4f 106 }
107
56093a11 108 runit("$OPT{make} realclean");
4a582685 109 }
0a7c7f4f 110}
111
56093a11 112exit $rep->finish;
0a7c7f4f 113
114sub runit
115{
4a582685 116 # TODO -- portability alert!!
117
56093a11 118 my($cmd, $warn) = @_;
119 $rep->vsay("\n Running [$cmd]");
4a582685 120 my $output = `$cmd 2>&1`;
121 $output = "\n" unless defined $output;
56093a11 122 $output =~ s/^/ > /gm;
123 $rep->say("\n Output:\n$output") if $OPT{verbose} || $?;
4a582685 124 if ($?) {
56093a11 125 $rep->warn(" Running '$cmd' failed: $?\n");
4a582685 126 return 0;
127 }
56093a11 128 push @$warn, $output =~ /(warning: .*)/ig;
4a582685 129 return 1;
44284200 130}
131
132sub FindPerls
133{
4a582685 134 # TODO -- need to decide how far back we go.
135 # TODO -- get list of user releases prior to 5.004
136 # TODO -- does not work on Windows (at least)
137
138 # find versions of Perl that are available
139 my @PerlBinaries = qw(
140 5.000
141 5.001
142 5.002
143 5.003
144 5.004 5.00401 5.00402 5.00403 5.00404 5.00405
145 5.005 5.00501 5.00502 5.00503 5.00504
146 5.6.0 5.6.1 5.6.2
147 5.7.0 5.7.1 5.7.2 5.7.3
56093a11 148 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
149 5.9.0 5.9.1 5.9.2 5.9.3
4a582685 150 );
151
152 print "Searching for Perl binaries...\n";
4a582685 153
154 # find_perl will send a warning to STDOUT if it can't find
155 # the requested perl, so need to temporarily silence STDOUT.
156 tie *STDOUT, 'NoSTDOUT';
157
56093a11 158 my $mm = MM->new( { NAME => 'dummy' });
159 my @path = $mm->path;
160 my @GoodPerls;
161
4a582685 162 for my $perl (@PerlBinaries) {
163 if (my $abs = $mm->find_perl($perl, ["perl$perl"], \@path, 0)) {
164 push @GoodPerls, $abs;
44284200 165 }
4a582685 166 }
167
168 untie *STDOUT;
44284200 169
4a582685 170 print "\nFound:\n", (map " $_\n", @GoodPerls), "\n";
171
172 return @GoodPerls;
44284200 173}
174
c07deaaf 175sub SearchPerls
176{
177 my @args = @_;
178 my @perls;
179
180 for my $arg (@args) {
181 if (-d $arg) {
182 my @found;
183 print "Searching for Perl binaries in '$arg'...\n";
184 find(sub {
0c96388f 185 $File::Find::name =~ m!perl5[\w._]+$!
186 and -f $File::Find::name
187 and -x $File::Find::name
188 and perl_version($File::Find::name)
189 and push @found, $File::Find::name;
c07deaaf 190 }, $arg);
56093a11 191 printf "Found %d Perl binar%s in '%s'.\n\n", cs(@found, 'y', 'ies'), $arg;
c07deaaf 192 push @perls, @found;
193 }
194 else {
195 push @perls, $arg;
196 }
197 }
198
199 return @perls;
200}
201
0c96388f 202sub perl_version
203{
204 my $perl = shift;
205 my $ver = `$perl -e 'print \$]' 2>&1`;
206 return $? == 0 && $ver >= 5 ? $ver : 0;
207}
208
c07deaaf 209sub parse_version
210{
211 my $ver = shift;
212
c07deaaf 213 if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) {
214 return $1 + 1e-3*$2 + 1e-6*$3;
215 }
216 elsif ($ver =~ /^\d+\.[\d_]+$/) {
217 $ver =~ s/_//g;
218 return $ver;
219 }
220
221 die "cannot parse version '$ver'\n";
222}
223
44284200 224package NoSTDOUT;
225
226use Tie::Handle;
227our @ISA = qw(Tie::Handle);
228
4a582685 229sub TIEHANDLE { bless \(my $s = ''), shift }
230sub PRINT {}
231sub WRITE {}
232
56093a11 233package Soak::Reporter;
234
235use strict;
236
237sub cs($;$$) { my $x = shift; my($s, $p) = @_ ? @_ : ('', 's'); ($x, $x == 1 ? $s : $p) }
238
239sub new
240{
241 my $class = shift;
242 bless {
243 color => 1,
244 verbose => 0,
245 @_,
246 _atbol => 1,
247 _total => 0,
248 _good => [],
249 _bad => [],
250 }, $class;
251}
252
253sub colored
254{
255 my $self = shift;
256
257 if ($self->{color}) {
258 my $c = eval {
259 require Term::ANSIColor;
260 Term::ANSIColor::colored(@_);
261 };
262
263 if ($@) {
264 $self->{color} = 0;
265 }
266 else {
267 return $c;
268 }
269 }
270
271 return $_[0];
272}
273
274sub _config
275{
276 my $self = shift;
277 return $self->{config} =~ /\S+/ ? " ($self->{config})" : '';
278}
279
280sub _test
281{
282 my $self = shift;
283 return "Testing "
284 . $self->colored($self->{perl}, 'blue')
285 . $self->colored($self->_config, 'green');
286}
287
288sub _testlen
289{
290 my $self = shift;
291 return length("Testing " . $self->{perl} . $self->_config);
292}
293
294sub _dots
295{
296 my $self = shift;
297 return '.' x $self->_dotslen;
298}
299
300sub _dotslen
301{
302 my $self = shift;
303 return $self->{width} - length($self->{perl} . $self->_config);
304}
305
306sub _sep
307{
308 my $self = shift;
309 my $width = shift;
310 $self->print($self->colored('-'x$width, 'bold'), "\n");
311}
312
313sub _vsep
314{
315 goto &_sep if $_[0]->{verbose};
316}
317
318sub set
319{
320 my $self = shift;
321 while (@_) {
322 my($k, $v) = splice @_, 0, 2;
323 $self->{$k} = $v;
324 }
325}
326
327sub test
328{
329 my $self = shift;
330 $self->_vsep($self->_testlen);
331 $self->print($self->_test, $self->{verbose} ? "\n" : ' ' . $self->_dots . ' ');
332 $self->_vsep($self->_testlen);
333}
334
335sub _warnings
336{
337 my($self, $mode) = @_;
338
339 my $warnings = 0;
340 my $differ = 0;
341
342 for my $w (@{$self->{_warnings}}) {
343 if (@{$w->[1]}) {
344 $warnings += @{$w->[1]};
345 $differ++;
346 }
347 }
348
349 my $rv = '';
350
351 if ($warnings) {
352 if ($mode eq 'summary') {
353 $rv .= sprintf " (%d warning%s", cs($warnings);
354 }
355 else {
356 $rv .= "\n";
357 }
358
359 for my $w (@{$self->{_warnings}}) {
360 if (@{$w->[1]}) {
361 if ($mode eq 'detail') {
362 $rv .= " Warnings during '$w->[0]':\n";
363 my $cnt = 1;
364 for my $msg (@{$w->[1]}) {
365 $rv .= sprintf " [%d] %s", $cnt++, $msg;
366 }
367 $rv .= "\n";
368 }
369 else {
370 unless ($self->{verbose}) {
371 $rv .= $differ == 1 ? " during " . $w->[0]
372 : sprintf(", %d during %s", scalar @{$w->[1]}, $w->[0]);
373 }
374 }
375 }
376 }
377
378 if ($mode eq 'summary') {
379 $rv .= ')';
380 }
381 }
382
383 return $rv;
384}
385
386sub _result
387{
388 my($self, $text, $color) = @_;
389 my $sum = $self->_warnings('summary');
390 my $len = $self->_testlen + $self->_dotslen + length($text) + length($sum) + 2;
391
392 $self->_vsep($len);
393 $self->print($self->_test, ' ', $self->_dots, ' ') if $self->{verbose} || $self->{_atbol};
394 $self->print($self->colored($text, $color));
395 $self->print($self->colored($sum, 'red'));
396 $self->print("\n");
397 $self->_vsep($len);
398 $self->print($self->_warnings('detail')) if $self->{verbose};
399 $self->{_total}++;
400}
401
402sub passed
403{
404 my $self = shift;
405 $self->_result(@_, 'ok', 'bold green');
406 push @{$self->{_good}}, [$self->{perl}, $self->{config}];
407}
408
409sub failed
410{
411 my $self = shift;
412 $self->_result(@_, 'not ok', 'bold red');
413 push @{$self->{_bad}}, [$self->{perl}, $self->{config}];
414}
415
416sub warnings
417{
418 my $self = shift;
419 $self->{_warnings} = \@_;
420}
421
422sub _tobol
423{
424 my $self = shift;
425 print "\n" unless $self->{_atbol};
426 $self->{_atbol} = 1;
427}
428
429sub print
430{
431 my $self = shift;
432 my $text = join '', @_;
433 print $text;
434 $self->{_atbol} = $text =~ /[\r\n]$/;
435}
436
437sub say
438{
439 my $self = shift;
440 $self->_tobol;
441 $self->print(@_, "\n");
442}
443
444sub vsay
445{
446 goto &say if $_[0]->{verbose};
447}
448
449sub warn
450{
451 my $self = shift;
452 $self->say($self->colored(join('', @_), 'red'));
453}
454
455sub die
456{
457 my $self = shift;
458 $self->say($self->colored(join('', 'FATAL: ', @_), 'bold red'));
459 exit -1;
460}
461
462sub status
463{
464 my($self, $text) = @_;
465 $self->_tobol;
466 $self->print($self->colored($text, 'bold'), "\n");
467}
468
469sub finish
470{
471 my $self = shift;
472
473 if (@{$self->{_bad}}) {
474 $self->status("\nFailed with:");
475 for my $fail (@{$self->{_bad}}) {
476 my($perl, $cfg) = @$fail;
477 $self->set(config => $cfg);
478 $self->say(" ", $self->colored($perl, 'blue'), $self->colored($self->_config, 'green'));
479 }
480 }
481
482 $self->status(sprintf("\nPassed with %d of %d combination%s.\n",
483 scalar @{$self->{_good}}, cs($self->{_total})));
484
485 return scalar @{$self->{_bad}};
486}
487
4a582685 488__END__
489
490=head1 NAME
491
492soak - Test Perl modules with multiple Perl releases
493
494=head1 SYNOPSIS
495
496 soak [options] [perl ...]
497
498 --make=program override name of make program ($Config{make})
c07deaaf 499 --min=version use at least this version of perl
4a582685 500 --mmargs=options pass options to Makefile.PL (multiple --mmargs possible)
501 --verbose be verbose
56093a11 502 --nocolor don't use colored output
503
504=head1 DESCRIPTION
505
506The F<soak> utility can be used to test Perl modules with
507multiple Perl releases or build options. It automates the
508task of running F<Makefile.PL> and the modules test suite.
509
510It is not primarily intended for cross-platform checking,
511so don't expect it to work on all platforms.
512
513=head1 EXAMPLES
514
515To test your favourite module, just change to its root
516directory (where the F<Makefile.PL> is located) and run:
517
518 soak
519
520This will automatically look for Perl binaries installed
521on your system.
522
523Alternatively, you can explicitly pass F<soak> a list of
524Perl binaries:
525
526 soak perl5.8.6 perl5.9.2
527
528Last but not least, you can pass it a list of directories
529to recursively search for Perl binaries, for example:
530
531 soak /tmp/perl/install /usr/bin
532
533All of the above examples will run
534
535 perl Makefile.PL
536 make
537 make test
538
539for your module and report success or failure.
540
541If your F<Makefile.PL> can take arguments, you may also
542want to test different configurations for your module.
543You can do so with the I<--mmargs> option:
544
545 soak --mmargs=' ' --mmargs='CCFLAGS=-Wextra' --mmargs='enable-debug'
546
547This will run
548
549 perl Makefile.PL
550 make
551 make test
552 perl Makefile.PL CCFLAGS=-Wextra
553 make
554 make test
555 perl Makefile.PL enable-debug
556 make
557 make test
558
559for each Perl binary.
560
561If you have a directory full of different Perl binaries,
562but your module isn't expected to work with ancient perls,
563you can use the I<--min> option to specify the minimum
564version a Perl binary must have to be chosen for testing:
565
566 soak --min=5.8.1
567
568Usually, the output of F<soak> is rather terse, to give
569you a good overview. If you'd like to see more of what's
570going on, use the I<--verbose> option:
571
572 soak --verbose
4a582685 573
574=head1 COPYRIGHT
575
0d0f8426 576Version 3.x, Copyright (c) 2004-2006, Marcus Holland-Moritz.
4a582685 577
578Version 2.x, Copyright (C) 2001, Paul Marquess.
579
580Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
581
582This program is free software; you can redistribute it and/or
583modify it under the same terms as Perl itself.
584
585=head1 SEE ALSO
586
587See L<Devel::PPPort>.
adfe19db 588
4a582685 589=cut
adfe19db 590