1 # (X)Emacs mode: -*- cperl -*-
7 test - tools for helping in test suites (not including running externalprograms).
11 use FindBin 1.42 qw( $Bin );
12 use Test 1.13 qw( ok plan );
14 BEGIN { unshift @INC, $Bin };
26 open my $fh, '>', 'foo';
28 for 'Bulgaria', 'Cholet';
30 }, 'write foo'), 1, 'write foo';
32 save_output('stderr', *STDERR{IO});
34 print restore_output('stderr');
38 This package provides some variables, and sets up an environment, for test
39 scripts, such as those used in F<t/>.
41 This package does not including running external programs; that is provided by
42 C<test2.pm>. This is so that suites not needing that can include only
43 test.pm, and so not require the presence of C<IPC::Run>.
45 Setting up the environment includes:
49 =item Prepending F<blib/script> onto the path
51 =item Pushing the module F<lib/> dir onto the @PERL5LIB var
55 =item Pushing the module F<lib/> dir onto the @INC var
57 For internal C<use> calls.
59 =item Changing directory to a temporary directory
61 To avoid cluttering the local dir, and/or allowing the local directory
62 structure to affect matters.
64 =item Cleaning up the temporary directory afterwards
66 Unless TEST_DEBUG is set in the environment.
72 # ----------------------------------------------------------------------------
74 # Pragmas -----------------------------
78 use vars qw( @EXPORT_OK );
80 # Inheritance -------------------------
82 use base qw( Exporter );
86 The following symbols are exported upon request:
124 @EXPORT_OK = qw( BIN_DIR DATA_DIR REF_DIR LIB_DIR PERL
125 check_req compare evcheck find_exec only_files read_file
126 save_output restore_output tempdir tmpnam );
128 # Utility -----------------------------
130 use Carp qw( carp croak );
131 use Cwd 2.01 qw( cwd );
132 use Env qw( PATH PERL5LIB );
133 use Fatal 1.02 qw( close open seek sysopen unlink );
134 use Fcntl 1.03 qw( :DEFAULT );
135 use File::Basename qw( basename );
136 use File::Compare 1.1002 qw( );
137 use File::Path 1.0401 qw( mkpath rmtree );
138 use File::Spec 0.6 qw( );
139 use FindBin 1.42 qw( $Bin );
140 use POSIX 1.02 qw( );
141 use Test 1.122 qw( ok skip );
143 # ----------------------------------------------------------------------------
146 if ( File::Spec->file_name_is_absolute($_[0]) ) {
149 return catdir(cwd, $_[0]);
154 File::Spec->catdir(@_);
158 File::Spec->catfile(@_);
162 File::Spec->updir(@_);
166 croak "Can't min over 0 args!\n"
178 croak "Can't max over 0 args!\n"
189 # -------------------------------------
191 # -------------------------------------
193 use constant BIN_DIR => catdir $Bin, updir, 'bin';
194 use constant DATA_DIR => catdir $Bin, updir, 'data';
195 use constant REF_DIR => catdir $Bin, updir, 'testref';
196 use constant LIB_DIR => catdir $Bin, updir, 'lib';
198 use constant BUILD_SCRIPT_DIR => => catdir $Bin, updir, qw( blib script );
203 for (split /:/, $PATH) {
204 my $try = catfile $_, $exec;
211 use constant PERL => (basename($^X) eq $^X ?
215 # -------------------------------------
217 # -------------------------------------
219 # @PERL5LIB not available in Env for perl 5.00503
220 # unshift @PERL5LIB, LIB_DIR;
221 $PERL5LIB = defined $PERL5LIB ? join(':', LIB_DIR, $PERL5LIB) : LIB_DIR;
222 unshift @INC, LIB_DIR;
224 $PATH = join ':', BUILD_SCRIPT_DIR, split /:/, $PATH;
229 my $tmpdn = tempdir();
233 die "Couldn't create temp dir: $tmpdn: $!\n"
234 unless -r $tmpdn and -w $tmpdn and -x $tmpdn and -o $tmpdn and -d $tmpdn;
236 #@INC = map rel2abs($_), @INC;
239 # -------------------------------------
241 # -------------------------------------
253 Arrayref of names of files to expect to exist.
263 1 if exactly expected files exist, false otherwise.
276 my %files = map { $_ => 1 } readdir MYDIR;
281 for (@$expect, '.', '..') {
282 if ( exists $files{$_} ) {
284 } elsif ( ! -e $_ ) { # $_ might be absolute
285 carp "File not found: $_\n"
292 carp "Extra file found: $_\n"
304 # -------------------------------------
308 Eval code, return status
322 Name to use in error messages
332 1 if eval was okay, 0 if not.
341 my ($code, $name) = @_;
349 carp "Code $name failed: $@\n"
357 # -------------------------------------
361 Redirect a filehandle to temporary storage for later examination.
371 Name to store as (used in L<restore_output>)
375 The filehandle to save
381 # Map from names to saved filehandles.
383 # Values are arrayrefs, being filehandle that was saved (to restore), the
384 # filehandle being printed to in the meantime, and the original filehandle.
385 # This may be treated as a stack; to allow multiple saves... push & pop this
391 croak sprintf("%s takes 2 arguments\n", (caller 0)[3])
393 my ($name, $filehandle) = @_;
395 my $tmpfh = do { local *F; *F; };
396 my $savefh = do { local *F; *F; };
398 (undef, $tmpfh) = test::tmpnam();
399 select((select($tmpfh), $| = 1)[0]);
401 open $savefh, '>&' . fileno $filehandle
402 or die "can't dup $name: $!";
403 open $filehandle, '>&' . fileno $tmpfh
404 or die "can't open $name to tempfile: $!";
406 push @{$grabs{$name}}, $savefh, $tmpfh, $filehandle;
409 # -------------------------------------
411 =head2 restore_output
413 Restore a saved filehandle to its original state, return the saved output.
423 Name of the filehandle to restore (as passed to L<save_output>).
433 A single string being the output saved.
442 croak "$name has not been saved\n"
443 unless exists $grabs{$name};
444 croak "All saved instances of $name have been restored\n"
445 unless @{$grabs{$name}};
446 my ($savefh, $tmpfh, $origfh) = splice @{$grabs{$name}}, -3;
449 or die "cannot close $name opened to tempfile: $!";
450 open $origfh, '>&' . fileno $savefh
451 or die "cannot dup $name back again: $!";
452 select((select($origfh), $| = 1)[0]);
456 my $string = <$tmpfh>;
462 sub _test_save_restore_output {
463 warn "to stderr 1\n";
464 save_output("stderr", *STDERR{IO});
466 print 'SAVED:->:', restore_output("stderr"), ":<-\n";
467 warn "to stderr 2\n";
470 # -------------------------------------
474 Very much like the one in L<POSIX> or L<File::Temp>, but does not get deleted
475 if TEST_DEBUG has SAVE in the value.
485 I<Optional>. If defined, a name by which to refer to the tmpfile in user
496 Name of temporary file.
500 Open filehandle to temp file, in r/w mode. Only created & returned in list
512 my $savewarn = $SIG{__WARN__};
513 # Subvert bizarre (& incorrect) subroutine redefined errors in 5.005_03
514 local $SIG{__WARN__} =
517 if defined $savewarn and
518 UNIVERSAL::isa($savewarn,'CODE') and
519 $_[0] !~ /^Subroutine tmpnam redefined/;
523 my $tmpnam = POSIX::tmpnam;
526 push @tmpfns, [ $tmpnam, $_[0] ];
528 push @tmpfns, $tmpnam;
532 sysopen my $tmpfh, $tmpnam, O_RDWR | O_CREAT | O_EXCL;
533 return $tmpnam, $tmpfh;
541 if ( defined $ENV{TEST_DEBUG} and $ENV{TEST_DEBUG} =~ /\bSAVE\b/ ) {
544 printf "Used temp file: %s (%s)\n", @$_;
546 print "Used temp file: $_\n";
550 unlink map((ref $_ ? $_->[0] : $_), @tmpfns)
555 # -------------------------------------
559 Very much like the one in L<POSIX> or L<File::Temp>, but does not get deleted
560 if TEST_DEBUG has SAVE in the value (does get deleted otherwise).
574 Name of temporary dir.
584 my $tempdir = POSIX::tmpnam;
586 or die "Failed to create temporary directory $tempdir: $!\n";
589 push @tmpdirs, [ $tempdir, $_[0] ];
591 push @tmpdirs, $tempdir;
600 if ( defined $ENV{TEST_DEBUG} and $ENV{TEST_DEBUG} =~ /\bSAVE\b/ ) {
601 printf "Used temp dir: %s (%s)\n", @$_;
603 # Solaris gets narky about removing the pwd.
604 chdir File::Spec->rootdir;
608 if ( defined $ENV{TEST_DEBUG} and $ENV{TEST_DEBUG} =~ /\bSAVE\b/ ) {
609 print "Used temp dir: $_\n";
611 # Solaris gets narky about removing the pwd.
612 chdir File::Spec->rootdir;
619 # -------------------------------------
623 compare(+{ fn1 => $fn1, fn2 => $fn2, gzip => 1 });
625 This performs one test.
631 A single argument is taken, considered as a hash ref, with the following keys:
633 In TEST_DEBUG mode, if the files do not compare equal, outputs file info on
640 B<Mandatory> File to compare
644 B<Mandatory> File to compare
648 B<Mandatory> Test name
652 B<Optional> sort files prior to comparison. Requires the C<sort> command to
653 be on C<$PATH> (else skips).
657 B<Optional> gunzip files prior to comparison. Requires the C<gzip> command to
658 be on C<$PATH> (else skips). gzip occurs prior to any sort.
662 B<Optional> untar files prior to comparison. Requires the C<tar> command to
663 be on C<$PATH> (else skips). any gzip occurs prior to any tar. Tar files are
664 considered equal if they each contain the same filenames & each file contained
665 is equal. If the sort flag is present, each file is sorted prior to comparison.
674 #XYZ my ($cmd, $name, $in) = @_;
676 #XYZ my $infn = defined $in ? tmpnam : '/dev/null';
677 #XYZ my $outfn = tmpnam;
678 #XYZ my $errfn = tmpnam;
681 #XYZ croak "Couldn't fork: $!\n"
682 #XYZ unless defined $pid;
684 #XYZ if ( $pid == 0 ) { # Child
685 #XYZ open STDOUT, '>', $outfn;
686 #XYZ open STDERR, '>', $errfn;
687 #XYZ open STDIN, '<', $infn;
692 #XYZ my $rv = waitpid $pid, 0;
693 #XYZ my $status = $?;
695 #XYZ croak "Unexpected waitpid return from child $name: $rv (expected $pid)\n"
696 #XYZ unless $rv == $pid;
698 #XYZ local $/ = undef;
699 #XYZ local (OUT, ERR);
700 #XYZ open *OUT, '<', $outfn;
701 #XYZ open *ERR, '<', $errfn;
702 #XYZ my $out = <OUT>;
703 #XYZ my $err = <ERR>;
707 #XYZ return $status >> 8, $status & 127, $status & 128 , $out, $err
710 # return codes and old-style call semantics left for backwards compatibility
712 my $savewarn = $SIG{__WARN__};
713 # Subvert bizarre (& incorrect) subroutine redefined errors in 5.005_03
714 local $SIG{__WARN__} =
717 if defined $savewarn and
718 UNIVERSAL::isa($savewarn,'CODE') and
719 $_[0] !~ /^Subroutine compare redefined/;
723 my ($fn1, $fn2, $sort) = @_;
724 my ($gzip, $tar, $name);
727 if ( @_ == 1 and UNIVERSAL::isa($_[0], 'HASH') ) {
728 ($fn1, $fn2, $name, $sort, $gzip, $tar, $notest) =
729 @{$_[0]}{qw( fn1 fn2 name sort gunzip untar notest )};
730 my @missing = grep ! defined $_[0]->{$_}, qw( fn1 fn2 name );
731 carp "Missing mandatory key(s): " . join(', ', @missing) . "\n"
735 my ($name1, $name2) = ($fn1, $fn2);
737 for ( grep ! defined, $fn1, $fn2 ) {
738 carp 'Usage: compare({fn1 => $fn1, fn2 => $fn2, name => "some name"})' ."\n"
749 my $fn = ($name1, $name2)[$_];
751 carp "Does not exist: $fn\n"
754 } elsif ( ! -r $fn ) {
755 carp "Cannot read: $fn\n"
769 unless ( find_exec('gzip') ) {
770 print "ok # Skip gzip not found in path\n";
776 system "gzip $fn1 -cd > $tmp1"
777 and croak "gzip $fn1 failed: $?\n";
778 system "gzip $fn2 -cd > $tmp2"
779 and croak "gzip $fn2 failed: $?\n";
780 ($fn1, $fn2) = ($tmp1, $tmp2);
784 unless ( find_exec('tar') ) {
785 print "ok # Skip tar not found in path\n";
790 chomp (my @list1 = sort qx( tar tf $fn1 ));
791 croak "tar tf $fn1 failed with wait status: $?\n"
793 chomp(my @list2 = sort qx( tar tf $fn2 ));
794 croak "tar tf $fn2 failed with wait status: $?\n"
797 if ( @list2 > @list1 ) {
799 sprintf("More files (%d) in $name2 than $name1 (%d)\n",
800 scalar @list2, scalar @list1)
802 ok @list1, @list2, $name
805 } elsif ( @list1 > @list2 ) {
807 sprintf("More files (%d) in $name1 than $name2 (%d)\n",
808 scalar @list1, scalar @list2)
810 ok @list1, @list2, $name
815 for (my $i = 0; $i < @list1; $i++) {
816 if ( $list1[$i] lt $list2[$i] ) {
817 carp "File $list1[$i] is present in $name1 but not $name2\n"
819 ok $list1[$i], $list2[$i], $name
822 } elsif ( $list1[$i] gt $list2[$i] ) {
823 carp "File $list2[$i] is present in $name2 but not $name1\n"
825 ok $list2[$i], $list1[$i], $name
831 for my $fn (@list1) {
834 system "tar -xf $fn1 -O $fn > $tmp1"
835 and croak "tar -xf $fn1 -O $fn failed: $?\n";
836 system "tar -xf $fn2 -O $fn > $tmp2"
837 and croak "tar -xf $fn2 -O $fn failed: $?\n";
838 my $ok = compare({ fn1 => $tmp1,
843 qq'Subcheck file "$fn" for compare $name1, $name2',
845 unless ( $ok >= 1 ) {
846 carp qq'Difference found testing file "$fn" in tars $name1 ($tmp1), $name2 ($tmp2)\n'
860 unless ( find_exec('sort') ) {
861 print "ok # Skip sort not found in path\n";
867 system sort => $fn1, -o => $tmp1
868 and croak "Sort $fn1 failed: $?\n";
869 system sort => $fn2, -o => $tmp2
870 and croak "Sort $fn2 failed: $?\n";
871 ($fn1, $fn2) = ($tmp1, $tmp2);
874 unless ( File::Compare::compare($fn1, $fn2) ) {
880 if ( $ENV{TEST_DEBUG} ) {
882 die "Fork failed: $!\n"
885 if ( $pid ) { # Parent
886 my $waitpid = waitpid($pid, 0);
887 die "Waitpid got: $waitpid (expected $pid)\n"
888 unless $waitpid == $pid;
890 open *STDOUT{IO}, ">&" . fileno STDERR;
891 # Uniquify file names
892 my @args = keys %{+{ map {;$_=>1} $name1, $name2, $fn1, $fn2 }};
893 exec qw(ls -l), @args;
896 my $fh1 = IO::File->new($fn1, O_RDONLY)
897 or die "Couldn't open $fn1: $!\n";
898 my $fh2 = IO::File->new($fn2, O_RDONLY)
899 or die "Couldn't open $fn2: $!\n";
904 while ( ! $found and my $line1 = <$fh1> ) {
906 if ( ! defined $line2 ) {
907 print STDERR "$fn2 ended at line: $.\n";
909 } elsif ( $line2 ne $line1 ) {
910 my $maxlength = max(map length($_), $line1, $line2);
911 my $minlength = min(map length($_), $line1, $line2);
913 my @diffchars = grep(substr($line1, $_, 1) ne substr($line2, $_, 1),
915 my $diff = ' ' x $minlength;
916 substr($diff, $_, 1) = '|'
919 my @extrachars, map((length($line1) > length($line2) ? '^' : 'v'),
920 $minlength..$maxlength-1);
922 $diff = join '', $diff, @extrachars;
924 my $diff_count = @diffchars;
925 my $extra_count = @extrachars;
927 print STDERR <<"END";
928 Difference at line $. ($diff_count characters differ) (top line is $extra_count chars longer):
934 Differing characters at positions @{[join ',',@diffchars]} (zero-based)
942 if ( defined $line2 ) {
943 print STDERR "$name1 ended before line: $.\n";
945 print STDERR "Difference between $name1, $name2 not found!\n";
959 # -------------------------------------
963 Perform a requisite check on a given executable. This will skip if the
964 required modules are not present.
966 4+(n+m)*2 tests are performed, where n is the number of prerequisites
967 expected, and m is the number of outputs expected.
973 check_req('ccu-touch',
975 [[REQ_FILE, '/etc/passwd']],
976 [[REQ_FILE, 'passwd.foo']],
986 The name of the command to run. It is assumed that this command is in
987 blib/script; hence it should be an executable in this package, and C<make>
988 shuold have been run recently.
992 The arguments to pass to the cmd_name, as an arrayref.
996 The expected prerequisites, as an arrayref, wherein every member is a
997 two-element arrayref, the members being the requisite type, and the requisite
1002 The expected outputs, in the same format as the L<epres|"epres">.
1006 The name to use in error messages.
1015 my ($cmd_name, $args, $epres, $eouts, $testname) = @_;
1017 eval "use Pipeline::DataFlow 1.03 qw( :req_types );";
1021 if $ENV{TEST_DEBUG};
1022 $skip = 'Skipped: Pipeline::DataFlow 1.03 not found';
1029 my ($code, $expect) = @_;
1030 my $name = sprintf "%s (%2d)", $testname, $count++;
1031 my $value = UNIVERSAL::isa($code, 'CODE') ? $code->($name) : $code;
1032 skip $skip, $value, $expect, $name;
1035 # Initialize nicely to cope when read_reqs fails
1036 my ($pres, $outs) = ([], []);
1040 ($pres, $outs) = Pipeline::DataFlow->read_reqs
1041 ([catfile($Bin, updir, 'blib', 'script', $cmd_name),
1046 $test->(scalar @$pres, scalar @$epres);
1049 @epres = sort { $a->[1] cmp $b->[1] } @$epres;
1050 @pres = sort { $a->[1] cmp $b->[1] } @$pres;
1052 for (my $i = 0; $i < @epres; $i++) {
1053 my ($type, $value) = @{$epres[$i]};
1054 $test->($type, @pres > $i ? $pres[$i]->[0] : undef);
1055 $test->($value, @pres > $i ? $pres[$i]->[1] : undef);
1058 $test->(scalar @$outs, scalar @$eouts);
1061 @eouts = sort { $a->[1] cmp $b->[1] } @$eouts;
1062 @outs = sort { $a->[1] cmp $b->[1] } @$outs;
1064 for (my $i = 0; $i < @eouts; $i++) {
1065 my ($type, $value) = @{$eouts[$i]};
1066 $test->($type, @outs > $i ? $outs[$i]->[0] : undef);
1067 $test->($value, @outs > $i ? $outs[$i]->[1] : undef);
1070 $test->(only_files([]), 1);
1073 # -------------------------------------
1085 The name of the program
1095 The path to the first executable file with the given name on C<$PATH>. Or
1096 nothing, if no such file exists.
1104 # defined further up to use in constants
1106 # -------------------------------------
1120 =item line-terminator
1122 B<Optional>. Value of C<$/>. Defaults to C<"\n">.
1132 A list of lines in the file (lines determined by the value of
1133 line-terminator), as an arrayref.
1142 my ($fn, $term) = @_;
1145 unless defined $term;
1147 my $fh = do { local *F; *F };
1148 sysopen $fh, $fn, O_RDONLY;
1156 # ----------------------------------------------------------------------------
1166 =head1 REPORTING BUGS
1172 Martyn J. Pearce C<fluffy@cpan.org>
1176 Copyright (c) 2001, 2002, 2004 Martyn J. Pearce. This program is free
1177 software; you can redistribute it and/or modify it under the same terms as
1186 1; # keep require happy.