Testsuite: tidying
[exim.git] / test / runtest
index 1fd233286d36feb0534488b3c1486d289aceed26..1fe7636da10ed2c65411ce1befe1859cc24f3281 100755 (executable)
@@ -33,6 +33,9 @@ use Exim::Runtest;
 
 use if $ENV{DEBUG} && $ENV{DEBUG} =~ /\bruntest\b/ => ('Smart::Comments' => '####');
 
+use constant TEST_TOP => 8999;
+use constant TEST_SPECIAL_TOP => 9999;
+
 
 # Start by initializing some global variables
 
@@ -50,7 +53,7 @@ my $cf = 'bin/cf -exact';
 my $cr = "\r";
 my $debug = 0;
 my $flavour = do {
-  my $f = Exim::Runtest::flavour();
+  my $f = Exim::Runtest::flavour() // '';
   (grep { $f eq $_ } Exim::Runtest::flavours()) ? $f : 'FOO';
 };
 my $force_continue = 0;
@@ -61,6 +64,7 @@ my $more = 'less -XF';
 my $optargs = '';
 my $save_output = 0;
 my $server_opts = '';
+my $slow = 0;
 my $valgrind = 0;
 
 my $have_ipv4 = 1;
@@ -68,10 +72,9 @@ my $have_ipv6 = 1;
 my $have_largefiles = 0;
 
 my $test_start = 1;
-my $test_end = $test_top = 8999;
-my $test_special_top = 9999;
+my $test_end = TEST_TOP;
+
 my @test_list = ();
-my @test_dirs = ();
 
 
 # Networks to use for DNS tests. We need to choose some networks that will
@@ -358,6 +361,7 @@ open(IN, "$file") || tests_exit(-1, "Failed to open $file: $!");
 my($is_log) = $file =~ /log/;
 my($is_stdout) = $file =~ /stdout/;
 my($is_stderr) = $file =~ /stderr/;
+my($is_mail) = $file =~ /mail/;
 
 # Date pattern
 
@@ -420,12 +424,6 @@ RESET_AFTER_EXTRA_LINE_READ:
   s?prvs=([^/]+)/[\da-f]{10}@?prvs=$1/xxxxxxxxxx@?g;    # Old form
   s?prvs=[\da-f]{10}=([^@]+)@?prvs=xxxxxxxxxx=$1@?g;    # New form
 
-  # Error lines on stdout from SSL contain process id values and file names.
-  # They also contain a source file name and line number, which may vary from
-  # release to release.
-  s/^\d+:error:/pppp:error:/;
-  s/:(?:\/[^\s:]+\/)?([^\/\s]+\.c):\d+:/:$1:dddd:/;
-
   # There are differences in error messages between OpenSSL versions
   s/SSL_CTX_set_cipher_list/SSL_connect/;
 
@@ -805,7 +803,10 @@ RESET_AFTER_EXTRA_LINE_READ:
   # numbers, or handle specific bad conditions in different ways, leading to
   # different wording in the error messages, so we cannot compare them.
 
-  s/(TLS error on connection (?:from .* )?\(SSL_\w+\): error:)(.*)/$1 <<detail omitted>>/;
+#XXX This loses any trailing "deliving unencypted to" which is unfortunate
+#    but I can't work out how to deal with that.
+  s/(TLS session: \(SSL_\w+\): error:)(.*)(?!: delivering)/$1 <<detail omitted>>/;
+  s/(TLS error on connection from .* \(SSL_\w+\): error:)(.*)/$1 <<detail omitted>>/;
   next if /SSL verify error: depth=0 error=certificate not trusted/;
 
   # ======== Maildir things ========
@@ -889,15 +890,25 @@ RESET_AFTER_EXTRA_LINE_READ:
         }
       }
 
+    # remote IPv6 addrs vary
+    s/^(Connection request from) \[.*:.*:.*\]$/$1 \[ipv6\]/;
+
     # openssl version variances
-    next if /^SSL info: unknown state/;
-    next if /^SSL info: SSLv2\/v3 write client hello A/;
-    next if /^SSL info: SSLv3 read server key exchange A/;
+  # Error lines on stdout from SSL contain process id values and file names.
+  # They also contain a source file name and line number, which may vary from
+  # release to release.
+
+    next if /^SSL info:/;
     next if /SSL verify error: depth=0 error=certificate not trusted/;
     s/SSL3_READ_BYTES/ssl3_read_bytes/i;
+    s/^\d+:error:\d+(:SSL routines:ssl3_read_bytes:[^:]+:).*(:SSL alert number \d\d)$/pppp:error:dddddddd$1\[...\]$2/;
 
     # gnutls version variances
     next if /^Error in the pull function./;
+
+    # optional IDN2 variant conversions.  Accept either IDN1 or IDN2
+    s/conversion  strasse.de/conversion  xn--strae-oqa.de/;
+    s/conversion: german.xn--strae-oqa.de/conversion: german.straße.de/;
     }
 
   # ======== stderr ========
@@ -963,7 +974,7 @@ RESET_AFTER_EXTRA_LINE_READ:
     }
     next if /^tls_validate_require_cipher child \d+ ended: status=0x0/;
 
-    # We invoke Exim with -D, so we hit this new messag as of Exim 4.73:
+    # We invoke Exim with -D, so we hit this new message as of Exim 4.73:
     next if /^macros_trusted overridden to true by whitelisting/;
 
     # We have to omit the localhost ::1 address so that all is well in
@@ -1076,6 +1087,10 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Not all platforms build with DKIM enabled
     next if /^PDKIM >> Body data for hash, canonicalized/;
 
+    #  Parts of DKIM-specific debug output depend on the time/date
+    next if /^date:\w+,\{SP\}/;
+    next if /^PDKIM \[[^[]+\] (Header hash|b) computed:/;
+
     # Not all platforms support TCP Fast Open, and the compile omits the check
     if (s/\S+ in hosts_try_fastopen\? no \(option unset\)\n$//)
       {
@@ -1265,8 +1280,8 @@ if (! -e $sf_current)
       log_failure($log_failed_filename, $testno, $rf);
       log_test($log_summary_filename, $testno, 'F') if ($force_continue);
     }
-    return 1 if /^c$/i;
-    last if (/^s$/);
+    return 1 if /^c$/i && $rf !~ /paniclog/ && $rsf !~ /paniclog/;
+    last if (/^[sc]$/);
     }
 
   foreach $f ($rf, $rsf)
@@ -1506,7 +1521,7 @@ $munges =
                   )($|[ ]=)/x' },
 
     'sys_bindir' =>
-    { 'mainlog' => 's%/(usr/)?bin/%SYSBINDIR/%' },
+    { 'mainlog' => 's%/(usr/(local/)?)?bin/%SYSBINDIR/%' },
 
     'sync_check_data' =>
     { 'mainlog'   => 's/^(.* SMTP protocol synchronization error .* next input=.{8}).*$/$1<suppressed>/',
@@ -1780,7 +1795,7 @@ system("$cmd");
 # The <SCRIPT> file is open for us to read an optional return code line,
 # followed by the command line and any following data lines for stdin. The
 # command line can be continued by the use of \. Data lines are not continued
-# in this way. In all lines, the following substutions are made:
+# in this way. In all lines, the following substitutions are made:
 #
 # DIR    => the current directory
 # CALLER => the caller of this script
@@ -1789,14 +1804,14 @@ system("$cmd");
 #            reference to the subtest number, holding previous value
 #            reference to the expected return code value
 #            reference to where to put the command name (for messages)
-#            auxilliary information returned from a previous run
+#            auxiliary information returned from a previous run
 #
-# Returns:   0 the commmand was executed inline, no subprocess was run
+# Returns:   0 the command was executed inline, no subprocess was run
 #            1 a non-exim command was run and waited for
 #            2 an exim command was run and waited for
 #            3 a command was run and not waited for (daemon, server, exim_lock)
 #            4 EOF was encountered after an initial return code line
-# Optionally alse a second parameter, a hash-ref, with auxilliary information:
+# Optionally also a second parameter, a hash-ref, with auxiliary information:
 #            exim_pid: pid of a run process
 #            munge: name of a post-script results munger
 
@@ -2257,14 +2272,20 @@ elsif (/^((?i:[A-Z\d_]+=\S+\s+)+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+
 
   if ($args =~ /\$msg/)
     {
-    my($listcmd) = "$parm_cwd/eximdir/exim -bp " .
-                   "-DEXIM_PATH=$parm_cwd/eximdir/exim " .
-                   "-C $parm_cwd/test-config |";
-    print ">> Getting queue list from:\n>>    $listcmd\n" if ($debug);
-    open (QLIST, $listcmd) || tests_exit(-1, "Couldn't run \"exim -bp\": $!\n");
-    my(@msglist) = ();
-    while (<QLIST>) { push (@msglist, $1) if /^\s*\d+[smhdw]\s+\S+\s+(\S+)/; }
-    close(QLIST);
+    my @listcmd  = ("$parm_cwd/eximdir/exim", '-bp',
+                   "-DEXIM_PATH=$parm_cwd/eximdir/exim",
+                   -C => "$parm_cwd/test-config");
+    print ">> Getting queue list from:\n>>    @listcmd\n" if $debug;
+    # We need the message ids sorted in ascending order.
+    # Message id is: <timestamp>-<pid>-<fractional-time>. On some systems (*BSD) the
+    # PIDs are randomized, so sorting just the whole PID doesn't work.
+    # We do the Schartz' transformation here (sort on
+    # <timestamp><fractional-time>). Thanks to Kirill Miazine
+    my @msglist =
+      map { $_->[1] }                                   # extract the values
+      sort { $a->[0] cmp $b->[0] }                      # sort by key
+      map { [join('.' => (split /-/, $_)[0,2]) => $_] } # key (timestamp.fractional-time) => value(message_id)
+      map { /^\s*\d+[smhdw]\s+\S+\s+(\S+)/ } `@listcmd` or tests_exit(-1, "No output from `exim -bp` (@listcmd)\n");
 
     # Done backwards just in case there are more than 9
 
@@ -2532,6 +2553,7 @@ while (@ARGV > 0 && $ARGV[0] =~ /^-/)
     if ($arg eq "-NOIPV4") { $have_ipv4 = 0; next; }
     if ($arg eq "-NOIPV6") { $have_ipv6 = 0; next; }
     if ($arg eq "-KEEP")   { $save_output = 1; next; }
+    if ($arg eq "-SLOW")   { $slow = 1; next; }
     if ($arg eq "-VALGRIND")   { $valgrind = 1; next; }
     if ($arg =~ /^-FLAVOU?R$/) { $flavour = shift; next; }
     }
@@ -2540,14 +2562,15 @@ while (@ARGV > 0 && $ARGV[0] =~ /^-/)
 
 # Any subsequent arguments are a range of test numbers.
 
-if (@ARGV > 0)
+if (@ARGV)
   {
-  $test_end = $test_start = $ARGV[0];
-  $test_end = $ARGV[1] if (@ARGV > 1);
-  $test_end = ($test_start >= 9000)? $test_special_top : $test_top
-    if $test_end eq "+";
+  $test_end = $test_start = shift;
+  $test_end = shift if @ARGV;
+  $test_end = ($test_start >= 9000)? TEST_SPECIAL_TOP : TEST_TOP
+    if $test_end eq '+';
   die "** Test numbers out of order\n" if ($test_end < $test_start);
   }
+my @test_range = $test_start..$test_end;
 
 
 ##################################################
@@ -2612,10 +2635,13 @@ close(IN);
 close(OUT);
 
 print("Probing with config file: $parm_cwd/test-config\n");
-open(EXIMINFO, "$parm_exim -d -C $parm_cwd/test-config -DDIR=$parm_cwd " .
-               "-bP exim_user exim_group 2>&1|") ||
-  die "** Cannot run $parm_exim: $!\n";
-while(<EXIMINFO>)
+
+my $eximinfo = "$parm_exim -d -C $parm_cwd/test-config -DDIR=$parm_cwd -bP exim_user exim_group";
+chomp(my @eximinfo = `$eximinfo 2>&1`);
+die "$0: Can't run $eximinfo\n" if $? == -1;
+
+warn 'Got ' . $?>>8 . " from $eximinfo\n" if $?;
+foreach (@eximinfo)
   {
   if (my ($version) = /^Exim version (\S+)/) {
     my $git = `git describe --dirty=-XX --match 'exim-4*'`;
@@ -2641,21 +2667,21 @@ ___
        if /^Configure owner:\s*(\d+):(\d+)/;
   print if /wrong owner/;
   }
-close(EXIMINFO);
 
-if (defined $parm_eximuser)
-  {
-  if ($parm_eximuser =~ /^\d+$/) { $parm_exim_uid = $parm_eximuser; }
-    else { $parm_exim_uid = getpwnam($parm_eximuser); }
-  }
-else
-  {
-  print "Unable to extract exim_user from binary.\n";
-  print "Check if Exim refused to run; if so, consider:\n";
-  print "  TRUSTED_CONFIG_LIST ALT_CONFIG_PREFIX WHITELIST_D_MACROS\n";
-  print "If debug permission denied, are you in the exim group?\n";
-  die "Failing to get information from binary.\n";
-  }
+if (not defined $parm_eximuser) {
+  die <<XXX, map { "|$_\n" } @eximinfo;
+Unable to extract exim_user from binary.
+Check if Exim refused to run; if so, consider:
+  TRUSTED_CONFIG_LIST ALT_CONFIG_PREFIX WHITELIST_D_MACROS
+If debug permission denied, are you in the exim group?
+Failing to get information from binary.
+Output from $eximinfo:
+XXX
+
+}
+
+if ($parm_eximuser =~ /^\d+$/) { $parm_exim_uid = $parm_eximuser; }
+else { $parm_exim_uid = getpwnam($parm_eximuser); }
 
 if (defined $parm_eximgroup)
   {
@@ -2796,7 +2822,7 @@ if (defined $parm_support{Content_Scanning})
     # This test for an active SpamAssassin is courtesy of John Jetmore.
     # The tests are hard coded to localhost:783, so no point in making
     # this test flexible like the clamav test until the test scripts are
-    # changed.  spamd doesn't have the nice PING/PONG protoccol that
+    # changed.  spamd doesn't have the nice PING/PONG protocol that
     # clamd does, but it does respond to errors in an informative manner,
     # so use that.
 
@@ -3349,6 +3375,8 @@ else
   print " OK\n";
   }
 
+tests_exit(-1, "Failed to unlink $log_summary_filename: $!")
+  if not unlink($log_summary_filename) and -e $log_summary_filename;
 
 ##################################################
 #        Create a list of available tests        #
@@ -3368,26 +3396,13 @@ print "Omitting \${dlfunc expansion tests (loadable module not present)\n"
 print "Omitting dbm tests (unable to copy exim_dbmbuild)\n"
   if $dbm_build_deleted;
 
-opendir(DIR, "scripts") || tests_exit(-1, "Failed to opendir(\"scripts\"): $!");
-@test_dirs = sort readdir(DIR);
-closedir(DIR);
-
-# Remove . and .. and CVS from the list.
 
-for ($i = 0; $i < @test_dirs; $i++)
-  {
-  my($d) = $test_dirs[$i];
-  if ($d eq "." || $d eq ".." || $d eq "CVS")
-    {
-    splice @test_dirs, $i, 1;
-    $i--;
-    }
-  }
+my @test_dirs = grep { not /^CVS$/ } map { basename $_ } glob 'scripts/*'
+  or die tests_exit(-1, "Failed to find test scripts in 'scripts/*`: $!");
 
 # Scan for relevant tests
 
-tests_exit(-1, "Failed to unlink $log_summary_filename") if !unlink($log_summary_filename);
-for ($i = 0; $i < @test_dirs; $i++)
+DIR: for ($i = 0; $i < @test_dirs; $i++)
   {
   my($testdir) = $test_dirs[$i];
   my($wantthis) = 1;
@@ -3397,19 +3412,19 @@ for ($i = 0; $i < @test_dirs; $i++)
   # Skip this directory if the first test is equal or greater than the first
   # test in the next directory.
 
-  next if ($i < @test_dirs - 1) &&
+  next DIR if ($i < @test_dirs - 1) &&
           ($test_start >= substr($test_dirs[$i+1], 0, 4));
 
   # No need to carry on if the end test is less than the first test in this
   # subdirectory.
 
-  last if $test_end < substr($testdir, 0, 4);
+  last DIR if $test_end < substr($testdir, 0, 4);
 
   # Check requirements, if any.
 
-  if (open(REQUIRES, "scripts/$testdir/REQUIRES"))
+  if (open(my $requires, "scripts/$testdir/REQUIRES"))
     {
-    while (<REQUIRES>)
+    while (<$requires>)
       {
       next if /^\s*$/;
       s/\s+$//;
@@ -3442,7 +3457,6 @@ for ($i = 0; $i < @test_dirs; $i++)
         tests_exit(-1, "Unknown line in \"scripts/$testdir/REQUIRES\": \"$_\"");
         }
       }
-    close(REQUIRES);
     }
   else
     {
@@ -3461,10 +3475,9 @@ for ($i = 0; $i < @test_dirs; $i++)
   # We want the tests from this subdirectory, provided they are in the
   # range that was selected.
 
-  opendir(SUBDIR, "scripts/$testdir") ||
-    tests_exit(-1, "Failed to opendir(\"scripts/$testdir\"): $!");
-  @testlist = sort readdir(SUBDIR);
-  close(SUBDIR);
+  @testlist = map { basename $_ } glob "scripts/$testdir/*";
+  tests_exit(-1, "Failed to read test scripts from `scripts/$testdir/*': $!")
+    if not @testlist;
 
   foreach $test (@testlist)
     {
@@ -3650,27 +3663,31 @@ closedir(DIR);
 if (not $force_continue) {
   # runtest needs to interact if we're not in continue
   # mode. It does so by communicate to /dev/tty
-  open(T, "/dev/tty") or tests_exit(-1, "Failed to open /dev/tty: $!");
+  open(T, '<', '/dev/tty') or tests_exit(-1, "Failed to open /dev/tty: $!");
+  print "\nPress RETURN to run the tests: ";
+  <T>;
 }
 
 
-print "\nPress RETURN to run the tests: ";
-$_ = $force_continue ? "c" : <T>;
-print "\n";
-
-$lasttestdir = '';
-
 foreach $test (@test_list)
   {
-  local($lineno) = 0;
-  local($commandno) = 0;
-  local($subtestno) = 0;
+  state $lasttestdir = '';
+
+  local $lineno     = 0;
+  local $commandno  = 0;
+  local $subtestno  = 0;
+  local $sortlog    = 0;
+
   (local $testno = $test) =~ s|.*/||;
-  local($sortlog) = 0;
 
-  my($gnutls) = 0;
-  my($docheck) = 1;
-  my($thistestdir) = substr($test, 0, -5);
+  # Leaving traces in the process table and in the environment
+  # gives us a chance to identify hanging processes (exim daemons)
+  local $0 = "[runtest $testno]";
+  local $ENV{EXIM_TEST_NUMBER} = $testno;
+
+  my $gnutls   = 0;
+  my $docheck  = 1;
+  my $thistestdir  = substr($test, 0, -5);
 
   $dynamic_socket->close() if $dynamic_socket;
 
@@ -3679,20 +3696,19 @@ foreach $test (@test_list)
     $gnutls = 0;
     if (-s "scripts/$thistestdir/REQUIRES")
       {
-      my($indent) = '';
+      my $indent = '';
       print "\n>>> The following tests require: ";
-      open(IN, "scripts/$thistestdir/REQUIRES") ||
-        tests_exit(-1, "Failed to open scripts/$thistestdir/REQUIRES: $1");
-      while (<IN>)
+      open(my $requires, '<', "scripts/$thistestdir/REQUIRES") ||
+        tests_exit(-1, "Failed to open scripts/$thistestdir/REQUIRES: $!");
+      while (<$requires>)
         {
         $gnutls = 1 if /^support GnuTLS/;
         print $indent, $_;
         $indent = ">>>                              ";
         }
-      close(IN);
       }
+      $lasttestdir = $thistestdir;
     }
-  $lasttestdir = $thistestdir;
 
   # Remove any debris in the spool directory and the test-mail directory
   # and also the files for collecting stdout and stderr. Then put back
@@ -3834,8 +3850,6 @@ foreach $test (@test_list)
     my($rc, $run_extra) = run_command($testno, \$subtestno, \$expectrc, \$commandname, $TEST_STATE);
     my($cmdrc) = $?;
 
-    $0 = "[runtest $testno]";
-
     if ($debug) {
       print ">> rc=$rc cmdrc=$cmdrc\n";
       if (defined $run_extra) {
@@ -3927,7 +3941,8 @@ foreach $test (@test_list)
       if ($? != 0)
         {
         if (($? & 0xff) == 0)
-          { printf("Server return code %d", $?/256); }
+          { printf("Server return code %d for test %d starting line %d", $?/256,
+               $testno, $subtest_startline); }
         elsif (($? & 0xff00) == 0)
           { printf("Server killed by signal %d", $? & 255); }
         else
@@ -3974,6 +3989,7 @@ foreach $test (@test_list)
 
   if ($docheck)
     {
+    sleep 1 if $slow;
     my $rc = check_output($TEST_STATE->{munge});
     log_test($log_summary_filename, $testno, 'P') if ($rc == 0);
     if ($rc < 2)
@@ -3993,7 +4009,7 @@ foreach $test (@test_list)
 #         Exit from the test script              #
 ##################################################
 
-tests_exit(-1, "No runnable tests selected") if @test_list == 0;
+tests_exit(-1, "No runnable tests selected") if not @test_list;
 tests_exit(0);
 
 # End of runtest script