diff --git a/tests/globalconfig.pm b/tests/globalconfig.pm index 9287e2b0d..e44781f95 100644 --- a/tests/globalconfig.pm +++ b/tests/globalconfig.pm @@ -85,7 +85,8 @@ our $CURLVERSION=""; # curl's reported version number our $pwd = getcwd(); # current working directory our $srcdir = $ENV{'srcdir'} || '.'; # root of the test source code our $perl="perl -I$srcdir"; # invoke perl like this -our $LOGDIR="log"; # root of the log directory +our $LOGDIR="log"; # root of the log directory; this will be different for + # each runner in multiprocess mode our $LIBDIR="./libtest"; our $TESTDIR="$srcdir/data"; our $CURL="../src/curl".exe_ext('TOOL'); # what curl binary to run on the tests diff --git a/tests/runner.pm b/tests/runner.pm index 71e20ce58..8ea50de89 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -22,7 +22,15 @@ # ########################################################################### -# This module contains entry points to run a single test +# This module contains entry points to run a single test. runner_init +# determines whether they will run in a separate process or in the process of +# the caller. The relevant interface is asynchronous so it will work in either +# case. Program arguments are marshalled and then written to the end of a pipe +# (in controlleripccall) which is later read from and the arguments +# unmarshalled (in ipcrecv) before the desired function is called normally. +# The function return values are then marshalled and written into another pipe +# (again in ipcrecv) when is later read from and unmarshalled (in runnerar) +# before being returned to the caller. package runner; @@ -36,6 +44,7 @@ BEGIN { our @EXPORT = qw( checktestcmd prepro + readtestkeywords restore_test_env runner_init runnerac_clearlocks @@ -59,7 +68,6 @@ BEGIN { # these are for debugging only our @EXPORT_OK = qw( - readtestkeywords singletest_preprocess ); } @@ -99,6 +107,7 @@ use testutil qw( ####################################################################### # Global variables set elsewhere but used only by this package +# These may only be set *before* runner_init is called our $DBGCURL=$CURL; #"../src/.libs/curl"; # alternative for debugging our $valgrind_logfile="--log-file"; # the option name for valgrind >=3 our $valgrind_tool="--tool=memcheck"; @@ -121,7 +130,8 @@ my $controllerw; # pipe that controller writes to my $runnerr; # pipe that runner reads from my $runnerw; # pipe that runner writes to my $controllerr; # pipe that controller reads from - +my $multiprocess; # nonzero with a separate test runner process +my $onerunnerid; # a single runner ID # redirected stdout/stderr to these files sub stdoutfilename { @@ -140,12 +150,9 @@ sub stderrfilename { # runnerac_* functions # Called by controller sub runner_init { - my ($logdir)=@_; + my ($logdir, $jobs)=@_; - # Set this directory as ours - # TODO: This will need to be uncommented once there are multiple runners - #$LOGDIR = $logdir; - mkdir("$LOGDIR/$PIDDIR", 0777); + $multiprocess = !!$jobs; # enable memory debugging if curl is compiled with it $ENV{'CURL_MEMDEBUG'} = "$LOGDIR/$MEMDUMP"; @@ -161,8 +168,58 @@ sub runner_init { pipe $runnerr, $controllerw; pipe $controllerr, $runnerw; - # There is only one runner right now - return "singleton"; + if($multiprocess) { + # Create a separate process in multiprocess mode + my $child = fork(); + if(0 == $child) { + # TODO: set up a better signal handler + $SIG{INT} = 'IGNORE'; + $SIG{TERM} = 'IGNORE'; + + $onerunnerid = $$; + print "Runner $onerunnerid starting\n" if($verbose); + + # Here we are the child (runner). + close($controllerw); + close($controllerr); + + # Set this directory as ours + $LOGDIR = $logdir; + mkdir("$LOGDIR/$PIDDIR", 0777); + + # handle IPC calls + event_loop(); + + # Can't rely on logmsg here in case it's buffered + print "Runner $onerunnerid exiting\n" if($verbose); + exit 0; + } + + # Here we are the parent (controller). + close($runnerw); + close($runnerr); + + $onerunnerid = $child; + + } else { + # Create our pid directory + mkdir("$LOGDIR/$PIDDIR", 0777); + + # Don't create a separate process + $onerunnerid = "integrated"; + } + + return $onerunnerid; +} + +####################################################################### +# Loop to execute incoming IPC calls until the shutdown call +sub event_loop { + while () { + if(ipcrecv()) { + last; + } + } } ####################################################################### @@ -979,6 +1036,11 @@ sub runner_test_preprocess { loadtest("${TESTDIR}/test${testnum}"); readtestkeywords(); + ################################################################### + # Restore environment variables that were modified in a previous run. + # Test definition may instruct to (un)set environment vars. + restore_test_env(1); + ################################################################### # Start the servers needed to run this test case my ($why, $error) = singletest_startservers($testnum, \%testtimings); @@ -1115,10 +1177,10 @@ sub controlleripccall { # Send IPC call via pipe syswrite($controllerw, (pack "L", length($margs)) . $margs); - # Call the remote function - # TODO: this will eventually be done in a separate runner process - # kicked off by runner_init() - ipcrecv(); + if(!$multiprocess) { + # Call the remote function here in single process mode + ipcrecv(); + } } ################################################################### @@ -1140,7 +1202,7 @@ sub runnerar { my $resarrayref = thaw $buf; # First argument is runner ID - unshift @$resarrayref, "singleton"; + unshift @$resarrayref, $onerunnerid; return @$resarrayref; } diff --git a/tests/runtests.pl b/tests/runtests.pl index bf90583a0..dca495076 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -174,6 +174,7 @@ my $postmortem; # display detailed info about failed tests my $run_disabled; # run the specific tests even if listed in DISABLED my $scrambleorder; my $randseed = 0; +my $jobs = 0; # Azure Pipelines specific variables my $AZURE_RUN_ID = 0; @@ -748,6 +749,10 @@ sub checksystemfeatures { "* System: $hosttype", "* OS: $hostos\n"); + if($jobs) { + # Only show if not the default for now + logmsg "* Jobs: $jobs\n"; + } if($feature{"TrackMemory"} && $feature{"threaded-resolver"}) { logmsg("*\n", "*** DISABLES memory tracking when using threaded resolver\n", @@ -1633,7 +1638,6 @@ sub singletest { if($singletest_state == ST_INIT) { my $logdir = getlogdir($testnum); - # first, remove all lingering log files if(!cleardir($logdir) && $clearlocks) { runnerac_clearlocks($runnerid, $logdir); @@ -1661,7 +1665,7 @@ sub singletest { # Test definition may instruct to (un)set environment vars. # This is done this early so that leftover variables don't affect # starting servers or CI registration. - restore_test_env(1); + # restore_test_env(1); ################################################################### # Load test file so CI registration can get the right data before the @@ -1687,6 +1691,11 @@ sub singletest { } updatetesttimings($testnum, %$testtimings); + ####################################################################### + # Load test file for this test number + my $logdir = getlogdir($testnum); + loadtest("${logdir}/test${testnum}"); + ####################################################################### # Print the test name and count tests $error = singletest_count($testnum, $why); @@ -1739,6 +1748,12 @@ sub singletest { ####################################################################### # Verify that the test succeeded + # + # Load test file for this test number + my $logdir = getlogdir($testnum); + loadtest("${logdir}/test${testnum}"); + readtestkeywords(); + $error = singletest_check($runnerid, $testnum, $cmdres, $CURLOUT, $tool, $usedvalgrind); if($error == -1) { my $err = ignoreresultcode($testnum); @@ -2076,6 +2091,14 @@ while(@ARGV) { # lists the test case names only $listonly=1; } + elsif($ARGV[0] =~ /^-j(.*)/) { + # parallel jobs + $jobs=1; + my $xtra = $1; + if($xtra =~ s/(\d+)$//) { + $jobs = $1; + } + } elsif($ARGV[0] eq "-k") { # keep stdout and stderr files after tests $keepoutfiles=1; @@ -2133,6 +2156,7 @@ Usage: runtests.pl [options] [test selection(s)] -g run the test case with gdb -gw run the test case with gdb as a windowed application -h this help text + -j[N] spawn this number of processes to run tests (default 0, max. 1) -k keep stdout and stderr files present after tests -L path require an additional perl library file to replace certain functions -l list all test case names/descriptions @@ -2291,8 +2315,10 @@ mkdir($LOGDIR, 0777); # get_disttests(); -# Disable buffered logging for now -setlogfunc(\&logmsg); +if(!$jobs) { + # Disable buffered logging with only one test job + setlogfunc(\&logmsg); +} ####################################################################### # Output curl version and host info being tested @@ -2560,7 +2586,7 @@ citest_starttestrun(); # Initialize the runner to prepare to run tests cleardir($LOGDIR); mkdir($LOGDIR, 0777); -my $runnerid = runner_init($LOGDIR); +my $runnerid = runner_init($LOGDIR, $jobs); ####################################################################### # The main test-loop