#use strict;
use v5.10.1;
use warnings;
-use if $^V >= v5.19.11, experimental => 'smartmatch';
use Errno;
use FileHandle;
sub new_value {
my($oldid, $base, $sequence) = @_;
my($newid) = $cache{$oldid};
+print ">> replace $oldid -> $newid\n" if ($debug && defined $newid);
if (! defined $newid)
{
$newid = sprintf($base, $$sequence++);
+ print ">> new $oldid -> $newid\n" if $debug;
$cache{$oldid} = $newid;
}
return $newid;
LINE: while(<IN>)
{
RESET_AFTER_EXTRA_LINE_READ:
+ if ($munge_skip)
+ {
+ # Munging is a no-op, except for exim_msgdate specials.
+ # Useful when testing exim_msgdate so that
+ # we compare unmunged dates and message-ids.
+ s%^localhost \d+ from message-id != given number \d+ at \K/.+(?=/test/eximdir/exim_msgdate line 387.$)%DIR%;
+
+ print MUNGED;
+ next;
+ }
+
# Custom munges
if ($extra)
{
next if $extra =~ m%^/% && eval $extra;
eval $extra if $extra =~ m/^s/;
+ eval substr($extra, 1) if $extra =~ m/^R/;
}
# Check for "*** truncated ***"
# patchexim should have fixed this for us
#s/Exim \K\d+[._]\d+[\w_-]*/x.yz/i;
- # Replace Exim message ids by a unique series
+ # Replace Exim message ids by a unique series.
+ # Both old and new formats, with separate replace series, for now.
s/(\d[^\W_]{5}-[^\W_]{6}-[^\W_]{2})
- /new_value($1, "10Hm%s-0005vi-00", \$next_msgid)/egx;
+ /new_value($1, "10Hm%s-0005vi-00", \$next_msgid_old)/egx;
+ s/(\d[^\W_]{5}-[^\W_]{11}-[^\W_]{4})
+ /new_value($1, "10Hm%s-000000005vi-0000", \$next_msgid)/egx;
# The names of lock files appear in some error and debug messages
s/\.lock(\.[-\w]+)+(\.[\da-f]+){2}/.lock.test.ex.dddddddd.pppppppp/;
s/\bgid=\d+/gid=gggg/;
s/\begid=\d+/egid=gggg/;
s/\b(?:pid=|pid\s|PID:\s|Process\s|child\s)\K(\d+)/new_value($1, "p%s", \$next_pid)/gxe;
+ s/ Ci=\K(\d+)/new_value($1, "p%s", \$next_pid)/gxe;
s/\buid=\d+/uid=uuuu/;
s/\beuid=\d+/euid=uuuu/;
s/set_process_info:\s+\d+/set_process_info: pppp/;
# ======== IP error numbers and messages ========
# These vary between operating systems
- s/Can(no|')t assign requested address/Network Error/;
+ s/(?:Can(?:no|')t assign requested address|Address not available)/Netwk addr not available/;
s/Operation timed out/Connection timed out/;
s/Address family not supported by protocol family/Network Error/;
- s/Network( is)? unreachable/Network Error/;
+ s/Network(?: is)? unreachable/Network Error/;
s/Invalid argument/Network Error/;
s/\(\d+\): Network/(dd): Network/;
s/([\s,])S=\d+\b/$1S=sss/;
s/:S\d+\b/:Ssss/;
- s/^(\s*\d+m\s+)\d+(\s+[a-z0-9-]{16} <)/$1sss$2/i if $is_stdout;
+ s/^(\s*\d+[mhd]\s+)\d+(\s+(?:[a-z0-9-]{23}|[a-z0-9-]{18}) <)/TTT sss$2/i if $is_stdout;
s/\sSIZE=\d+\b/ SIZE=ssss/;
s/\ssize=\d+\b/ size=sss/ if $is_stderr;
s/old size = \d+\b/old size = sssss/;
# DMARC is not always supported by the build
next if /^dmarc_tld_file =/;
+ # timestamp in dmarc history file
+ s/received \K\d{10}$/1692480217/;
# ARC is not always supported by the build
next if /^arc_sign =/;
# gsasl library version may not support some methods
s/250-AUTH ANONYMOUS PLAIN SCRAM-SHA-1\K SCRAM-SHA-256//;
+
+ # mailq times change with when the run is done, vs. static-source spoolfiles
+ s/\s*\d*[hd](?= 317 (?:[-0-9A-Za-z]{23}|[-0-9A-Za-z]{16}) <nobody\@test.ex>)/DDd/;
+ # mailq sizes change with caller running the test
+ s/\s[01]m [34]\d\d(?= (?:[-0-9A-Za-z]{23}|[-0-9A-Za-z]{16}) <CALLER\@the.local.host.name>)/ 1m 396/;
+
+ # Not all builds include EXPERIMENTAL_DSN_INFO (1 of 2)
+ if (/^X-Exim-Diagnostic:/)
+ {
+ while (<IN>) {
+ last if (/^$/ || !/^\s/);
+ }
+ goto RESET_AFTER_EXTRA_LINE_READ;
+ }
}
# ======== stderr ========
}
next if / in limits_advertise_hosts?\? no \(matched "!\*"\)/;
+ # Experimental_XCLIENT
+ next if / in hosts_xclient?\? no \(option unset\)/;
+
# TCP Fast Open
next if /^(ppppp )?setsockopt FASTOPEN: Network Error/;
s/ \.\.\. >>> / ... /;
if (s/ non-TFO mode connection attempt to 224.0.0.0, 0 data\b$//) { chomp; $_ .= <IN>; }
s/Address family not supported by protocol family/Network Error/;
- s/Network is unreachable/Network Error/;
+ s/Network(?: is)? unreachable/Network Error/;
}
next if /^(ppppp |\d+ )?setsockopt FASTOPEN: Protocol not available$/;
s/^(Connecting to .* \.\.\. sending) \d+ (nonTFO early-data)$/$1 dd $2/;
next if / Berkeley DB error: /;
# CHUNKING: exact sizes depend on hostnames in headers
- s/(=>.* K C="250- \d)\d+ (byte chunk, total \d)\d+/$1nn $2nn/;
+ s/(=>.* K (?:DKIM=\S+ )?C="250- \d)\d+ (byte chunk, total \d)\d+/$1nn $2nn/;
# OpenSSL version variances
s/(TLS error on connection [^:]*: error:)[0-9A-F]{8}(:system library):(?:fopen|func\(4095\)|):(No such file or directory)$/$1xxxxxxxx$2:fopen:$3/;
- next if /TLS error \(SSL_read\): error:0A000126:SSL routines::unexpected eof while reading$/ ;
+ next if /TLS error \(SSL_read\): .*error:0A000126:SSL routines::unexpected eof while reading$/ ;
s/EVDATA: \K\(SSL_accept\): error:0A000126:SSL routines::unexpected eof while reading/SSL_accept: TCP connection closed by peer/;
s/(DANE attempt failed.*error:)[0-9A-F]{8}(:SSL routines:)(?:(?i)ssl3_get_server_certificate|tls_process_server_certificate|CONNECT_CR_CERT|)(?=:certificate verify failed$)/$1xxxxxxxx$2ssl3_get_server_certificate/;
s/(DKIM: validation error: )error:[0-9A-F]{8}:rsa routines:(?:(?i)int_rsa_verify|CRYPTO_internal):(?:bad signature|algorithm mismatch)$/$1Public key signature verification has failed./;
<IN>;
<IN>;
}
+
+ # Not all builds include EXPERIMENTAL_DSN_INFO (2 of 2)
+ if (/^X-Exim-Diagnostic:/)
+ {
+ while (<IN>) {
+ last if (/^$/ || !/^\s/);
+ }
+ goto RESET_AFTER_EXTRA_LINE_READ;
+ }
}
# ======== All files other than stderr ========
for (my $i = 0; $i < @munged; $i++)
{
- if ($munged[$i] =~ /^[-\d]{10}\s[:\d]{8}\s[-A-Za-z\d]{16}\s[-=*]>/)
+ if ($munged[$i] =~ /^[-\d]{10}\s[:\d]{8}(\.\d{3})?\s[-A-Za-z\d]{23}\s[-=*]>/)
{
my $j;
for ($j = $i + 1; $j < @munged; $j++)
{
last if $munged[$j] !~
- /^[-\d]{10}\s[:\d]{8}\s[-A-Za-z\d]{16}\s[-=*]>/;
+ /^[-\d]{10}\s[:\d]{8}(\.\d{3})?\s[-A-Za-z\d]{23}\s[-=*]>/;
}
@temp = splice(@munged, $i, $j - $i);
@temp = sort(@temp);
# Usable files are:
# paniclog, rejectlog, mainlog, stdout, stderr, msglog, mail
# Search strings starting with 's' do substitutions;
-# with '/' do line-skips.
+# with '/' do line-skips,
+# with 'R' run given code.
# Triggered by a scriptfile line "munge <name>"
##################################################
$munges =
},
'optional_dsn_info' =>
- { 'mail' => '/^(X-(Remote-MTA-(smtp-greeting|helo-response)|Exim-Diagnostic|(body|message)-linecount):|Remote-MTA: X-ip;)/'
+ { 'mail' => 'Rif (/^(X-(Remote-MTA-(smtp-greeting|helo-response)|Exim-Diagnostic|(body|message)-linecount):|Remote-MTA: X-ip;)/) {
+ while (1) {
+ $_ = <IN>;
+ next if /^ /;
+ goto RESET_AFTER_EXTRA_LINE_READ;
+ }
+ }'
},
'optional_config' =>
foreach $msglog (@msglogs)
{
next if ($msglog eq "." || $msglog eq ".." || $msglog eq "CVS");
+
($munged_msglog = $msglog) =~
s/((?:[^\W_]{6}-){2}[^\W_]{2})
- /new_value($1, "10Hm%s-0005vi-00", \$next_msgid)/egx;
+ /new_value($1, "10Hm%s-0005vi-00", \$next_msgid_old)/egx;
+
+ $munged_msglog =~
+ s/([^\W_]{6}-[^\W_]{11}-[^\W_]{4})
+ /new_value($1, "10Hm%s-000000005vi-0000", \$next_msgid)/egx;
+
$yield = max($yield, check_file("spool/msglog/$msglog", undef,
"test-msglog-munged", "msglog/$testno.$munged_msglog", 0,
$munge->{msglog}));
# Arguments: the current test number
# reference to the subtest number, holding previous value
# reference to the expected return code value
+# reference to flag for not-expected return value
# reference to where to put the command name (for messages)
# auxiliary information returned from a previous run
#
sub run_command{
my($testno) = $_[0];
my($subtestref) = $_[1];
-my($commandnameref) = $_[3];
-my($aux_info) = $_[4];
+my($commandnameref) = $_[4];
+my($aux_info) = $_[5];
my($yield) = 1;
our %ENV = map { $_ => $ENV{$_} } grep { /^(?:USER|SHELL|PATH|TERM|EXIM_TEST_.*)$/ } keys %ENV;
-if (/^(\d+)\s*$/) # Handle unusual return code
+if (/^(~)?(\d+)\s*(?:([A-Z]+)=(\S+))?$/) # Handle unusual return code
{
- my($r) = $_[2];
- $$r = $1 << 8;
+ my($r, $rn) = ($_[2], $_[3]);
+ $$r = $2 << 8;
+ $$rn = 1 if (defined $1);
+ $ENV{$3} = $4 if (defined $3);
$_ = <SCRIPT>;
return 4 if !defined $_; # Missing command
$lineno++;
}
+# The "exim_id_update" command runs exim_id_update on the current spool
+
+if (/^exim_id_update(\s+.*)?$/)
+ {
+ run_system("(sudo ./eximdir/exim_id_update" . ($1 || '') . " $parm_cwd/spool/input;" .
+ "echo exim_id_update exit code = \$?)" .
+ ">>test-stdout 2>>test-stderr");
+ return 1;
+ }
+
+
# The "gnutls" command makes a copy of saved GnuTLS parameter data in the
# spool directory, to save Exim from re-creating it each time.
# Various Unix management commands are recognized
if (/^(ln|ls|du|mkdir|mkfifo|touch|cp|cat)\s/ ||
- /^sudo\s(rmdir|rm|mv|chown|chmod)\s/)
+ /^sudo\s(mkdir|rmdir|rm|mv|cp|chown|chmod)\s/)
{
run_system("$_ >>test-stdout 2>>test-stderr");
return 1;
}
+if (/^cat2\s/)
+ {
+ s/^cat2/cat/;
+ run_system("$_ 2>&1 >test-stderr");
+ return 1;
+ }
if (/^client/ || /^(sudo\s+)?perl\b/)
{
+ if (defined($tls)) {
+ s/^client-anytls/client-ssl/ if ($tls eq 'openssl');
+ s/^client-anytls/client-gnutls/ if ($tls eq 'gnutls');
+ }
s"client"./bin/client";
$cmd = "$_ >>test-stdout 2>>test-stderr";
}
if (defined $queuespec)
{
- @listcmd = ("$parm_cwd/eximdir/exim", '-bp',
+ @listcmd = ("$parm_cwd/$exim_server", '-bp',
$queuespec,
- "-DEXIM_PATH=$parm_cwd/eximdir/exim",
+ "-DEXIM_PATH=$parm_cwd$exim_server",
-C => "$parm_cwd/test-config");
}
else
{
- @listcmd = ("$parm_cwd/eximdir/exim", '-bp',
- "-DEXIM_PATH=$parm_cwd/eximdir/exim",
+ @listcmd = ("$parm_cwd/$exim_server", '-bp',
+ "-DEXIM_PATH=$parm_cwd/$exim_server",
-C => "$parm_cwd/test-config");
}
print ">> Getting queue list from:\n>> @listcmd\n" if $debug;
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 " .
+ $cmd = "$envset$sudo$opt_valgrind";
+
+ if ($special ne '') {
+ $cmd .= "$parm_cwd/eximdir/exim$special$optargs " .
+ "-DEXIM_PATH=$parm_cwd/eximdir/exim$special ";
+ }
+ elsif ($args =~ /(^|\s)-DSERVER=server\s/) {
+ $cmd .= "$parm_cwd/$exim_server$optargs " .
+ "-DEXIM_PATH=$parm_cwd/$exim_server ";
+ }
+ else {
+ $cmd .= "$parm_cwd/$exim_client$optargs " .
+ "-DEXIM_PATH=$parm_cwd/$exim_client ";
+ }
+
+ $cmd .= "-C $parm_cwd/test-config $args " .
">>test-stdout 2>>test-stderr";
+
# If the command is starting an Exim daemon, we run it in the same
# way as the "server" command above, that is, we don't want to wait
# for the process to finish. That happens when "killdaemon" is obeyed later
}
}
+sub check_running_dovecot
+{
+system('dovecot --version >/dev/null');
+if ($? == 0)
+ {
+ print "Dovecot appears to be available\n";
+ $parm_running{dovecot} = ' ';
+ }
+else
+ {
+ print "Dovecot not found\n";
+ }
+}
+
###############################################################################
'ipv6!' => \$have_ipv6,
'keep' => \$save_output,
'slow' => \$slow,
+ 'tls=s' => \my $tls,
'valgrind' => \$valgrind,
'range=s{2}' => \my @range_wanted,
'test=i@' => \my @tests_wanted,
print "Exim binary is `$parm_exim'\n" if defined $parm_exim;
+my %wanted;
my @wanted = sort numerically uniq
@tests_wanted ? @tests_wanted : (),
@range_wanted ? $range_wanted[0] .. $range_wanted[1] : (),
0+$ARGV[0]..0+$ARGV[1] # add 0 to cope with test numbers starting with zero
: ();
@wanted = 1..TEST_TOP if not @wanted;
+map { $wanted{sprintf("%04d",$_)}= $_; } @wanted;
##################################################
# Check for sudo access to root #
check_running_clamav();
}
check_running_redis();
+check_running_dovecot();
##################################################
# Test for the basic requirements #
die "** Unable to make patched exim: $!\n"
if (system("sudo ./patchexim $parm_exim") != 0);
+# If TLS-library-specific binaries have been made, grab them too
+
+$suff = 'openssl';
+$f = $parm_exim . '_' . $suff;
+if (-f $f) {
+ $exim_openssl = "eximdir/exim_$suff";
+ die "** Unable to make patched exim: $!\n"
+ if (system("sudo ./patchexim -o $exim_openssl $f") != 0);
+ }
+$suff = 'gnutls';
+$f = $parm_exim . '_' . $suff;
+if (-f $f) {
+ $exim_gnutls = "eximdir/exim_$suff";
+ die "** Unable to make patched exim: $!\n"
+ if (system("sudo ./patchexim -o $exim_gnutls $f") != 0);
+ }
+
+if (defined($tls))
+ {
+ die "** Need both $exim_openssl and $exim_gnutls for cross-library teting\n"
+ if ( !defined($exim_openssl) || !defined($exim_gnutls) );
+ if ($tls eq 'openssl')
+ {
+ $exim_client = $exim_openssl;
+ $exim_server = $exim_gnutls;
+ }
+ elsif ($tls eq 'gnutls')
+ {
+ $exim_client = $exim_gnutls;
+ $exim_server = $exim_openssl;
+ }
+ else
+ { die "** need eother openssl or gnutls speified as the client for cross-library testing, saw $tls\n"; }
+ }
+else
+ { $exim_client = $exim_server = 'eximdir/exim'; }
+print ">> \$exim_client <$exim_client>\n";;
+print ">> \$exim_server <$exim_server>\n";;
+
# From this point on, exits from the program must go via the subroutine
# tests_exit(), so that suitable cleaning up can be done when required.
# Arrange to catch interrupting signals, to assist with this.
$dbm_build_deleted = 1;
}
-foreach my $tool (qw(exim_dumpdb exim_lock exinext exigrep eximstats exiqgrep)) {
+foreach my $tool (qw(exim_dumpdb exim_lock exinext exigrep eximstats exiqgrep exim_msgdate exim_id_update)) {
cp("$parm_exim_dir/$tool" => "eximdir/$tool")
or tests_exit(-1, "Failed to make a copy of $tool: $!");
}
# Collect some version information
print '-' x 78, "\n";
print "Perl version for runtest: $]\n";
-foreach (map { "./eximdir/$_" } qw(exigrep exinext eximstats exiqgrep)) {
+foreach (map { "./eximdir/$_" } qw(exigrep exinext eximstats exiqgrep exim_msgdate)) {
# fold (or unfold?) multiline output into a one-liner
print join(', ', map { chomp; $_ } `$_ --version`), "\n";
}
{
if (!defined $parm_malware{$1}) { $wantthis = 0; last; }
}
- elsif (/^feature (.*)$/)
+ elsif (/^(not )?feature (.*)$/)
{
# move to a subroutine?
- my $eximinfo = "$parm_exim -C $parm_cwd/test-config -DDIR=$parm_cwd -bP macro $1";
+ my $eximinfo = "$parm_exim -C $parm_cwd/test-config -DDIR=$parm_cwd -bP macro $2";
open (IN, "$parm_cwd/confs/0000") ||
tests_exit(-1, "Couldn't open $parm_cwd/confs/0000: $!\n");
close(OUT);
system($eximinfo . " >/dev/null 2>&1");
- if ($? != 0) {
- unlink("$parm_cwd/test-config");
+ if (!defined $1 && $? != 0 || defined $1 && $? == 0) {
$wantthis = 0;
- $_ = "feature $1";
+ unlink("$parm_cwd/test-config");
+ $_ = $1 || "" . "feature $2";
last;
}
unlink("$parm_cwd/test-config");
# We want the tests from this subdirectory, provided they are in the
# range that was selected.
- @testlist = grep { $_ ~~ @wanted } grep { /^\d+(?:\.\d+)?$/ } map { basename $_ } glob "scripts/$testdir/*";
+ undef @testlist;
+ map { push @testlist, $_ if exists $wanted{$_} } grep { /^\d+(?:\.\d+)?$/ } map { basename $_ } glob "scripts/$testdir/*";
+
tests_exit(-1, "Failed to read test scripts from `scripts/$testdir/*': $!")
if not @testlist;
# set up the initial sequence strings.
undef %cache;
+ $next_msgid_old = "aX";
$next_msgid = "aX";
$next_pid = 1234;
$next_port = 1111;
$message_skip = 0;
$msglog_skip = 0;
+ $munge_skip = 0;
$stderr_skip = 0;
$stdout_skip = 0;
$rmfiltertest = 0;
{
if (/^no_message_check/) { $message_skip = 1; next; }
if (/^no_msglog_check/) { $msglog_skip = 1; next; }
+ if (/^no_munge/) { $munge_skip = 1; next; }
if (/^no_stderr_check/) { $stderr_skip = 1; next; }
if (/^no_stdout_check/) { $stdout_skip = 1; next; }
if (/^rmfiltertest/) { $rmfiltertest = 1; next; }
# set above, but doesn't hurt to leave them here.
if (/^no_message_check/) { $message_skip = 1; next; }
if (/^no_msglog_check/) { $msglog_skip = 1; next; }
+ if (/^no_munge/) { $munge_skip = 1; next; }
if (/^no_stderr_check/) { $stderr_skip = 1; next; }
if (/^no_stdout_check/) { $stdout_skip = 1; next; }
if (/^rmfiltertest/) { $rmfiltertest = 1; next; }
# was run and not waited for (usually a daemon or server startup).
my($commandname) = '';
- my($expectrc) = 0;
- my($rc, $run_extra) = run_command($testno, \$subtestno, \$expectrc, \$commandname, $TEST_STATE);
+ my($expectrc, $expect_not) = (0, 0);
+ my($rc, $run_extra) = run_command($testno, \$subtestno, \$expectrc, \$expect_not, \$commandname, $TEST_STATE);
my($cmdrc) = $?;
if ($debug) {
# We ran and waited for a command. Check for the expected result unless
# it died.
- if ($cmdrc != $expectrc && !$sigpipehappened)
+ if (!$sigpipehappened && ($expect_not ? ($cmdrc == $expectrc) : ($cmdrc != $expectrc)))
{
printf("** Command $commandno (\"$commandname\", starting at line $subtest_startline)\n");
if (($cmdrc & 0xff) == 0)
{
- printf("** Return code %d (expected %d)", $cmdrc/256, $expectrc/256);
+ if ($expect_not)
+ { printf("** Return code %d (expected anything but that)", $cmdrc/256); }
+ else
+ { printf("** Return code %d (expected %d)", $cmdrc/256, $expectrc/256); }
}
elsif (($cmdrc & 0xff00) == 0)
{ printf("** Killed by signal %d", $cmdrc & 255); }