This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
OS2-Process/Process.pm: Fix broken pod links
[perl5.git] / t / test.pl
index 088c7e3..2fbde93 100644 (file)
--- a/t/test.pl
+++ b/t/test.pl
@@ -24,6 +24,10 @@ my $planned;
 my $noplan;
 my $Perl;       # Safer version of $^X set by which_perl()
 
+# This defines ASCII/UTF-8 vs EBCDIC/UTF-EBCDIC
+$::IS_ASCII  = ord 'A' ==  65;
+$::IS_EBCDIC = ord 'A' == 193;
+
 $TODO = 0;
 $NO_ENDING = 0;
 $Tests_Are_Passing = 1;
@@ -84,18 +88,22 @@ sub _diag {
     $TODO ? _print(@mess) : _print_stderr(@mess);
 }
 
-# Use this instead of "print STDERR" when outputing failure diagnostic
+# Use this instead of "print STDERR" when outputting failure diagnostic
 # messages
 sub diag {
     _diag(@_);
 }
 
-# Use this instead of "print" when outputing informational messages
+# Use this instead of "print" when outputting informational messages
 sub note {
     return unless @_;
     _print( _comment(@_) );
 }
 
+sub is_miniperl {
+    return !defined &DynaLoader::boot_DynaLoader;
+}
+
 sub _comment {
     return map { /^#/ ? "$_\n" : "# $_\n" }
            map { split /\n/ } @_;
@@ -110,6 +118,73 @@ sub skip_all {
     exit(0);
 }
 
+sub skip_all_if_miniperl {
+    skip_all(@_) if is_miniperl();
+}
+
+sub skip_all_without_dynamic_extension {
+    my $extension = shift;
+    skip_all("no dynamic loading on miniperl, no $extension") if is_miniperl();
+    unless (eval {require Config; 1}) {
+       warn "test.pl had problems loading Config: $@";
+       return;
+    }
+    $extension =~ s!::!/!g;
+    return if ($Config::Config{extensions} =~ /\b$extension\b/);
+    skip_all("$extension was not built");
+}
+
+sub skip_all_without_perlio {
+    skip_all('no PerlIO') unless PerlIO::Layer->find('perlio');
+}
+
+sub skip_all_without_config {
+    unless (eval {require Config; 1}) {
+       warn "test.pl had problems loading Config: $@";
+       return;
+    }
+    foreach (@_) {
+       next if $Config::Config{$_};
+       my $key = $_; # Need to copy, before trying to modify.
+       $key =~ s/^use//;
+       $key =~ s/^d_//;
+       skip_all("no $key");
+    }
+}
+
+sub find_git_or_skip {
+    my ($found_dir, $reason);
+    if (-d '.git') {
+       $found_dir = 1;
+    } elsif (-l 'MANIFEST' && -l 'AUTHORS') {
+       my $where = readlink 'MANIFEST';
+       die "Can't readling MANIFEST: $!" unless defined $where;
+       die "Confusing symlink target for MANIFEST, '$where'"
+           unless $where =~ s!/MANIFEST\z!!;
+       if (-d "$where/.git") {
+           # Looks like we are in a symlink tree
+           chdir $where or die "Can't chdir '$where': $!";
+           note("Found source tree at $where");
+           $found_dir = 1;
+       }
+    }
+    if ($found_dir) {
+       my $version_string = `git --version`;
+       if (defined $version_string
+             && $version_string =~ /\Agit version (\d+\.\d+\.\d+)(.*)/) {
+           return if eval "v$1 ge v1.5.0";
+           # If you have earlier than 1.5.0 and it works, change this test
+           $reason = "in git checkout, but git version '$1$2' too old";
+       } else {
+           $reason = "in git checkout, but cannot run git";
+       }
+    } else {
+       $reason = 'not being run from a git checkout';
+    }
+    skip_all($reason) if $_[0] && $_[0] eq 'all';
+    skip($reason, @_);
+}
+
 sub _ok {
     my ($pass, $where, $name, @mess) = @_;
     # Do not try to microoptimize by factoring out the "not ".
@@ -131,13 +206,14 @@ sub _ok {
 
     _print "$out\n";
 
-    unless ($pass) {
+    if ($pass) {
+       note @mess; # Ensure that the message is properly escaped.
+    }
+    else {
        _diag "# Failed $where\n";
+       _diag @mess;
     }
 
-    # Ensure that the message is properly escaped.
-    _diag @mess;
-
     $test = $test + 1; # don't use ++
 
     return $pass;
@@ -258,12 +334,12 @@ sub cmp_ok ($$$@) {
     }
     unless ($pass) {
         # It seems Irix long doubles can have 2147483648 and 2147483648
-        # that stringify to the same thing but are acutally numerically
+        # that stringify to the same thing but are actually 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
+        # This will also show numbers for some unneeded cases, but will
+        # definitely 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";
         }
@@ -316,12 +392,12 @@ sub like   ($$@) { like_yn (0,@_) }; # 0 for -
 sub unlike ($$@) { like_yn (1,@_) }; # 1 for un-
 
 sub like_yn ($$$@) {
-    my ($flip, $got, $expected, $name, @mess) = @_;
+    my ($flip, undef, $expected, $name, @mess) = @_;
     my $pass;
-    $pass = $got =~ /$expected/ if !$flip;
-    $pass = $got !~ /$expected/ if $flip;
+    $pass = $_[1] =~ /$expected/ if !$flip;
+    $pass = $_[1] !~ /$expected/ if $flip;
     unless ($pass) {
-       unshift(@mess, "#      got '$got'\n",
+       unshift(@mess, "#      got '$_[1]'\n",
                $flip
                ? "# expected !~ /$expected/\n" : "# expected /$expected/\n");
     }
@@ -361,6 +437,10 @@ sub skip {
     last SKIP;
 }
 
+sub skip_if_miniperl {
+    skip(@_) if is_miniperl();
+}
+
 sub todo_skip {
     my $why = shift;
     my $n   = @_ ? shift : 1;
@@ -413,20 +493,29 @@ sub eq_hash {
   !$fail;
 }
 
+# We only provide a subset of the Test::More functionality.
 sub require_ok ($) {
     my ($require) = @_;
-    eval <<REQUIRE_OK;
+    if ($require =~ tr/[A-Za-z0-9:.]//c) {
+       fail("Invalid character in \"$require\", passed to require_ok");
+    } else {
+       eval <<REQUIRE_OK;
 require $require;
 REQUIRE_OK
-    _ok(!$@, _where(), "require $require");
+       is($@, '', _where(), "require $require");
+    }
 }
 
 sub use_ok ($) {
     my ($use) = @_;
-    eval <<USE_OK;
+    if ($use =~ tr/[A-Za-z0-9:.]//c) {
+       fail("Invalid character in \"$use\", passed to use");
+    } else {
+       eval <<USE_OK;
 use $use;
 USE_OK
-    _ok(!$@, _where(), "use $use");
+       is($@, '', _where(), "use $use");
+    }
 }
 
 # runperl - Runs a separate perl interpreter.
@@ -500,7 +589,7 @@ sub _create_runperl { # Create the string to qx in runperl().
     } elsif (defined $args{progfile}) {
        $runperl = $runperl . qq( "$args{progfile}");
     } else {
-       # You probaby didn't want to be sucking in from the upstream stdin
+       # You probably didn't want to be sucking in from the upstream stdin
        die "test.pl:runperl(): none of prog, progs, progfile, args, "
            . " switches or stdin specified"
            unless defined $args{args} or defined $args{switches}
@@ -548,7 +637,7 @@ sub runperl {
        # run a fresh perl, so we'll brute force launder everything for you
        my $sep;
 
-       if (! eval 'require Config; 1') {
+       if (! eval {require Config; 1}) {
            warn "test.pl had problems loading Config: $@";
            $sep = ':';
        } else {
@@ -595,10 +684,10 @@ sub which_perl {
        $Perl = $^X;
 
        # VMS should have 'perl' aliased properly
-       return $Perl if $^O eq 'VMS';
+       return $Perl if $is_vms;
 
        my $exe;
-       if (! eval 'require Config; 1') {
+       if (! eval {require Config; 1}) {
            warn "test.pl had problems loading Config: $@";
            $exe = '';
        } else {
@@ -612,7 +701,7 @@ sub which_perl {
 
        if ($Perl =~ /^perl\Q$exe\E$/i) {
            my $perl = "perl$exe";
-           if (! eval 'require File::Spec; 1') {
+           if (! eval {require File::Spec; 1}) {
                warn "test.pl had problems loading File::Spec: $@";
                $Perl = "./$perl";
            } else {
@@ -680,16 +769,8 @@ sub tempfile {
 # This is the temporary file for _fresh_perl
 my $tmpfile = tempfile();
 
-#
-# _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) = @_;
+    my($prog, $action, $expect, $runperl_args, $name) = @_;
 
     # Given the choice of the mis-parsable {}
     # (we want an anon hash, but a borked lexer might think that it's a block)
@@ -705,7 +786,7 @@ sub _fresh_perl {
     open TEST, ">$tmpfile" or die "Cannot open $tmpfile: $!";
 
     # VMS adjustments
-    if( $^O eq 'VMS' ) {
+    if( $is_vms ) {
         $prog =~ s#/dev/null#NL:#;
 
         # VMS file locking
@@ -728,7 +809,7 @@ sub _fresh_perl {
     # various yaccs may or may not capitalize 'syntax'.
     $results =~ s/^(syntax|parse) error/syntax error/mig;
 
-    if ($^O eq 'VMS') {
+    if ($is_vms) {
         # some tests will trigger VMS messages that won't be expected
         $results =~ s/\n?%[A-Z]+-[SIWEF]-[A-Z]+,.*//;
 
@@ -736,21 +817,31 @@ sub _fresh_perl {
         $results =~ s/\n\n/\n/g;
     }
 
-    my $pass = $resolve->($results);
-    unless ($pass) {
-        _diag "# PROG: \n$prog\n";
-        _diag "# EXPECTED:\n", $resolve->(), "\n";
-        _diag "# GOT:\n$results\n";
-        _diag "# 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 = $name . '...' if length $first_line > length $name;
     }
 
-    _ok($pass, _where(), "fresh_perl - $name");
+    # Historically this was implemented using a closure, but then that means
+    # that the tests for closures avoid using this code. Given that there
+    # are exactly two callers, doing exactly two things, the simpler approach
+    # feels like a better trade off.
+    my $pass;
+    if ($action eq 'eq') {
+       $pass = is($results, $expect, $name);
+    } elsif ($action eq '=~') {
+       $pass = like($results, $expect, $name);
+    } else {
+       die "_fresh_perl can't process action '$action'";
+    }
+       
+    unless ($pass) {
+        _diag "# PROG: \n$prog\n";
+        _diag "# STATUS: $status\n";
+    }
+
+    return $pass;
 }
 
 #
@@ -767,9 +858,7 @@ sub fresh_perl_is {
     $expected =~ s/\n+$//;
 
     local $Level = 2;
-    _fresh_perl($prog,
-               sub { @_ ? $_[0] eq $expected : $expected },
-               $runperl_args, $name);
+    _fresh_perl($prog, 'eq', $expected, $runperl_args, $name);
 }
 
 #
@@ -781,9 +870,175 @@ sub fresh_perl_is {
 sub fresh_perl_like {
     my($prog, $expected, $runperl_args, $name) = @_;
     local $Level = 2;
-    _fresh_perl($prog,
-               sub { @_ ? $_[0] =~ $expected : $expected },
-               $runperl_args, $name);
+    _fresh_perl($prog, '=~', $expected, $runperl_args, $name);
+}
+
+# Many tests use the same format in __DATA__ or external files to specify a
+# sequence of (fresh) tests to run, extra files they may temporarily need, and
+# what the expected output is. So have excatly one copy of the code to run that
+
+sub run_multiple_progs {
+    my $up = shift;
+    my @prgs;
+    if ($up) {
+       # The tests in lib run in a temporary subdirectory of t, and always
+       # pass in a list of "programs" to run
+       @prgs = @_;
+    } else {
+       # The tests below t run in t and pass in a file handle.
+       my $fh = shift;
+       local $/;
+       @prgs = split "\n########\n", <$fh>;
+    }
+
+    my $tmpfile = tempfile();
+
+    for (@prgs){
+       unless (/\n/) {
+           print "# From $_\n";
+           next;
+       }
+       my $switch = "";
+       my @temps ;
+       my @temp_path;
+       if (s/^(\s*-\w+)//) {
+           $switch = $1;
+       }
+       my ($prog, $expected) = split(/\nEXPECT(?:\n|$)/, $_, 2);
+
+       my %reason;
+       foreach my $what (qw(skip todo)) {
+           $prog =~ s/^#\s*\U$what\E\s*(.*)\n//m and $reason{$what} = $1;
+           # If the SKIP reason starts ? then it's taken as a code snippet to
+           # evaluate. This provides the flexibility to have conditional SKIPs
+           if ($reason{$what} && $reason{$what} =~ s/^\?//) {
+               my $temp = eval $reason{$what};
+               if ($@) {
+                   die "# In \U$what\E code reason:\n# $reason{$what}\n$@";
+               }
+               $reason{$what} = $temp;
+           }
+       }
+
+       if ($prog =~ /--FILE--/) {
+           my @files = split(/\n--FILE--\s*([^\s\n]*)\s*\n/, $prog) ;
+           shift @files ;
+           die "Internal error: test $_ didn't split into pairs, got " .
+               scalar(@files) . "[" . join("%%%%", @files) ."]\n"
+                   if @files % 2;
+           while (@files > 2) {
+               my $filename = shift @files;
+               my $code = shift @files;
+               push @temps, $filename;
+               if ($filename =~ m#(.*)/# && $filename !~ m#^\.\./#) {
+                   require File::Path;
+                   File::Path::mkpath($1);
+                   push(@temp_path, $1);
+               }
+               open my $fh, '>', $filename or die "Cannot open $filename: $!\n";
+               print $fh $code;
+               close $fh or die "Cannot close $filename: $!\n";
+           }
+           shift @files;
+           $prog = shift @files;
+       }
+
+       open my $fh, '>', $tmpfile or die "Cannot open >$tmpfile: $!";
+       print $fh q{
+        BEGIN {
+            open STDERR, '>&', STDOUT
+              or die "Can't dup STDOUT->STDERR: $!;";
+        }
+       };
+       print $fh "\n#line 1\n";  # So the line numbers don't get messed up.
+       print $fh $prog,"\n";
+       close $fh or die "Cannot close $tmpfile: $!";
+       my $results = runperl( stderr => 1, progfile => $tmpfile, $up
+                              ? (switches => ["-I$up/lib", $switch], nolib => 1)
+                              : (switches => [$switch])
+                               );
+       my $status = $?;
+       $results =~ s/\n+$//;
+       # allow expected output to be written as if $prog is on STDIN
+       $results =~ s/$::tempfile_regexp/-/g;
+       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;
+       }
+       # bison says 'parse error' instead of 'syntax error',
+       # various yaccs may or may not capitalize 'syntax'.
+       $results =~ s/^(syntax|parse) error/syntax error/mig;
+       # allow all tests to run when there are leaks
+       $results =~ s/Scalars leaked: \d+\n//g;
+
+       $expected =~ s/\n+$//;
+       my $prefix = ($results =~ s#^PREFIX(\n|$)##) ;
+       # any special options? (OPTIONS foo bar zap)
+       my $option_regex = 0;
+       my $option_random = 0;
+       if ($expected =~ s/^OPTIONS? (.+)\n//) {
+           foreach my $option (split(' ', $1)) {
+               if ($option eq 'regex') { # allow regular expressions
+                   $option_regex = 1;
+               }
+               elsif ($option eq 'random') { # all lines match, but in any order
+                   $option_random = 1;
+               }
+               else {
+                   die "$0: Unknown OPTION '$option'\n";
+               }
+           }
+       }
+       die "$0: can't have OPTION regex and random\n"
+           if $option_regex + $option_random > 1;
+       my $ok = 0;
+       if ($results =~ s/^SKIPPED\n//) {
+           print "$results\n" ;
+           $ok = 1;
+       }
+       elsif ($option_random) {
+           my @got = sort split "\n", $results;
+           my @expected = sort split "\n", $expected;
+
+           $ok = "@got" eq "@expected";
+       }
+       elsif ($option_regex) {
+           $ok = $results =~ /^$expected/;
+       }
+       elsif ($prefix) {
+           $ok = $results =~ /^\Q$expected/;
+       }
+       else {
+           $ok = $results eq $expected;
+       }
+
+       local $::TODO = $reason{todo};
+
+       unless ($ok) {
+           my $err_line = "PROG: $switch\n$prog\n" .
+                          "EXPECTED:\n$expected\n" .
+                          "GOT:\n$results\n";
+           if ($::TODO) {
+               $err_line =~ s/^/# /mg;
+               print $err_line;  # Harness can't filter it out from STDERR.
+           }
+           else {
+               print STDERR $err_line;
+           }
+       }
+
+       ok($ok);
+
+       foreach (@temps) {
+           unlink $_ if $_;
+       }
+       foreach (@temp_path) {
+           File::Path::rmtree $_ if -d $_;
+       }
+    }
 }
 
 sub can_ok ($@) {
@@ -875,6 +1130,77 @@ WHOA
     _ok( !$diag, _where(), $name );
 }
 
+# Purposefully avoiding a closure.
+sub __capture {
+    push @::__capture, join "", @_;
+}
+    
+sub capture_warnings {
+    my $code = shift;
+
+    local @::__capture;
+    local $SIG {__WARN__} = \&__capture;
+    &$code;
+    return @::__capture;
+}
+
+# This will generate a variable number of tests.
+# Use done_testing() instead of a fixed plan.
+sub warnings_like {
+    my ($code, $expect, $name) = @_;
+    local $Level = $Level + 1;
+
+    my @w = capture_warnings($code);
+
+    cmp_ok(scalar @w, '==', scalar @$expect, $name);
+    foreach my $e (@$expect) {
+       if (ref $e) {
+           like(shift @w, $e, $name);
+       } else {
+           is(shift @w, $e, $name);
+       }
+    }
+    if (@w) {
+       diag("Saw these additional warnings:");
+       diag($_) foreach @w;
+    }
+}
+
+sub _fail_excess_warnings {
+    my($expect, $got, $name) = @_;
+    local $Level = $Level + 1;
+    # This will fail, and produce diagnostics
+    is($expect, scalar @$got, $name);
+    diag("Saw these warnings:");
+    diag($_) foreach @$got;
+}
+
+sub warning_is {
+    my ($code, $expect, $name) = @_;
+    die sprintf "Expect must be a string or undef, not a %s reference", ref $expect
+       if ref $expect;
+    local $Level = $Level + 1;
+    my @w = capture_warnings($code);
+    if (@w > 1) {
+       _fail_excess_warnings(0 + defined $expect, \@w, $name);
+    } else {
+       is($w[0], $expect, $name);
+    }
+}
+
+sub warning_like {
+    my ($code, $expect, $name) = @_;
+    die sprintf "Expect must be a regexp object"
+       unless ref $expect eq 'Regexp';
+    local $Level = $Level + 1;
+    my @w = capture_warnings($code);
+    if (@w > 1) {
+       _fail_excess_warnings(0 + defined $expect, \@w, $name);
+    } else {
+       like($w[0], $expect, $name);
+    }
+}
+
 # Set a watchdog to timeout the entire test file
 # NOTE:  If the test file uses 'threads', then call the watchdog() function
 #        _AFTER_ the 'threads' module is loaded.
@@ -902,9 +1228,9 @@ sub watchdog ($;$)
 
         # On Windows and VMS, try launching a watchdog process
         #   using system(1, ...) (see perlport.pod)
-        if (($^O eq 'MSWin32') || ($^O eq 'VMS')) {
+        if ($is_mswin || $is_vms) {
             # On Windows, try to get the 'real' PID
-            if ($^O eq 'MSWin32') {
+            if ($is_mswin) {
                 eval { require Win32; };
                 if (defined(&Win32::GetCurrentProcessId)) {
                     $pid_to_kill = Win32::GetCurrentProcessId();
@@ -920,7 +1246,7 @@ sub watchdog ($;$)
                 local $SIG{'__WARN__'} = sub {
                     _diag("Watchdog warning: $_[0]");
                 };
-                my $sig = $^O eq 'VMS' ? 'TERM' : 'KILL';
+                my $sig = $is_vms ? 'TERM' : 'KILL';
                 my $cmd = _create_runperl( prog =>  "sleep($timeout);" .
                                                     "warn qq/# $timeout_msg" . '\n/;' .
                                                     "kill($sig, $pid_to_kill);");
@@ -980,7 +1306,7 @@ sub watchdog ($;$)
 
     # Use a watchdog thread because either 'threads' is loaded,
     #   or fork() failed
-    if (eval 'require threads; 1') {
+    if (eval {require threads; 1}) {
         'threads'->create(sub {
                 # Load POSIX if available
                 eval { require POSIX; };
@@ -995,7 +1321,7 @@ sub watchdog ($;$)
                 select(STDERR); $| = 1;
                 _diag($timeout_msg);
                 POSIX::_exit(1) if (defined(&POSIX::_exit));
-                my $sig = $^O eq 'VMS' ? 'TERM' : 'KILL';
+                my $sig = $is_vms ? 'TERM' : 'KILL';
                 kill($sig, $pid_to_kill);
             })->detach();
         return;
@@ -1012,7 +1338,7 @@ WATCHDOG_VIA_ALARM:
             select(STDERR); $| = 1;
             _diag($timeout_msg);
             POSIX::_exit(1) if (defined(&POSIX::_exit));
-            my $sig = $^O eq 'VMS' ? 'TERM' : 'KILL';
+            my $sig = $is_vms ? 'TERM' : 'KILL';
             kill($sig, $pid_to_kill);
         };
     }