Testsuite: Add doc for PORT_DYNAMIC
[exim.git] / test / runtest
index efcd3ed1caeaede17d88e314cf1d1fa7c59a3db9..bdba26fc9055421879a8ecd7cc2f440bdc1b61c0 100755 (executable)
 ###############################################################################
 
 #use strict;
+#use 5.010;
 use Errno;
 use FileHandle;
+use IO::Socket::INET;
 use Socket;
 use Time::Local;
 use Cwd;
@@ -38,6 +40,7 @@ $gnutls_dh_bits_normal = 2236;
 $cf = "bin/cf -exact";
 $cr = "\r";
 $debug = 0;
+$flavour = 'FOO';
 $force_continue = 0;
 $force_update = 0;
 $log_failed_filename = "failed-summary.log";
@@ -45,7 +48,7 @@ $more = "less -XF";
 $optargs = "";
 $save_output = 0;
 $server_opts = "";
-$flavour = 'FOO';
+$valgrind = 0;
 
 $have_ipv4 = 1;
 $have_ipv6 = 1;
@@ -79,15 +82,19 @@ $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
 
 # 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;
 
 ###############################################################################
 ###############################################################################
@@ -137,6 +144,7 @@ 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;
 }
 
 
@@ -348,7 +356,7 @@ $spid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 # that are specific to certain file types, though there are also some of those
 # inline too.
 
-while(<IN>)
+LINE: while(<IN>)
   {
 RESET_AFTER_EXTRA_LINE_READ:
   # Custom munges
@@ -426,7 +434,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 ========
@@ -652,6 +660,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.
 
@@ -668,6 +679,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
@@ -1003,6 +1018,29 @@ RESET_AFTER_EXTRA_LINE_READ:
       while (<IN>) { last if !/^\s/; }
       }
 
+    # remote port numbers vary
+    s/(Connection request from 127.0.0.1 port) \d{1,5}/$1 sssss/;
+
+    # Skip hosts_require_dane checks when the options
+    # are unset, because dane ain't always there.
+
+    next if /in\shosts_require_dane\?\sno\s\(option\sunset\)/x;
+
+    # SUPPORT_PROXY
+    next if /host in hosts_proxy\?/;
+
+    # Experimental_International
+    next if / in smtputf8_advertise_hosts\? no \(option unset\)/;
+
+    # Environment cleaning
+    next if /\w+ in keep_environment\? (yes|no)/;
+
+    # 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/;
+
     # When Exim is checking the size of directories for maildir, it uses
     # the check_dir_size() function to scan directories. Of course, the order
     # of the files that are obtained using readdir() varies from system to
@@ -1024,20 +1062,6 @@ RESET_AFTER_EXTRA_LINE_READ:
         @saved = ();
         }
 
-    # remote port numbers vary
-    s/(Connection request from 127.0.0.1 port) \d{1,5}/$1 sssss/;
-
-    # Skip hosts_require_dane checks when the options
-    # are unset, because dane ain't always there.
-
-    next if /in\shosts_require_dane\?\sno\s\(option\sunset\)/x;
-
-    # SUPPORT_PROXY
-    next if /host in hosts_proxy\?/;
-
-    # Experimental_International
-    next if / in smtputf8_advertise_hosts\? no \(option unset\)/;
-
       # Skip some lines that Exim puts out at the start of debugging output
       # because they will be different in different binaries.
 
@@ -1052,6 +1076,7 @@ RESET_AFTER_EXTRA_LINE_READ:
                 /^log selectors =/ ||
                 /^cwd=/ ||
                 /^Fixed never_users:/ ||
+               /^Configure owner:/ ||
                 /^Size of off_t:/
                 );
 
@@ -1410,6 +1435,10 @@ $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/'
+    },
   };
 
 
@@ -1685,6 +1714,8 @@ my($commandnameref) = $_[3];
 my($aux_info) = $_[4];
 my($yield) = 1;
 
+our %ENV = map { $_ => $ENV{$_} } grep { /^(?:USER|SHELL|PATH|TERM|EXIM_TEST_.*)$/ } keys %ENV;
+
 if (/^(\d+)\s*$/)                # Handle unusual return code
   {
   my($r) = $_[2];
@@ -1950,7 +1981,7 @@ if (/^sleep\s+(.*)$/)
 # Various Unix management commands are recognized
 
 if (/^(ln|ls|du|mkdir|mkfifo|touch|cp|cat)\s/ ||
-    /^sudo (rmdir|rm|chown|chmod)\s/)
+    /^sudo\s(rmdir|rm|mv|chown|chmod)\s/)
   {
   run_system("$_ >>test-stdout 2>>test-stderr");
   return 1;
@@ -1971,6 +2002,7 @@ if (/^(ln|ls|du|mkdir|mkfifo|touch|cp|cat)\s/ ||
 # command, triggered by $server_pid being non-zero. The server sends its output
 # to a different file. The variable $server_opts, if not empty, contains
 # options to disable IPv4 or IPv6 if necessary.
+# This works because "server" swallows its stdin before waiting for a connection.
 
 if (/^server\s+(.*)$/)
   {
@@ -2088,7 +2120,7 @@ if (/^client/ || /^(sudo\s+)?perl\b/)
 # not drop privilege when -C and -D options are present. To run the exim
 # command as root, we use sudo.
 
-elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+)?\s+(.*)$/)
+elsif (/^((?i:[A-Z\d_]+=\S+\s+)+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+)?\s+(.*)$/)
   {
   $args = $6;
   my($envset) = (defined $1)? $1      : "";
@@ -2135,8 +2167,7 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+)?\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")
@@ -2149,7 +2180,10 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+)?\s+(.*
 
   $args =~ s/(?:^|\s)-d\S*// if $optargs =~ /(?:^|\s)-d/;
 
-  $cmd = "$envset$sudo$parm_cwd/eximdir/exim$special$optargs " .
+  my $opt_valgrind = $valgrind ? "valgrind --leak-check=yes --suppressions=$parm_cwd/aux-fixed/valgrind.supp " : "";
+
+  $cmd = "$envset$sudo$opt_valgrind" .
+         "$parm_cwd/eximdir/exim$special$optargs " .
          "-DEXIM_PATH=$parm_cwd/eximdir/exim$special " .
          "-C $parm_cwd/test-config $args " .
          ">>test-stdout 2>>test-stderr";
@@ -2198,31 +2232,24 @@ elsif (/^([A-Z_]+=\S+\s+)?(\d+)?\s*(sudo(?:\s+-u\s+(\w+))?\s+)?exim(_\S+)?\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
@@ -2319,7 +2346,7 @@ return $yield;            # Ran command and waited
 ###############################################################################
 ###############################################################################
 
-# Here beginneth the Main Program ...
+# Here begins the Main Program ...
 
 ###############################################################################
 ###############################################################################
@@ -2332,9 +2359,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};
 };
 
@@ -2349,7 +2376,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;
 
 
 
@@ -2358,7 +2385,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";
   }
@@ -2404,6 +2431,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 "-VALGRIND")   { $valgrind = 1; next; }
     if ($arg =~ /^-FLAVOU?R$/) { $flavour = shift; next; }
     }
   $optargs .= " $arg";
@@ -2537,9 +2565,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);
@@ -2591,6 +2632,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";
 
@@ -2989,39 +3038,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
@@ -3030,7 +3066,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>";
@@ -3046,7 +3082,7 @@ else
   $parm_running{"IPv4"} = " ";
   }
 
-if ($parm_ipv6 eq "")
+if (not $parm_ipv6)
   {
   $have_ipv6 = 0;
   $parm_ipv6 = "<no IPv6 address found>";
@@ -3094,8 +3130,9 @@ if ($parm_ipv6 =~ /^[\da-f]/)
 # Find the host name, fully qualified.
 
 chomp($temp = `hostname`);
+die "'hostname' didn't return anything\n" unless defined $temp and length $temp;
 $parm_hostname = (gethostbyname($temp))[0];
-$parm_hostname = "no.host.name.found" if $parm_hostname eq "";
+$parm_hostname = "no.host.name.found" unless defined $parm_hostname and length $parm_hostname;
 print "Hostname is $parm_hostname\n";
 
 if ($parm_hostname !~ /\./)
@@ -3558,6 +3595,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;
@@ -3626,6 +3665,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);
@@ -3707,8 +3756,9 @@ foreach $test (@test_list)
 
     my($subtest_startline) = $lineno;
 
-    # Now run the command. The function returns 0 if exim was run and waited
-    # for, 1 if any other command was run and waited for, and 2 if a command
+    # Now run the command. The function returns 0 for an inline command,
+    # 1 if a non-exim command was run and waited for, 2 if an exim
+    # command was run and waited for, and 3 if a command
     # was run and not waited for (usually a daemon or server startup).
 
     my($commandname) = "";
@@ -3871,4 +3921,3 @@ tests_exit(-1, "No runnable tests selected") if @test_list == 0;
 tests_exit(0);
 
 # End of runtest script
-# vim: set sw=2 et :