123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491 |
- #***************************************************************************
- # _ _ ____ _
- # Project ___| | | | _ \| |
- # / __| | | | |_) | |
- # | (__| |_| | _ <| |___
- # \___|\___/|_| \_\_____|
- #
- # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- #
- # This software is licensed as described in the file COPYING, which
- # you should have received as part of this distribution. The terms
- # are also available at https://curl.se/docs/copyright.html.
- #
- # You may opt to use, copy, modify, merge, publish, distribute and/or sell
- # copies of the Software, and permit persons to whom the Software is
- # furnished to do so, under the terms of the COPYING file.
- #
- # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- # KIND, either express or implied.
- #
- # SPDX-License-Identifier: curl
- #
- ###########################################################################
- # 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;
- use strict;
- use warnings;
- use 5.006;
- BEGIN {
- use base qw(Exporter);
- our @EXPORT = qw(
- checktestcmd
- prepro
- readtestkeywords
- restore_test_env
- runner_init
- runnerac_clearlocks
- runnerac_shutdown
- runnerac_stopservers
- runnerac_test_preprocess
- runnerac_test_run
- runnerar
- runnerar_ready
- stderrfilename
- stdoutfilename
- $DBGCURL
- $gdb
- $gdbthis
- $gdbxwin
- $shallow
- $tortalloc
- $valgrind_logfile
- $valgrind_tool
- );
- # these are for debugging only
- our @EXPORT_OK = qw(
- singletest_preprocess
- );
- }
- use B qw(
- svref_2object
- );
- use Storable qw(
- freeze
- thaw
- );
- use pathhelp qw(
- exe_ext
- );
- use processhelp qw(
- portable_sleep
- );
- use servers qw(
- checkcmd
- clearlocks
- initserverconfig
- serverfortest
- stopserver
- stopservers
- subvariables
- );
- use getpart;
- use globalconfig;
- use testutil qw(
- clearlogs
- logmsg
- runclient
- shell_quote
- subbase64
- subnewlines
- );
- use valgrind;
- #######################################################################
- # 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";
- our $gdb = checktestcmd("gdb");
- our $gdbthis = 0; # run test case with debugger (gdb or lldb)
- our $gdbxwin; # use windowed gdb when using gdb
- # torture test variables
- our $shallow;
- our $tortalloc;
- # local variables
- my %oldenv; # environment variables before test is started
- my $UNITDIR="./unit";
- my $CURLLOG="$LOGDIR/commands.log"; # all command lines run
- my $defserverlogslocktimeout = 5; # timeout to await server logs lock removal
- my $defpostcommanddelay = 0; # delay between command and postcheck sections
- my $multiprocess; # nonzero with a separate test runner process
- # pipes
- my $runnerr; # pipe that runner reads from
- my $runnerw; # pipe that runner writes to
- # per-runner variables, indexed by runner ID; these are used by controller only
- my %controllerr; # pipe that controller reads from
- my %controllerw; # pipe that controller writes to
- # redirected stdout/stderr to these files
- sub stdoutfilename {
- my ($logdir, $testnum)=@_;
- return "$logdir/stdout$testnum";
- }
- sub stderrfilename {
- my ($logdir, $testnum)=@_;
- return "$logdir/stderr$testnum";
- }
- #######################################################################
- # Initialize the runner and prepare it to run tests
- # The runner ID returned by this function must be passed into the other
- # runnerac_* functions
- # Called by controller
- sub runner_init {
- my ($logdir, $jobs)=@_;
- $multiprocess = !!$jobs;
- # enable memory debugging if curl is compiled with it
- $ENV{'CURL_MEMDEBUG'} = "$logdir/$MEMDUMP";
- $ENV{'CURL_ENTROPY'}="12345678";
- $ENV{'CURL_FORCETIME'}=1; # for debug NTLM magic
- $ENV{'CURL_GLOBAL_INIT'}=1; # debug curl_global_init/cleanup use
- $ENV{'HOME'}=$pwd;
- $ENV{'CURL_HOME'}=$ENV{'HOME'};
- $ENV{'XDG_CONFIG_HOME'}=$ENV{'HOME'};
- $ENV{'COLUMNS'}=79; # screen width!
- # Incorporate the $logdir into the random seed and re-seed the PRNG.
- # This gives each runner a unique yet consistent seed which provides
- # more unique port number selection in each runner, yet is deterministic
- # across runs.
- $randseed += unpack('%16C*', $logdir);
- srand $randseed;
- # create pipes for communication with runner
- my ($thisrunnerr, $thiscontrollerw, $thiscontrollerr, $thisrunnerw);
- pipe $thisrunnerr, $thiscontrollerw;
- pipe $thiscontrollerr, $thisrunnerw;
- my $thisrunnerid;
- if($multiprocess) {
- # Create a separate process in multiprocess mode
- my $child = fork();
- if(0 == $child) {
- # TODO: set up better signal handlers
- $SIG{INT} = 'IGNORE';
- $SIG{TERM} = 'IGNORE';
- eval {
- # some msys2 perl versions don't define SIGUSR1
- $SIG{USR1} = 'IGNORE';
- };
- $thisrunnerid = $$;
- print "Runner $thisrunnerid starting\n" if($verbose);
- # Here we are the child (runner).
- close($thiscontrollerw);
- close($thiscontrollerr);
- $runnerr = $thisrunnerr;
- $runnerw = $thisrunnerw;
- # Set this directory as ours
- $LOGDIR = $logdir;
- mkdir("$LOGDIR/$PIDDIR", 0777);
- mkdir("$LOGDIR/$LOCKDIR", 0777);
- # Initialize various server variables
- initserverconfig();
- # handle IPC calls
- event_loop();
- # Can't rely on logmsg here in case it's buffered
- print "Runner $thisrunnerid exiting\n" if($verbose);
- # To reach this point, either the controller has sent
- # runnerac_stopservers() and runnerac_shutdown() or we have called
- # runnerabort(). In both cases, there are no more of our servers
- # running and we can safely exit.
- exit 0;
- }
- # Here we are the parent (controller).
- close($thisrunnerw);
- close($thisrunnerr);
- $thisrunnerid = $child;
- } else {
- # Create our pid directory
- mkdir("$LOGDIR/$PIDDIR", 0777);
- # Don't create a separate process
- $thisrunnerid = "integrated";
- }
- $controllerw{$thisrunnerid} = $thiscontrollerw;
- $runnerr = $thisrunnerr;
- $runnerw = $thisrunnerw;
- $controllerr{$thisrunnerid} = $thiscontrollerr;
- return $thisrunnerid;
- }
- #######################################################################
- # Loop to execute incoming IPC calls until the shutdown call
- sub event_loop {
- while () {
- if(ipcrecv()) {
- last;
- }
- }
- }
- #######################################################################
- # Check for a command in the PATH of the machine running curl.
- #
- sub checktestcmd {
- my ($cmd)=@_;
- my @testpaths=("$LIBDIR/.libs", "$LIBDIR");
- return checkcmd($cmd, @testpaths);
- }
- # See if Valgrind should actually be used
- sub use_valgrind {
- if($valgrind) {
- my @valgrindoption = getpart("verify", "valgrind");
- if((!@valgrindoption) || ($valgrindoption[0] !~ /disable/)) {
- return 1;
- }
- }
- return 0;
- }
- # Massage the command result code into a useful form
- sub normalize_cmdres {
- my $cmdres = $_[0];
- my $signal_num = $cmdres & 127;
- my $dumped_core = $cmdres & 128;
- if(!$anyway && ($signal_num || $dumped_core)) {
- $cmdres = 1000;
- }
- else {
- $cmdres >>= 8;
- $cmdres = (2000 + $signal_num) if($signal_num && !$cmdres);
- }
- return ($cmdres, $dumped_core);
- }
- # 'prepro' processes the input array and replaces %-variables in the array
- # etc. Returns the processed version of the array
- sub prepro {
- my $testnum = shift;
- my (@entiretest) = @_;
- my $show = 1;
- my @out;
- my $data_crlf;
- my @pshow;
- my @altshow;
- my $plvl;
- my $line;
- for my $s (@entiretest) {
- my $f = $s;
- $line++;
- if($s =~ /^ *%if (.*)/) {
- my $cond = $1;
- my $rev = 0;
- if($cond =~ /^!(.*)/) {
- $cond = $1;
- $rev = 1;
- }
- $rev ^= $feature{$cond} ? 1 : 0;
- push @pshow, $show; # push the previous state
- $plvl++;
- if($show) {
- # only if this was showing before we can allow the alternative
- # to go showing as well
- push @altshow, $rev ^ 1; # push the reversed show state
- }
- else {
- push @altshow, 0; # the alt should still hide
- }
- if($show) {
- # we only allow show if already showing
- $show = $rev;
- }
- next;
- }
- elsif($s =~ /^ *%else/) {
- if(!$plvl) {
- print STDERR "error: test$testnum:$line: %else no %if\n";
- last;
- }
- $show = pop @altshow;
- push @altshow, $show; # put it back for consistency
- next;
- }
- elsif($s =~ /^ *%endif/) {
- if(!$plvl--) {
- print STDERR "error: test$testnum:$line: %endif had no %if\n";
- last;
- }
- $show = pop @pshow;
- pop @altshow; # not used here but we must pop it
- next;
- }
- if($show) {
- # The processor does CRLF replacements in the <data*> sections if
- # necessary since those parts might be read by separate servers.
- if($s =~ /^ *<data(.*)\>/) {
- if($1 =~ /crlf="yes"/ ||
- ($feature{"hyper"} && ($keywords{"HTTP"} || $keywords{"HTTPS"}))) {
- $data_crlf = 1;
- }
- }
- elsif(($s =~ /^ *<\/data/) && $data_crlf) {
- $data_crlf = 0;
- }
- subvariables(\$s, $testnum, "%");
- subbase64(\$s);
- subnewlines(0, \$s) if($data_crlf);
- push @out, $s;
- }
- }
- return @out;
- }
- #######################################################################
- # Load test keywords into %keywords hash
- #
- sub readtestkeywords {
- my @info_keywords = getpart("info", "keywords");
- # Clear the list of keywords from the last test
- %keywords = ();
- for my $k (@info_keywords) {
- chomp $k;
- $keywords{$k} = 1;
- }
- }
- #######################################################################
- # Return a list of log locks that still exist
- #
- sub logslocked {
- opendir(my $lockdir, "$LOGDIR/$LOCKDIR");
- my @locks;
- foreach (readdir $lockdir) {
- if(/^(.*)\.lock$/) {
- push @locks, $1;
- }
- }
- return @locks;
- }
- #######################################################################
- # Memory allocation test and failure torture testing.
- #
- sub torture {
- my ($testcmd, $testnum, $gdbline) = @_;
- # remove memdump first to be sure we get a new nice and clean one
- unlink("$LOGDIR/$MEMDUMP");
- # First get URL from test server, ignore the output/result
- runclient($testcmd);
- logmsg " CMD: $testcmd\n" if($verbose);
- # memanalyze -v is our friend, get the number of allocations made
- my $count=0;
- my @out = `$memanalyze -v "$LOGDIR/$MEMDUMP"`;
- for(@out) {
- if(/^Operations: (\d+)/) {
- $count = $1;
- last;
- }
- }
- if(!$count) {
- logmsg " found no functions to make fail\n";
- return 0;
- }
- my @ttests = (1 .. $count);
- if($shallow && ($shallow < $count)) {
- my $discard = scalar(@ttests) - $shallow;
- my $percent = sprintf("%.2f%%", $shallow * 100 / scalar(@ttests));
- logmsg " $count functions found, but only fail $shallow ($percent)\n";
- while($discard) {
- my $rm;
- do {
- # find a test to discard
- $rm = rand(scalar(@ttests));
- } while(!$ttests[$rm]);
- $ttests[$rm] = undef;
- $discard--;
- }
- }
- else {
- logmsg " $count functions to make fail\n";
- }
- for (@ttests) {
- my $limit = $_;
- my $fail;
- my $dumped_core;
- if(!defined($limit)) {
- # --shallow can undefine them
- next;
- }
- if($tortalloc && ($tortalloc != $limit)) {
- next;
- }
- if($verbose) {
- my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
- localtime(time());
- my $now = sprintf("%02d:%02d:%02d ", $hour, $min, $sec);
- logmsg "Fail function no: $limit at $now\r";
- }
- # make the memory allocation function number $limit return failure
- $ENV{'CURL_MEMLIMIT'} = $limit;
- # remove memdump first to be sure we get a new nice and clean one
- unlink("$LOGDIR/$MEMDUMP");
- my $cmd = $testcmd;
- if($valgrind && !$gdbthis) {
- my @valgrindoption = getpart("verify", "valgrind");
- if((!@valgrindoption) || ($valgrindoption[0] !~ /disable/)) {
- my $valgrindcmd = "$valgrind ";
- $valgrindcmd .= "$valgrind_tool " if($valgrind_tool);
- $valgrindcmd .= "--quiet --leak-check=yes ";
- $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp ";
- # $valgrindcmd .= "--gen-suppressions=all ";
- $valgrindcmd .= "--num-callers=16 ";
- $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum";
- $cmd = "$valgrindcmd $testcmd";
- }
- }
- logmsg "*** Function number $limit is now set to fail ***\n" if($gdbthis);
- my $ret = 0;
- if($gdbthis) {
- runclient($gdbline);
- }
- else {
- $ret = runclient($cmd);
- }
- #logmsg "$_ Returned " . ($ret >> 8) . "\n";
- # Now clear the variable again
- delete $ENV{'CURL_MEMLIMIT'} if($ENV{'CURL_MEMLIMIT'});
- if(-r "core") {
- # there's core file present now!
- logmsg " core dumped\n";
- $dumped_core = 1;
- $fail = 2;
- }
- if($valgrind) {
- my @e = valgrindparse("$LOGDIR/valgrind$testnum");
- if(@e && $e[0]) {
- if($automakestyle) {
- logmsg "FAIL: torture $testnum - valgrind\n";
- }
- else {
- logmsg " valgrind ERROR ";
- logmsg @e;
- }
- $fail = 1;
- }
- }
- # verify that it returns a proper error code, doesn't leak memory
- # and doesn't core dump
- if(($ret & 255) || ($ret >> 8) >= 128) {
- logmsg " system() returned $ret\n";
- $fail=1;
- }
- else {
- my @memdata=`$memanalyze "$LOGDIR/$MEMDUMP"`;
- my $leak=0;
- for(@memdata) {
- if($_ ne "") {
- # well it could be other memory problems as well, but
- # we call it leak for short here
- $leak=1;
- }
- }
- if($leak) {
- logmsg "** MEMORY FAILURE\n";
- logmsg @memdata;
- logmsg `$memanalyze -l "$LOGDIR/$MEMDUMP"`;
- $fail = 1;
- }
- }
- if($fail) {
- logmsg " $testnum: torture FAILED: function number $limit in test.\n",
- " invoke with \"-t$limit\" to repeat this single case.\n";
- stopservers($verbose);
- return 1;
- }
- }
- logmsg "\n" if($verbose);
- logmsg "torture OK\n";
- return 0;
- }
- #######################################################################
- # restore environment variables that were modified in test
- sub restore_test_env {
- my $deleteoldenv = $_[0]; # 1 to delete the saved contents after restore
- foreach my $var (keys %oldenv) {
- if($oldenv{$var} eq 'notset') {
- delete $ENV{$var} if($ENV{$var});
- }
- else {
- $ENV{$var} = $oldenv{$var};
- }
- if($deleteoldenv) {
- delete $oldenv{$var};
- }
- }
- }
- #######################################################################
- # Start the servers needed to run this test case
- sub singletest_startservers {
- my ($testnum, $testtimings) = @_;
- # remove old test server files before servers are started/verified
- unlink("$LOGDIR/$SERVERCMD");
- unlink("$LOGDIR/$SERVERIN");
- unlink("$LOGDIR/$PROXYIN");
- # timestamp required servers verification start
- $$testtimings{"timesrvrini"} = Time::HiRes::time();
- my $why;
- my $error;
- if (!$listonly) {
- my @what = getpart("client", "server");
- if(!$what[0]) {
- warn "Test case $testnum has no server(s) specified";
- $why = "no server specified";
- $error = -1;
- } else {
- my $err;
- ($why, $err) = serverfortest(@what);
- if($err == 1) {
- # Error indicates an actual problem starting the server
- $error = -2;
- } else {
- $error = -1;
- }
- }
- }
- # timestamp required servers verification end
- $$testtimings{"timesrvrend"} = Time::HiRes::time();
- return ($why, $error);
- }
- #######################################################################
- # Generate preprocessed test file
- sub singletest_preprocess {
- my $testnum = $_[0];
- # Save a preprocessed version of the entire test file. This allows more
- # "basic" test case readers to enjoy variable replacements.
- my @entiretest = fulltest();
- my $otest = "$LOGDIR/test$testnum";
- @entiretest = prepro($testnum, @entiretest);
- # save the new version
- open(my $fulltesth, ">", "$otest") || die "Failure writing test file";
- foreach my $bytes (@entiretest) {
- print $fulltesth pack('a*', $bytes) or die "Failed to print '$bytes': $!";
- }
- close($fulltesth) || die "Failure writing test file";
- # in case the process changed the file, reload it
- loadtest("$LOGDIR/test${testnum}");
- }
- #######################################################################
- # Set up the test environment to run this test case
- sub singletest_setenv {
- my @setenv = getpart("client", "setenv");
- foreach my $s (@setenv) {
- chomp $s;
- if($s =~ /([^=]*)(.*)/) {
- my ($var, $content) = ($1, $2);
- # remember current setting, to restore it once test runs
- $oldenv{$var} = ($ENV{$var})?"$ENV{$var}":'notset';
- if($content =~ /^=(.*)/) {
- # assign it
- $content = $1;
- if($var =~ /^LD_PRELOAD/) {
- if(exe_ext('TOOL') && (exe_ext('TOOL') eq '.exe')) {
- logmsg "Skipping LD_PRELOAD due to lack of OS support\n" if($verbose);
- next;
- }
- if($feature{"debug"} || !$has_shared) {
- logmsg "Skipping LD_PRELOAD due to no release shared build\n" if($verbose);
- next;
- }
- }
- $ENV{$var} = "$content";
- logmsg "setenv $var = $content\n" if($verbose);
- }
- else {
- # remove it
- delete $ENV{$var} if($ENV{$var});
- }
- }
- }
- if($proxy_address) {
- $ENV{http_proxy} = $proxy_address;
- $ENV{HTTPS_PROXY} = $proxy_address;
- }
- }
- #######################################################################
- # Check that test environment is fine to run this test case
- sub singletest_precheck {
- my $testnum = $_[0];
- my $why;
- my @precheck = getpart("client", "precheck");
- if(@precheck) {
- my $cmd = $precheck[0];
- chomp $cmd;
- if($cmd) {
- my @p = split(/ /, $cmd);
- if($p[0] !~ /\//) {
- # the first word, the command, does not contain a slash so
- # we will scan the "improved" PATH to find the command to
- # be able to run it
- my $fullp = checktestcmd($p[0]);
- if($fullp) {
- $p[0] = $fullp;
- }
- $cmd = join(" ", @p);
- }
- my @o = `$cmd 2> $LOGDIR/precheck-$testnum`;
- if($o[0]) {
- $why = $o[0];
- $why =~ s/[\r\n]//g;
- }
- elsif($?) {
- $why = "precheck command error";
- }
- logmsg "prechecked $cmd\n" if($verbose);
- }
- }
- return $why;
- }
- #######################################################################
- # Prepare the test environment to run this test case
- sub singletest_prepare {
- my ($testnum) = @_;
- if($feature{"TrackMemory"}) {
- unlink("$LOGDIR/$MEMDUMP");
- }
- unlink("core");
- # remove server output logfiles after servers are started/verified
- unlink("$LOGDIR/$SERVERIN");
- unlink("$LOGDIR/$PROXYIN");
- # if this section exists, it might be FTP server instructions:
- my @ftpservercmd = getpart("reply", "servercmd");
- push @ftpservercmd, "Testnum $testnum\n";
- # write the instructions to file
- writearray("$LOGDIR/$SERVERCMD", \@ftpservercmd);
- # create (possibly-empty) files before starting the test
- for my $partsuffix (('', '1', '2', '3', '4')) {
- my @inputfile=getpart("client", "file".$partsuffix);
- my %fileattr = getpartattr("client", "file".$partsuffix);
- my $filename=$fileattr{'name'};
- if(@inputfile || $filename) {
- if(!$filename) {
- logmsg " $testnum: IGNORED: section client=>file has no name attribute\n";
- return -1;
- }
- my $fileContent = join('', @inputfile);
- # make directories if needed
- my $path = $filename;
- # cut off the file name part
- $path =~ s/^(.*)\/[^\/]*/$1/;
- my @ldparts = split(/\//, $LOGDIR);
- my $nparts = @ldparts;
- my @parts = split(/\//, $path);
- if(join("/", @parts[0..$nparts-1]) eq $LOGDIR) {
- # the file is in $LOGDIR/
- my $d = shift @parts;
- for(@parts) {
- $d .= "/$_";
- mkdir $d; # 0777
- }
- }
- if (open(my $outfile, ">", "$filename")) {
- binmode $outfile; # for crapage systems, use binary
- if($fileattr{'nonewline'}) {
- # cut off the final newline
- chomp($fileContent);
- }
- print $outfile $fileContent;
- close($outfile);
- } else {
- logmsg "ERROR: cannot write $filename\n";
- }
- }
- }
- return 0;
- }
- #######################################################################
- # Run the test command
- sub singletest_run {
- my ($testnum, $testtimings) = @_;
- # get the command line options to use
- my ($cmd, @blaha)= getpart("client", "command");
- if($cmd) {
- # make some nice replace operations
- $cmd =~ s/\n//g; # no newlines please
- # substitute variables in the command line
- }
- else {
- # there was no command given, use something silly
- $cmd="-";
- }
- my $CURLOUT="$LOGDIR/curl$testnum.out"; # curl output if not stdout
- # if stdout section exists, we verify that the stdout contained this:
- my $out="";
- my %cmdhash = getpartattr("client", "command");
- if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-output/)) {
- #We may slap on --output!
- if (!partexists("verify", "stdout") ||
- ($cmdhash{'option'} && $cmdhash{'option'} =~ /force-output/)) {
- $out=" --output $CURLOUT ";
- }
- }
- my @codepieces = getpart("client", "tool");
- my $tool="";
- if(@codepieces) {
- $tool = $codepieces[0];
- chomp $tool;
- $tool .= exe_ext('TOOL');
- }
- my $disablevalgrind;
- my $CMDLINE="";
- my $cmdargs;
- my $cmdtype = $cmdhash{'type'} || "default";
- my $fail_due_event_based = $run_event_based;
- if($cmdtype eq "perl") {
- # run the command line prepended with "perl"
- $cmdargs ="$cmd";
- $CMDLINE = "$perl ";
- $tool=$CMDLINE;
- $disablevalgrind=1;
- }
- elsif($cmdtype eq "shell") {
- # run the command line prepended with "/bin/sh"
- $cmdargs ="$cmd";
- $CMDLINE = "/bin/sh ";
- $tool=$CMDLINE;
- $disablevalgrind=1;
- }
- elsif(!$tool && !$keywords{"unittest"}) {
- # run curl, add suitable command line options
- my $inc="";
- if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-include/)) {
- $inc = " --include";
- }
- $cmdargs = "$out$inc ";
- if($cmdhash{'option'} && ($cmdhash{'option'} =~ /binary-trace/)) {
- $cmdargs .= "--trace $LOGDIR/trace$testnum ";
- }
- else {
- $cmdargs .= "--trace-ascii $LOGDIR/trace$testnum ";
- }
- $cmdargs .= "--trace-config all ";
- $cmdargs .= "--trace-time ";
- if($run_event_based) {
- $cmdargs .= "--test-event ";
- $fail_due_event_based--;
- }
- $cmdargs .= $cmd;
- if ($proxy_address) {
- $cmdargs .= " --proxy $proxy_address ";
- }
- }
- else {
- $cmdargs = " $cmd"; # $cmd is the command line for the test file
- $CURLOUT = stdoutfilename($LOGDIR, $testnum); # sends received data to stdout
- # Default the tool to a unit test with the same name as the test spec
- if($keywords{"unittest"} && !$tool) {
- $tool="unit$testnum";
- }
- if($tool =~ /^lib/) {
- $CMDLINE="$LIBDIR/$tool";
- }
- elsif($tool =~ /^unit/) {
- $CMDLINE="$UNITDIR/$tool";
- }
- if(! -f $CMDLINE) {
- logmsg " $testnum: IGNORED: The tool set in the test case for this: '$tool' does not exist\n";
- return (-1, 0, 0, "", "", 0);
- }
- $DBGCURL=$CMDLINE;
- }
- if($fail_due_event_based) {
- logmsg " $testnum: IGNORED: This test cannot run event based\n";
- return (-1, 0, 0, "", "", 0);
- }
- if($gdbthis) {
- # gdb is incompatible with valgrind, so disable it when debugging
- # Perhaps a better approach would be to run it under valgrind anyway
- # with --db-attach=yes or --vgdb=yes.
- $disablevalgrind=1;
- }
- my @stdintest = getpart("client", "stdin");
- if(@stdintest) {
- my $stdinfile="$LOGDIR/stdin-for-$testnum";
- my %hash = getpartattr("client", "stdin");
- if($hash{'nonewline'}) {
- # cut off the final newline from the final line of the stdin data
- chomp($stdintest[-1]);
- }
- writearray($stdinfile, \@stdintest);
- $cmdargs .= " <$stdinfile";
- }
- if(!$tool) {
- $CMDLINE=shell_quote($CURL);
- if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-q/)) {
- $CMDLINE .= " -q";
- }
- }
- if(use_valgrind() && !$disablevalgrind) {
- my $valgrindcmd = "$valgrind ";
- $valgrindcmd .= "$valgrind_tool " if($valgrind_tool);
- $valgrindcmd .= "--quiet --leak-check=yes ";
- $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp ";
- # $valgrindcmd .= "--gen-suppressions=all ";
- $valgrindcmd .= "--num-callers=16 ";
- $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum";
- $CMDLINE = "$valgrindcmd $CMDLINE";
- }
- $CMDLINE .= "$cmdargs > " . stdoutfilename($LOGDIR, $testnum) .
- " 2> " . stderrfilename($LOGDIR, $testnum);
- if($verbose) {
- logmsg "$CMDLINE\n";
- }
- open(my $cmdlog, ">", $CURLLOG) || die "Failure writing log file";
- print $cmdlog "$CMDLINE\n";
- close($cmdlog) || die "Failure writing log file";
- my $dumped_core;
- my $cmdres;
- if($gdbthis) {
- my $gdbinit = "$TESTDIR/gdbinit$testnum";
- open(my $gdbcmd, ">", "$LOGDIR/gdbcmd") || die "Failure writing gdb file";
- if($gdbthis == 1) {
- # gdb mode
- print $gdbcmd "set args $cmdargs\n";
- print $gdbcmd "show args\n";
- print $gdbcmd "source $gdbinit\n" if -e $gdbinit;
- }
- else {
- # lldb mode
- print $gdbcmd "set args $cmdargs\n";
- }
- close($gdbcmd) || die "Failure writing gdb file";
- }
- # Flush output.
- $| = 1;
- # timestamp starting of test command
- $$testtimings{"timetoolini"} = Time::HiRes::time();
- # run the command line we built
- if ($torture) {
- $cmdres = torture($CMDLINE,
- $testnum,
- "$gdb --directory $LIBDIR " . shell_quote($DBGCURL) . " -x $LOGDIR/gdbcmd");
- }
- elsif($gdbthis == 1) {
- # gdb
- my $GDBW = ($gdbxwin) ? "-w" : "";
- runclient("$gdb --directory $LIBDIR " . shell_quote($DBGCURL) . " $GDBW -x $LOGDIR/gdbcmd");
- $cmdres=0; # makes it always continue after a debugged run
- }
- elsif($gdbthis == 2) {
- # $gdb is "lldb"
- print "runs lldb -- $CURL $cmdargs\n";
- runclient("lldb -- $CURL $cmdargs");
- $cmdres=0; # makes it always continue after a debugged run
- }
- else {
- # Convert the raw result code into a more useful one
- ($cmdres, $dumped_core) = normalize_cmdres(runclient("$CMDLINE"));
- }
- # timestamp finishing of test command
- $$testtimings{"timetoolend"} = Time::HiRes::time();
- return (0, $cmdres, $dumped_core, $CURLOUT, $tool, use_valgrind() && !$disablevalgrind);
- }
- #######################################################################
- # Clean up after test command
- sub singletest_clean {
- my ($testnum, $dumped_core, $testtimings)=@_;
- if(!$dumped_core) {
- if(-r "core") {
- # there's core file present now!
- $dumped_core = 1;
- }
- }
- if($dumped_core) {
- logmsg "core dumped\n";
- if(0 && $gdb) {
- logmsg "running gdb for post-mortem analysis:\n";
- open(my $gdbcmd, ">", "$LOGDIR/gdbcmd2") || die "Failure writing gdb file";
- print $gdbcmd "bt\n";
- close($gdbcmd) || die "Failure writing gdb file";
- runclient("$gdb --directory libtest -x $LOGDIR/gdbcmd2 -batch " . shell_quote($DBGCURL) . " core ");
- # unlink("$LOGDIR/gdbcmd2");
- }
- }
- # If a server logs advisor read lock file exists, it is an indication
- # that the server has not yet finished writing out all its log files,
- # including server request log files used for protocol verification.
- # So, if the lock file exists the script waits here a certain amount
- # of time until the server removes it, or the given time expires.
- my $serverlogslocktimeout = $defserverlogslocktimeout;
- my %cmdhash = getpartattr("client", "command");
- if($cmdhash{'timeout'}) {
- # test is allowed to override default server logs lock timeout
- if($cmdhash{'timeout'} =~ /(\d+)/) {
- $serverlogslocktimeout = $1 if($1 >= 0);
- }
- }
- if($serverlogslocktimeout) {
- my $lockretry = $serverlogslocktimeout * 20;
- my @locks;
- while((@locks = logslocked()) && $lockretry--) {
- portable_sleep(0.05);
- }
- if(($lockretry < 0) &&
- ($serverlogslocktimeout >= $defserverlogslocktimeout)) {
- logmsg "Warning: server logs lock timeout ",
- "($serverlogslocktimeout seconds) expired (locks: " .
- join(", ", @locks) . ")\n";
- }
- }
- # Test harness ssh server does not have this synchronization mechanism,
- # this implies that some ssh server based tests might need a small delay
- # once that the client command has run to avoid false test failures.
- #
- # gnutls-serv also lacks this synchronization mechanism, so gnutls-serv
- # based tests might need a small delay once that the client command has
- # run to avoid false test failures.
- my $postcommanddelay = $defpostcommanddelay;
- if($cmdhash{'delay'}) {
- # test is allowed to specify a delay after command is executed
- if($cmdhash{'delay'} =~ /(\d+)/) {
- $postcommanddelay = $1 if($1 > 0);
- }
- }
- portable_sleep($postcommanddelay) if($postcommanddelay);
- # timestamp removal of server logs advisor read lock
- $$testtimings{"timesrvrlog"} = Time::HiRes::time();
- # test definition might instruct to stop some servers
- # stop also all servers relative to the given one
- my @killtestservers = getpart("client", "killserver");
- if(@killtestservers) {
- foreach my $server (@killtestservers) {
- chomp $server;
- if(stopserver($server)) {
- logmsg " $testnum: killserver FAILED\n";
- return 1; # normal error if asked to fail on unexpected alive
- }
- }
- }
- return 0;
- }
- #######################################################################
- # Verify that the postcheck succeeded
- sub singletest_postcheck {
- my ($testnum)=@_;
- # run the postcheck command
- my @postcheck= getpart("client", "postcheck");
- if(@postcheck) {
- my $cmd = join("", @postcheck);
- chomp $cmd;
- if($cmd) {
- logmsg "postcheck $cmd\n" if($verbose);
- my $rc = runclient("$cmd");
- # Must run the postcheck command in torture mode in order
- # to clean up, but the result can't be relied upon.
- if($rc != 0 && !$torture) {
- logmsg " $testnum: postcheck FAILED\n";
- return -1;
- }
- }
- }
- return 0;
- }
- ###################################################################
- # Get ready to run a single test case
- sub runner_test_preprocess {
- my ($testnum)=@_;
- my %testtimings;
- if(clearlogs()) {
- logmsg "Warning: log messages were lost\n";
- }
- # timestamp test preparation start
- # TODO: this metric now shows only a portion of the prep time; better would
- # be to time singletest_preprocess below instead
- $testtimings{"timeprepini"} = Time::HiRes::time();
- ###################################################################
- # Load test metadata
- # ignore any error here--if there were one, it would have been
- # caught during the selection phase and this test would not be
- # running now
- 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);
- if(!$why) {
- ###############################################################
- # Generate preprocessed test file
- # This must be done after the servers are started so server
- # variables are available for substitution.
- singletest_preprocess($testnum);
- ###############################################################
- # Set up the test environment to run this test case
- singletest_setenv();
- ###############################################################
- # Check that the test environment is fine to run this test case
- if (!$listonly) {
- $why = singletest_precheck($testnum);
- $error = -1;
- }
- }
- return ($why, $error, clearlogs(), \%testtimings);
- }
- ###################################################################
- # Run a single test case with an environment that already been prepared
- # Returns 0=success, -1=skippable failure, -2=permanent error,
- # 1=unskippable test failure, as first integer, plus any log messages,
- # plus more return values when error is 0
- sub runner_test_run {
- my ($testnum)=@_;
- if(clearlogs()) {
- logmsg "Warning: log messages were lost\n";
- }
- #######################################################################
- # Prepare the test environment to run this test case
- my $error = singletest_prepare($testnum);
- if($error) {
- return (-2, clearlogs());
- }
- #######################################################################
- # Run the test command
- my %testtimings;
- my $cmdres;
- my $dumped_core;
- my $CURLOUT;
- my $tool;
- my $usedvalgrind;
- ($error, $cmdres, $dumped_core, $CURLOUT, $tool, $usedvalgrind) = singletest_run($testnum, \%testtimings);
- if($error) {
- return (-2, clearlogs(), \%testtimings);
- }
- #######################################################################
- # Clean up after test command
- $error = singletest_clean($testnum, $dumped_core, \%testtimings);
- if($error) {
- return ($error, clearlogs(), \%testtimings);
- }
- #######################################################################
- # Verify that the postcheck succeeded
- $error = singletest_postcheck($testnum);
- if($error) {
- return ($error, clearlogs(), \%testtimings);
- }
- #######################################################################
- # restore environment variables that were modified
- restore_test_env(0);
- return (0, clearlogs(), \%testtimings, $cmdres, $CURLOUT, $tool, $usedvalgrind);
- }
- # Async call runner_clearlocks
- # Called by controller
- sub runnerac_clearlocks {
- return controlleripccall(\&runner_clearlocks, @_);
- }
- # Async call runner_shutdown
- # This call does NOT generate an IPC response and must be the last IPC call
- # received.
- # Called by controller
- sub runnerac_shutdown {
- my ($runnerid)=$_[0];
- my $err = controlleripccall(\&runner_shutdown, @_);
- # These have no more use
- close($controllerw{$runnerid});
- undef $controllerw{$runnerid};
- close($controllerr{$runnerid});
- undef $controllerr{$runnerid};
- return $err;
- }
- # Async call of runner_stopservers
- # Called by controller
- sub runnerac_stopservers {
- return controlleripccall(\&runner_stopservers, @_);
- }
- # Async call of runner_test_preprocess
- # Called by controller
- sub runnerac_test_preprocess {
- return controlleripccall(\&runner_test_preprocess, @_);
- }
- # Async call of runner_test_run
- # Called by controller
- sub runnerac_test_run {
- return controlleripccall(\&runner_test_run, @_);
- }
- ###################################################################
- # Call an arbitrary function via IPC
- # The first argument is the function reference, the second is the runner ID
- # Returns 0 on success, -1 on error writing to runner
- # Called by controller (indirectly, via a more specific function)
- sub controlleripccall {
- my $funcref = shift @_;
- my $runnerid = shift @_;
- # Get the name of the function from the reference
- my $cv = svref_2object($funcref);
- my $gv = $cv->GV;
- # Prepend the name to the function arguments so it's marshalled along with them
- unshift @_, $gv->NAME;
- # Marshall the arguments into a flat string
- my $margs = freeze \@_;
- # Send IPC call via pipe
- my $err;
- while(! defined ($err = syswrite($controllerw{$runnerid}, (pack "L", length($margs)) . $margs)) || $err <= 0) {
- if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
- # Runner has likely died
- return -1;
- }
- # system call was interrupted, probably by ^C; restart it so we stay in sync
- }
- if(!$multiprocess) {
- # Call the remote function here in single process mode
- ipcrecv();
- }
- return 0;
- }
- ###################################################################
- # Receive async response of a previous call via IPC
- # The first return value is the runner ID or undef on error
- # Called by controller
- sub runnerar {
- my ($runnerid) = @_;
- my $err;
- my $datalen;
- while(! defined ($err = sysread($controllerr{$runnerid}, $datalen, 4)) || $err <= 0) {
- if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
- # Runner is likely dead and closed the pipe
- return undef;
- }
- # system call was interrupted, probably by ^C; restart it so we stay in sync
- }
- my $len=unpack("L", $datalen);
- my $buf;
- while(! defined ($err = sysread($controllerr{$runnerid}, $buf, $len)) || $err <= 0) {
- if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
- # Runner is likely dead and closed the pipe
- return undef;
- }
- # system call was interrupted, probably by ^C; restart it so we stay in sync
- }
- # Decode response values
- my $resarrayref = thaw $buf;
- # First argument is runner ID
- # TODO: remove this; it's unneeded since it's passed in
- unshift @$resarrayref, $runnerid;
- return @$resarrayref;
- }
- ###################################################################
- # Returns runner ID if a response from an async call is ready or error
- # First value is ready, second is error, however an error case shows up
- # as ready in Linux, so you can't trust it.
- # argument is 0 for nonblocking, undef for blocking, anything else for timeout
- # Called by controller
- sub runnerar_ready {
- my ($blocking) = @_;
- my $rin = "";
- my %idbyfileno;
- my $maxfileno=0;
- foreach my $p (keys(%controllerr)) {
- my $fd = fileno($controllerr{$p});
- vec($rin, $fd, 1) = 1;
- $idbyfileno{$fd} = $p; # save the runner ID for each pipe fd
- if($fd > $maxfileno) {
- $maxfileno = $fd;
- }
- }
- $maxfileno || die "Internal error: no runners are available to wait on\n";
- # Wait for any pipe from any runner to be ready
- # This may be interrupted and return EINTR, but this is ignored and the
- # caller will need to later call this function again.
- # TODO: this is relatively slow with hundreds of fds
- my $ein = $rin;
- if(select(my $rout=$rin, undef, my $eout=$ein, $blocking) >= 1) {
- for my $fd (0..$maxfileno) {
- # Return an error condition first in case it's both
- if(vec($eout, $fd, 1)) {
- return (undef, $idbyfileno{$fd});
- }
- if(vec($rout, $fd, 1)) {
- return ($idbyfileno{$fd}, undef);
- }
- }
- die "Internal pipe readiness inconsistency\n";
- }
- return (undef, undef);
- }
- ###################################################################
- # Cleanly abort and exit the runner
- # This uses print since there is no longer any controller to write logs.
- sub runnerabort{
- print "Controller is gone: runner $$ for $LOGDIR exiting\n";
- my ($error, $logs) = runner_stopservers();
- print $logs;
- runner_shutdown();
- }
- ###################################################################
- # Receive an IPC call in the runner and execute it
- # The IPC is read from the $runnerr pipe and the response is
- # written to the $runnerw pipe
- # Returns 0 if more IPC calls are expected or 1 if the runner should exit
- sub ipcrecv {
- my $err;
- my $datalen;
- while(! defined ($err = sysread($runnerr, $datalen, 4)) || $err <= 0) {
- if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
- # pipe has closed; controller is gone and we must exit
- runnerabort();
- # Special case: no response will be forthcoming
- return 1;
- }
- # system call was interrupted, probably by ^C; restart it so we stay in sync
- }
- my $len=unpack("L", $datalen);
- my $buf;
- while(! defined ($err = sysread($runnerr, $buf, $len)) || $err <= 0) {
- if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
- # pipe has closed; controller is gone and we must exit
- runnerabort();
- # Special case: no response will be forthcoming
- return 1;
- }
- # system call was interrupted, probably by ^C; restart it so we stay in sync
- }
- # Decode the function name and arguments
- my $argsarrayref = thaw $buf;
- # The name of the function to call is the first argument
- my $funcname = shift @$argsarrayref;
- # print "ipcrecv $funcname\n";
- # Synchronously call the desired function
- my @res;
- if($funcname eq "runner_clearlocks") {
- @res = runner_clearlocks(@$argsarrayref);
- }
- elsif($funcname eq "runner_shutdown") {
- runner_shutdown(@$argsarrayref);
- # Special case: no response will be forthcoming
- return 1;
- }
- elsif($funcname eq "runner_stopservers") {
- @res = runner_stopservers(@$argsarrayref);
- }
- elsif($funcname eq "runner_test_preprocess") {
- @res = runner_test_preprocess(@$argsarrayref);
- }
- elsif($funcname eq "runner_test_run") {
- @res = runner_test_run(@$argsarrayref);
- } else {
- die "Unknown IPC function $funcname\n";
- }
- # print "ipcrecv results\n";
- # Marshall the results to return
- $buf = freeze \@res;
- while(! defined ($err = syswrite($runnerw, (pack "L", length($buf)) . $buf)) || $err <= 0) {
- if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
- # pipe has closed; controller is gone and we must exit
- runnerabort();
- # Special case: no response will be forthcoming
- return 1;
- }
- # system call was interrupted, probably by ^C; restart it so we stay in sync
- }
- return 0;
- }
- ###################################################################
- # Kill the server processes that still have lock files in a directory
- sub runner_clearlocks {
- my ($lockdir)=@_;
- if(clearlogs()) {
- logmsg "Warning: log messages were lost\n";
- }
- clearlocks($lockdir);
- return clearlogs();
- }
- ###################################################################
- # Kill all server processes
- sub runner_stopservers {
- my $error = stopservers($verbose);
- my $logs = clearlogs();
- return ($error, $logs);
- }
- ###################################################################
- # Shut down this runner
- sub runner_shutdown {
- close($runnerr);
- undef $runnerr;
- close($runnerw);
- undef $runnerw;
- }
- 1;
|