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