X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=t%2Ftest.pl;h=91daf1ae1b5b046db17e09a604c08439151c31d3;hb=19376ff47d71b111f6a574ff2b52b5d3565221cc;hp=bd5d5774467fe535e6df8224fbc7cbab54836615;hpb=c880be78cc1faa866b54d83a9b8a76d90b52ff8e;p=p5sagit%2Fp5-mst-13.2.git diff --git a/t/test.pl b/t/test.pl index bd5d577..91daf1a 100644 --- a/t/test.pl +++ b/t/test.pl @@ -23,10 +23,17 @@ sub plan { END { my $ran = $test - 1; if (!$NO_ENDING && defined $planned && $planned != $ran) { - print STDOUT "# Looks like you planned $planned tests but ran $ran.\n"; + print STDERR "# Looks like you planned $planned tests but ran $ran.\n"; } } +# Use this instead of "print STDERR" when outputing failure diagnostic +# messages +sub _diag { + my $fh = $TODO ? *STDOUT : *STDERR; + print $fh @_; +} + sub skip_all { if (@_) { print STDOUT "1..0 - @_\n"; @@ -53,12 +60,12 @@ sub _ok { print STDOUT "$out\n"; unless ($pass) { - print STDOUT "# Failed $where\n"; + _diag "# Failed $where\n"; } # Ensure that the message is properly escaped. - print STDOUT map { /^#/ ? "$_\n" : "# $_\n" } - map { split /\n/ } @mess if @mess; + _diag map { /^#/ ? "$_\n" : "# $_\n" } + map { split /\n/ } @mess if @mess; $test++; @@ -70,6 +77,7 @@ sub _where { return "at $caller[1] line $caller[2]"; } +# DON'T use this for matches. Use like() instead. sub ok { my ($pass, $name, @mess) = @_; _ok($pass, _where(), $name, @mess); @@ -79,10 +87,47 @@ sub _q { my $x = shift; return 'undef' unless defined $x; my $q = $x; + $q =~ s/\\/\\\\/; $q =~ s/'/\\'/; return "'$q'"; } +sub _qq { + my $x = shift; + return defined $x ? '"' . display ($x) . '"' : 'undef'; +}; + +# keys are the codes \n etc map to, values are 2 char strings such as \n +my %backslash_escape; +foreach my $x (split //, 'nrtfa\\\'"') { + $backslash_escape{ord eval "\"\\$x\""} = "\\$x"; +} +# A way to display scalars containing control characters and Unicode. +# Trying to avoid setting $_, or relying on local $_ to work. +sub display { + my @result; + foreach my $x (@_) { + if (defined $x and not ref $x) { + my $y = ''; + foreach my $c (unpack("U*", $x)) { + if ($c > 255) { + $y .= sprintf "\\x{%x}", $c; + } elsif ($backslash_escape{$c}) { + $y .= $backslash_escape{$c}; + } else { + my $z = chr $c; # Maybe we can get away with a literal... + $z = sprintf "\\%03o", $c if $z =~ /[[:^print:]]/; + $y .= $z; + } + } + $x = $y; + } + return $x unless wantarray; + push @result, $x; + } + return @result; +} + sub is { my ($got, $expected, $name, @mess) = @_; my $pass = $got eq $expected; @@ -103,6 +148,70 @@ sub isnt { _ok($pass, _where(), $name, @mess); } +sub cmp_ok { + my($got, $type, $expected, $name, @mess) = @_; + + my $pass; + { + local $^W = 0; + local($@,$!); # don't interfere with $@ + # eval() sometimes resets $! + $pass = eval "\$got $type \$expected"; + } + unless ($pass) { + # It seems Irix long doubles can have 2147483648 and 2147483648 + # that stringify to the same thing but are acutally numerically + # different. Display the numbers if $type isn't a string operator, + # and the numbers are stringwise the same. + # (all string operators have alphabetic names, so tr/a-z// is true) + # This will also show numbers for some uneeded cases, but will + # definately be helpful for things such as == and <= that fail + if ($got eq $expected and $type !~ tr/a-z//) { + unshift @mess, "# $got - $expected = " . ($got - $expected) . "\n"; + } + unshift(@mess, "# got "._q($got)."\n", + "# expected $type "._q($expected)."\n"); + } + _ok($pass, _where(), $name, @mess); +} + +# Check that $got is within $range of $expected +# if $range is 0, then check it's exact +# else if $expected is 0, then $range is an absolute value +# otherwise $range is a fractional error. +# Here $range must be numeric, >= 0 +# Non numeric ranges might be a useful future extension. (eg %) +sub within { + my ($got, $expected, $range, $name, @mess) = @_; + my $pass; + if (!defined $got or !defined $expected or !defined $range) { + # This is a fail, but doesn't need extra diagnostics + } elsif ($got !~ tr/0-9// or $expected !~ tr/0-9// or $range !~ tr/0-9//) { + # This is a fail + unshift @mess, "# got, expected and range must be numeric\n"; + } elsif ($range < 0) { + # This is also a fail + unshift @mess, "# range must not be negative\n"; + } elsif ($range == 0) { + # Within 0 is == + $pass = $got == $expected; + } elsif ($expected == 0) { + # If expected is 0, treat range as absolute + $pass = ($got <= $range) && ($got >= - $range); + } else { + my $diff = $got - $expected; + $pass = abs ($diff / $expected) < $range; + } + unless ($pass) { + if ($got eq $expected) { + unshift @mess, "# $got - $expected = " . ($got - $expected) . "\n"; + } + unshift@mess, "# got "._q($got)."\n", + "# expected "._q($expected)." (within "._q($range).")\n"; + } + _ok($pass, _where(), $name, @mess); +} + # Note: this isn't quite as fancy as Test::More::like(). sub like { my ($got, $expected, $name, @mess) = @_; @@ -110,7 +219,8 @@ sub like { if (ref $expected eq 'Regexp') { $pass = $got =~ $expected; unless ($pass) { - unshift(@mess, "# got '$got'\n"); + unshift(@mess, "# got '$got'\n", + "# expected /$expected/\n"); } } else { $pass = $got =~ /$expected/; @@ -160,6 +270,34 @@ sub eq_array { return 1; } +sub eq_hash { + my ($orig, $suspect) = @_; + my $fail; + while (my ($key, $value) = each %$suspect) { + # Force a hash recompute if this perl's internals can cache the hash key. + $key = "" . $key; + if (exists $orig->{$key}) { + if ($orig->{$key} ne $value) { + print STDOUT "# key ", _qq($key), " was ", _qq($orig->{$key}), + " now ", _qq($value), "\n"; + $fail = 1; + } + } else { + print STDOUT "# key ", _qq($key), " is ", _qq($value), + ", not in original.\n"; + $fail = 1; + } + } + foreach (keys %$orig) { + # Force a hash recompute if this perl's internals can cache the hash key. + $_ = "" . $_; + next if (exists $suspect->{$_}); + print STDOUT "# key ", _qq($_), " was ", _qq($orig->{$_}), " now missing.\n"; + $fail = 1; + } + !$fail; +} + sub require_ok { my ($require) = @_; eval < 0; $$runperl .= ' ' . $_; } } @@ -252,7 +390,7 @@ sub runperl { if ($args{verbose}) { my $runperldisplay = $runperl; $runperldisplay =~ s/\n/\n\#/g; - print STDOUT "# $runperldisplay\n"; + print STDERR "# $runperldisplay\n"; } my $result = `$runperl`; $result =~ s/\n\n/\n/ if $is_vms; # XXX pipes sometimes double these @@ -260,18 +398,11 @@ sub runperl { } -sub BAILOUT { - print STDOUT "Bail out! @_\n"; - exit; +sub DIE { + print STDERR "# @_\n"; + exit 1; } - -# A way to display scalars containing control characters and Unicode. -sub display { - map { join("", map { $_ > 255 ? sprintf("\\x{%x}", $_) : chr($_) =~ /[[:cntrl:]]/ ? sprintf("\\%03o", $_) : chr($_) } unpack("U*", $_)) } @_; -} - - # A somewhat safer version of the sometimes wrong $^X. my $Perl; sub which_perl { @@ -286,6 +417,7 @@ sub which_perl { } else { $exe = $Config{_exe}; } + $exe = '' unless defined $exe; # This doesn't absolutize the path: beware of future chdirs(). # We could do File::Spec->abs2rel() but that does getcwd()s, @@ -301,11 +433,13 @@ sub which_perl { $Perl = File::Spec->catfile(File::Spec->curdir(), $perl); } } - - # Its like this. stat on Cygwin treats 'perl' to mean 'perl.exe' - # but open does not. This can get confusing, so to be safe we - # always put the .exe on the end on Cygwin. - $Perl .= $exe if $^O eq 'cygwin' && $Perl !~ /\Q$exe\E$/; + + # Build up the name of the executable file from the name of + # the command. + + if ($Perl !~ /\Q$exe\E$/i) { + $Perl .= $exe; + } warn "which_perl: cannot find $Perl from $^X" unless -f $Perl; @@ -315,4 +449,110 @@ sub which_perl { return $Perl; } +sub unlink_all { + foreach my $file (@_) { + 1 while unlink $file; + print STDERR "# Couldn't unlink '$file': $!\n" if -f $file; + } +} + + +my $tmpfile = "misctmp000"; +1 while -f ++$tmpfile; +END { unlink_all $tmpfile } + +# +# _fresh_perl +# +# The $resolve must be a subref that tests the first argument +# for success, or returns the definition of success (e.g. the +# expected scalar) if given no arguments. +# + +sub _fresh_perl { + my($prog, $resolve, $runperl_args, $name) = @_; + + $runperl_args ||= {}; + $runperl_args->{progfile} = $tmpfile; + $runperl_args->{stderr} = 1; + + open TEST, ">$tmpfile" or die "Cannot open $tmpfile: $!"; + + # VMS adjustments + if( $^O eq 'VMS' ) { + $prog =~ s#/dev/null#NL:#; + + # VMS file locking + $prog =~ s{if \(-e _ and -f _ and -r _\)} + {if (-e _ and -f _)} + } + + print TEST $prog, "\n"; + close TEST or die "Cannot close $tmpfile: $!"; + + my $results = runperl(%$runperl_args); + my $status = $?; + + # Clean up the results into something a bit more predictable. + $results =~ s/\n+$//; + $results =~ s/at\s+misctmp\d+\s+line/at - line/g; + $results =~ s/of\s+misctmp\d+\s+aborted/of - aborted/g; + + # bison says 'parse error' instead of 'syntax error', + # various yaccs may or may not capitalize 'syntax'. + $results =~ s/^(syntax|parse) error/syntax error/mig; + + if ($^O eq 'VMS') { + # some tests will trigger VMS messages that won't be expected + $results =~ s/\n?%[A-Z]+-[SIWEF]-[A-Z]+,.*//; + + # pipes double these sometimes + $results =~ s/\n\n/\n/g; + } + + my $pass = $resolve->($results); + unless ($pass) { + print STDERR "# PROG: $switch\n$prog\n"; + print STDERR "# EXPECTED:\n", $resolve->(), "\n"; + print STDERR "# GOT:\n$results\n"; + print STDERR "# STATUS: $status\n"; + } + + # Use the first line of the program as a name if none was given + unless( $name ) { + ($first_line, $name) = $prog =~ /^((.{1,50}).*)/; + $name .= '...' if length $first_line > length $name; + } + + _ok($pass, _where(), "fresh_perl - $name"); +} + +# +# run_perl_is +# +# Combination of run_perl() and is(). +# + +sub fresh_perl_is { + my($prog, $expected, $runperl_args, $name) = @_; + _fresh_perl($prog, + sub { @_ ? $_[0] eq $expected : $expected }, + $runperl_args, $name); +} + +# +# run_perl_like +# +# Combination of run_perl() and like(). +# + +sub fresh_perl_like { + my($prog, $expected, $runperl_args, $name) = @_; + _fresh_perl($prog, + sub { @_ ? + $_[0] =~ (ref $expected ? $expected : /$expected/) : + $expected }, + $runperl_args, $name); +} + 1;