Testsuite: find a group name if 'mail' is not available.
[users/heiko/exim.git] / test / runtest
index fb93d8de37bd30c7d4c78f6f298f0b506590c318..78cd051d38da314f4b1ca3ff41681dc1ce2c6b69 100755 (executable)
@@ -1,4 +1,6 @@
-#! /usr/bin/perl -w
+#! /usr/bin/env perl
+# We use env, because in some environments of our build farm
+# the Perl 5.010 interpreter is only reachable via $PATH
 
 ###############################################################################
 # This is the controlling script for the "new" test suite for Exim. It should #
 ###############################################################################
 
 #use strict;
+use 5.010;
+use warnings;
+
 use Errno;
 use FileHandle;
+use IO::Socket::INET;
 use Socket;
 use Time::Local;
 use Cwd;
 use File::Basename;
+use FindBin qw'$Bin';
+
+use lib "$Bin/lib";
+use Exim::Runtest;
+
 use if $ENV{DEBUG} && $ENV{DEBUG} =~ /\bruntest\b/ => ('Smart::Comments' => '####');
 
 
@@ -80,15 +91,23 @@ $parm_port_d = 1225;         # Used for the Exim daemon
 $parm_port_d2 = 1226;        # Additional for daemon
 $parm_port_d3 = 1227;        # Additional for daemon
 $parm_port_d4 = 1228;        # Additional for daemon
+my $dynamic_socket;          # allocated later for PORT_DYNAMIC
+
+# Find a suiteable group name for test (currently only 0001
+# uses a group name. A numeric group id would do
+my $parm_mailgroup = Exim::Runtest::mailgroup('mail');
 
 # Manually set locale
-$ENV{'LC_ALL'} = 'C';
+$ENV{LC_ALL} = 'C';
 
 # In some environments USER does not exists, but we
 # need it for some test(s)
 $ENV{USER} = getpwuid($>)
   if not exists $ENV{USER};
 
+my ($parm_configure_owner, $parm_configure_group);
+my ($parm_ipv4, $parm_ipv6);
+my $parm_hostname;
 
 ###############################################################################
 ###############################################################################
@@ -138,6 +157,8 @@ s?\bPORT_S\b?$parm_port_s?g;
 s?\bTESTNUM\b?$_[0]?g;
 s?(\b|_)V4NET([\._])?$1$parm_ipv4_test_net$2?g;
 s?\bV6NET:?$parm_ipv6_test_net:?g;
+s?\bPORT_DYNAMIC\b?$dynamic_socket->sockport()?eg;
+s?\bMAILGROUP\b?$parm_mailgroup?g;
 }
 
 
@@ -427,7 +448,7 @@ RESET_AFTER_EXTRA_LINE_READ:
 
   # File descriptor numbers may vary
   s/^writing data block fd=\d+/writing data block fd=dddd/;
-  s/running as transport filter: write=\d+ read=\d+/running as transport filter: write=dddd read=dddd/;
+  s/(running as transport filter:) fd_write=\d+ fd_read=\d+/$1 fd_write=dddd fd_read=dddd/;
 
 
   # ======== Dumpdb output ========
@@ -653,6 +674,9 @@ RESET_AFTER_EXTRA_LINE_READ:
   s/waiting for children of \d+/waiting for children of pppp/;
   s/waiting for (\S+) \(\d+\)/waiting for $1 (pppp)/;
 
+  # The spool header file name varies with PID
+  s%^(Writing spool header file: .*/hdr).[0-9]{1,5}%$1.pppp%;
+
   # ======== Port numbers ========
   # Incoming port numbers may vary, but not in daemon startup line.
 
@@ -669,6 +693,10 @@ RESET_AFTER_EXTRA_LINE_READ:
   # Port in host address in spool file output from -Mvh
   s/^-host_address (.*)\.\d+/-host_address $1.9999/;
 
+  if ($dynamic_socket and $dynamic_socket->opened and my $port = $dynamic_socket->sockport) {
+    s/^Connecting to 127\.0\.0\.1 port \K$port/<dynamic port>/;
+  }
+
 
   # ======== Local IP addresses ========
   # The amount of space between "host" and the address in verification output
@@ -1021,8 +1049,35 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Environment cleaning
     next if /\w+ in keep_environment\? (yes|no)/;
 
-    # The spool header file name varies with PID
-    s%^(Writing spool header file: .*/hdr).[0-9]{1,5}%$1.pppp%;
+    # Sizes vary with test hostname
+    s/^cmd buf flush \d+ bytes$/cmd buf flush ddd bytes/;
+
+    # Spool filesystem free space changes on different systems.
+    s/^((?:spool|log) directory space =) -?\d+K (inodes =)\s*-?\d+/$1 nnnnnK $2 nnnnn/;
+
+    # Non-TLS builds have different expansions for received_header_text
+    if (s/(with \$received_protocol)\}\} \$\{if def:tls_cipher \{\(\$tls_cipher\)\n$/$1/)
+      {
+      $_ .= <IN>;
+      s/\s+\}\}(?=\(Exim )/\}\} /;
+      }
+    if (/^  condition: def:tls_cipher$/)
+      {
+      <IN>; <IN>; <IN>; <IN>; <IN>; <IN>;
+      <IN>; <IN>; <IN>; <IN>; <IN>; next;
+      }
+
+    # Not all platforms build with DKIM enabled
+    next if /^PDKIM >> Body data for hash, canonicalized/;
+
+    # Not all platforms support TCP Fast Open, and the compile omits the check
+    if (s/\S+ in hosts_try_fastopen\? no \(option unset\)\n$//)
+      {
+      $_ .= <IN>;
+      s/ \.\.\. >>> / ... /;
+      }
+
+    next if /^(ppppp )?setsockopt FASTOPEN: Protocol not available$/;
 
     # When Exim is checking the size of directories for maildir, it uses
     # the check_dir_size() function to scan directories. Of course, the order
@@ -1059,6 +1114,7 @@ RESET_AFTER_EXTRA_LINE_READ:
                 /^log selectors =/ ||
                 /^cwd=/ ||
                 /^Fixed never_users:/ ||
+               /^Configure owner:/ ||
                 /^Size of off_t:/
                 );
 
@@ -1417,6 +1473,13 @@ $munges =
     { 'mainlog'   => 's/^(.* SMTP protocol synchronization error .* next input=.{8}).*$/$1<suppressed>/',
       'rejectlog' => 's/^(.* SMTP protocol synchronization error .* next input=.{8}).*$/$1<suppressed>/'},
 
+    'debuglog_stdout' =>
+    { 'stdout' => 's/^\d\d:\d\d:\d\d\s+\d+ //;
+                  s/Process \d+ is ready for new message/Process pppp is ready for new message/'
+    },
+
+    'timeout_errno' =>         # actual errno differs Solaris vs. Linux
+    { 'mainlog' => 's/(host deferral .* errno) <\d+> /$1 <EEE> /' },
   };
 
 
@@ -1959,7 +2022,7 @@ if (/^sleep\s+(.*)$/)
 # Various Unix management commands are recognized
 
 if (/^(ln|ls|du|mkdir|mkfifo|touch|cp|cat)\s/ ||
-    /^sudo\s(rmdir|rm|chown|chmod)\s/)
+    /^sudo\s(rmdir|rm|mv|chown|chmod)\s/)
   {
   run_system("$_ >>test-stdout 2>>test-stderr");
   return 1;
@@ -2145,8 +2208,7 @@ elsif (/^((?i:[A-Z\d_]+=\S+\s+)+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+
 
     # Done backwards just in case there are more than 9
 
-    my($i);
-    for ($i = @msglist; $i > 0; $i--) { $args =~ s/\$msg$i/$msglist[$i-1]/g; }
+    for (my $i = @msglist; $i > 0; $i--) { $args =~ s/\$msg$i/$msglist[$i-1]/g; }
     if ( $args =~ /\$msg\d/ )
       {
       tests_exit(-1, "Not enough messages in spool, for test $testno line $lineno\n")
@@ -2211,31 +2273,24 @@ elsif (/^((?i:[A-Z\d_]+=\S+\s+)+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+
     }
   elsif ($cmd =~ /\s-DSERVER=wait:(\d+)\s/)
     {
+
+    # The port and the $dynamic_socket was already allocated while parsing the
+    # script file, where -DSERVER=wait:PORT_DYNAMIC was encountered.
+
     my $listen_port = $1;
-    my $waitmode_sock = new FileHandle;
     if ($debug) { printf ">> wait-mode daemon: $cmd\n"; }
     run_system("sudo mkdir spool/log 2>/dev/null");
     run_system("sudo chown $parm_eximuser:$parm_eximgroup spool/log");
 
-    my ($s_ip,$s_port) = ('127.0.0.1', $listen_port);
-    my $sin = sockaddr_in($s_port, inet_aton($s_ip))
-        or die "** Failed packing $s_ip:$s_port\n";
-    socket($waitmode_sock, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
-        or die "** Unable to open socket $s_ip:$s_port: $!\n";
-    setsockopt($waitmode_sock, SOL_SOCKET, SO_REUSEADDR, 1)
-        or die "** Unable to setsockopt(SO_REUSEADDR): $!\n";
-    bind($waitmode_sock, $sin)
-        or die "** Unable to bind socket ($s_port): $!\n";
-    listen($waitmode_sock, 5);
     my $pid = fork();
     if (not defined $pid) { die "** fork failed: $!\n" }
     if (not $pid) {
       close(STDIN);
-      open(STDIN, "<&", $waitmode_sock) or die "** dup sock to stdin failed: $!\n";
-      close($waitmode_sock);
+      open(STDIN, '<&', $dynamic_socket) or die "** dup sock to stdin failed: $!\n";
+      close($dynamic_socket);
       print "[$$]>> ${cmd}-server\n" if ($debug);
       exec "exec ${cmd}-server";
-      exit(1);
+      die "Can't exec ${cmd}-server: $!\n";
     }
     while (<SCRIPT>) { $lineno++; last if /^\*{4}\s*$/; }   # Ignore any input
     select(undef, undef, undef, 0.3);             # Let the daemon get going
@@ -2332,7 +2387,7 @@ return $yield;            # Ran command and waited
 ###############################################################################
 ###############################################################################
 
-# Here beginneth the Main Program ...
+# Here begins the Main Program ...
 
 ###############################################################################
 ###############################################################################
@@ -2345,9 +2400,9 @@ print "Exim tester $testversion\n";
 # we map all (.../bin) to (.../sbin:.../bin)
 $ENV{PATH} = do {
   my %seen = map { $_, 1 } split /:/, $ENV{PATH};
-  join ':' => map { m{(.*)/bin$} 
-                ? ( $seen{"$1/sbin"} ? () : ("$1/sbin"), $_) 
-                : ($_) } 
+  join ':' => map { m{(.*)/bin$}
+                ? ( $seen{"$1/sbin"} ? () : ("$1/sbin"), $_)
+                : ($_) }
       split /:/, $ENV{PATH};
 };
 
@@ -2362,7 +2417,7 @@ umask 022;
 #       Check for the "less" command             #
 ##################################################
 
-$more = "more" if system("which less >/dev/null 2>&1") != 0;
+$more = 'more' if system('which less >/dev/null 2>&1') != 0;
 
 
 
@@ -2371,7 +2426,7 @@ $more = "more" if system("which less >/dev/null 2>&1") != 0;
 ##################################################
 
 print "You need to have sudo access to root to run these tests. Checking ...\n";
-if (system("sudo date >/dev/null") != 0)
+if (system('sudo true >/dev/null') != 0)
   {
   die "** Test for sudo failed: testing abandoned.\n";
   }
@@ -2551,9 +2606,22 @@ open(EXIMINFO, "$parm_exim -d -C $parm_cwd/test-config -DDIR=$parm_cwd " .
   die "** Cannot run $parm_exim: $!\n";
 while(<EXIMINFO>)
   {
+  if (my ($version) = /^Exim version (\S+)/) {
+    my $git = `git describe --dirty=-XX --match 'exim-4*'`;
+    if (defined $git and $? == 0) {
+      chomp $git;
+      $version =~ s/^\d+\K\./_/;
+      $git =~ s/^exim-//i;
+      $git =~ s/.*-\Kg([[:xdigit:]]+(?:-XX)?)/$1/;
+      print "\n*** Version mismatch (Exim: $version vs. GIT: $git). ***\n\n"
+        if not $version eq $git;
+    }
+  }
   $parm_eximuser = $1 if /^exim_user = (.*)$/;
   $parm_eximgroup = $1 if /^exim_group = (.*)$/;
   $parm_trusted_config_list = $1 if /^TRUSTED_CONFIG_LIST:.*?"(.*?)"$/;
+  ($parm_configure_owner, $parm_configure_group) = ($1, $2)
+       if /^Configure owner:\s*(\d+):(\d+)/;
   print "$_" if /wrong owner/;
   }
 close(EXIMINFO);
@@ -2568,6 +2636,7 @@ 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";
   }
 
@@ -2605,6 +2674,14 @@ else
   die "Unable to check the TRUSTED_CONFIG_LIST, seems to be empty?\n";
   }
 
+die "CONFIGURE_OWNER ($parm_configure_owner) does not match the user invoking $0 ($>)\n"
+       if $parm_configure_owner != $>;
+
+die "CONFIGURE_GROUP ($parm_configure_group) does not match the group invoking $0 ($))\n"
+       if 0020 & (stat "$parm_cwd/test-config")[2]
+       and $parm_configure_group != $);
+
+
 open(EXIMINFO, "$parm_exim -d-all+transport -bV -C $parm_cwd/test-config -DDIR=$parm_cwd |") ||
   die "** Cannot run $parm_exim: $!\n";
 
@@ -3003,39 +3080,26 @@ else
 # Find this host's IP addresses - there may be many, of course, but we keep
 # one of each type (IPv4 and IPv6).
 
-$parm_ipv4 = "";
-$parm_ipv6 = "";
-
-$local_ipv4 = "";
-$local_ipv6 = "";
-
-open(IFCONFIG, "ifconfig -a|") || die "** Cannot run \"ifconfig\": $!\n";
-while (($parm_ipv4 eq "" || $parm_ipv6 eq "") && ($_ = <IFCONFIG>))
+open(IFCONFIG, '-|', (grep { -x "$_/ip" } split /:/, $ENV{PATH}) ? 'ip address' : 'ifconfig -a')
+  or die "** Cannot run 'ip address' or 'ifconfig -a'\n";
+while (not ($parm_ipv4 and $parm_ipv6) and defined($_ = <IFCONFIG>))
   {
-  my($ip);
-  if ($parm_ipv4 eq "" &&
-      $_ =~ /^\s*inet(?:\saddr)?:?\s?(\d+\.\d+\.\d+\.\d+)\s/i)
+  if (not $parm_ipv4 and /^\s*inet(?:\saddr)?:?\s?(\d+\.\d+\.\d+\.\d+)(?:\/\d+)?\s/i)
     {
-    $ip = $1;
-    next if ($ip =~ /^127\./ || $ip =~ /^10\./);
-    $parm_ipv4 = $ip;
+    next if $1 =~ /^(?:127|10)\./;
+    $parm_ipv4 = $1;
     }
 
-  if ($parm_ipv6 eq "" &&
-      $_ =~ /^\s*inet6(?:\saddr)?:?\s?([abcdef\d:]+)/i)
+  if (not $parm_ipv6 and /^\s*inet6(?:\saddr)?:?\s?([abcdef\d:]+)(?:\/\d+)/i)
     {
-    $ip = $1;
-    next if ($ip eq "::1" || $ip =~ /^fe80/i);
-    $parm_ipv6 = $ip;
+    next if $1 eq '::1' or $1 =~ /^fe80/i;
+    $parm_ipv6 = $1;
     }
   }
 close(IFCONFIG);
 
 # Use private IP addresses if there are no public ones.
 
-$parm_ipv4 = $local_ipv4 if ($parm_ipv4 eq "");
-$parm_ipv6 = $local_ipv6 if ($parm_ipv6 eq "");
-
 # If either type of IP address is missing, we need to set the value to
 # something other than empty, because that wrecks the substitutions. The value
 # is reflected, so use a meaningful string. Set appropriate options for the
@@ -3044,7 +3108,7 @@ $parm_ipv6 = $local_ipv6 if ($parm_ipv6 eq "");
 # of IPV4 or IPv6 can be simulated by command options, which force $have_ipv4
 # and $have_ipv6 false.
 
-if ($parm_ipv4 eq "")
+if (not $parm_ipv4)
   {
   $have_ipv4 = 0;
   $parm_ipv4 = "<no IPv4 address found>";
@@ -3060,7 +3124,7 @@ else
   $parm_running{"IPv4"} = " ";
   }
 
-if ($parm_ipv6 eq "")
+if (not $parm_ipv6)
   {
   $have_ipv6 = 0;
   $parm_ipv6 = "<no IPv6 address found>";
@@ -3108,8 +3172,16 @@ if ($parm_ipv6 =~ /^[\da-f]/)
 # Find the host name, fully qualified.
 
 chomp($temp = `hostname`);
-$parm_hostname = (gethostbyname($temp))[0];
-$parm_hostname = "no.host.name.found" if $parm_hostname eq "";
+die "'hostname' didn't return anything\n" unless defined $temp and length $temp;
+if ($temp =~ /\./)
+  {
+  $parm_hostname = $temp;
+  }
+else
+  {
+  $parm_hostname = (gethostbyname($temp))[0];
+  $parm_hostname = "no.host.name.found" unless defined $parm_hostname and length $parm_hostname;
+  }
 print "Hostname is $parm_hostname\n";
 
 if ($parm_hostname !~ /\./)
@@ -3552,7 +3624,12 @@ closedir(DIR);
 # contains ****. We open input from the terminal so that we can read responses
 # to prompts.
 
-open(T, "/dev/tty") || tests_exit(-1, "Failed to open /dev/tty: $!");
+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: $!");
+}
+
 
 print "\nPress RETURN to run the tests: ";
 $_ = $force_continue ? "c" : <T>;
@@ -3572,6 +3649,8 @@ foreach $test (@test_list)
   my($docheck) = 1;
   my($thistestdir) = substr($test, 0, -5);
 
+  $dynamic_socket->close() if $dynamic_socket;
+
   if ($lasttestdir ne $thistestdir)
     {
     $gnutls = 0;
@@ -3640,6 +3719,16 @@ foreach $test (@test_list)
     if (/^no_stdout_check/)  { $stdout_skip = 1; next; }
     if (/^rmfiltertest/)     { $rmfiltertest = 1; next; }
     if (/^sortlog/)          { $sortlog = 1; next; }
+    if (/\bPORT_DYNAMIC\b/)  {
+      for (my $port = 1024; $port < 65000; $port++) {
+        $dynamic_socket = IO::Socket::INET->new(
+          LocalHost => '127.0.0.1',
+          LocalPort => $port,
+          Listen => 10,
+          ReuseAddr => 1,
+        ) and last;
+      }
+      }
     }
   # Reset to beginning of file for per test interpreting/processing
   seek(SCRIPT, 0, 0);
@@ -3886,4 +3975,3 @@ tests_exit(-1, "No runnable tests selected") if @test_list == 0;
 tests_exit(0);
 
 # End of runtest script
-# vim: set sw=2 et :