xref: /curl/tests/runner.pm (revision e3aa2a07)
1#***************************************************************************
2#                                  _   _ ____  _
3#  Project                     ___| | | |  _ \| |
4#                             / __| | | | |_) | |
5#                            | (__| |_| |  _ <| |___
6#                             \___|\___/|_| \_\_____|
7#
8# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9#
10# This software is licensed as described in the file COPYING, which
11# you should have received as part of this distribution. The terms
12# are also available at https://curl.se/docs/copyright.html.
13#
14# You may opt to use, copy, modify, merge, publish, distribute and/or sell
15# copies of the Software, and permit persons to whom the Software is
16# furnished to do so, under the terms of the COPYING file.
17#
18# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19# KIND, either express or implied.
20#
21# SPDX-License-Identifier: curl
22#
23###########################################################################
24
25# This module contains entry points to run a single test. runner_init
26# determines whether they will run in a separate process or in the process of
27# the caller. The relevant interface is asynchronous so it will work in either
28# case. Program arguments are marshalled and then written to the end of a pipe
29# (in controlleripccall) which is later read from and the arguments
30# unmarshalled (in ipcrecv) before the desired function is called normally.
31# The function return values are then marshalled and written into another pipe
32# (again in ipcrecv) when is later read from and unmarshalled (in runnerar)
33# before being returned to the caller.
34
35package runner;
36
37use strict;
38use warnings;
39use 5.006;
40
41BEGIN {
42    use base qw(Exporter);
43
44    our @EXPORT = qw(
45        checktestcmd
46        prepro
47        readtestkeywords
48        restore_test_env
49        runner_init
50        runnerac_clearlocks
51        runnerac_shutdown
52        runnerac_stopservers
53        runnerac_test_preprocess
54        runnerac_test_run
55        runnerar
56        runnerar_ready
57        stderrfilename
58        stdoutfilename
59        $DBGCURL
60        $gdb
61        $gdbthis
62        $gdbxwin
63        $shallow
64        $tortalloc
65        $valgrind_logfile
66        $valgrind_tool
67    );
68
69    # these are for debugging only
70    our @EXPORT_OK = qw(
71        singletest_preprocess
72    );
73}
74
75use B qw(
76    svref_2object
77    );
78use Storable qw(
79    freeze
80    thaw
81    );
82
83use pathhelp qw(
84    exe_ext
85    );
86use processhelp qw(
87    portable_sleep
88    );
89use servers qw(
90    checkcmd
91    clearlocks
92    initserverconfig
93    serverfortest
94    stopserver
95    stopservers
96    subvariables
97    );
98use getpart;
99use globalconfig;
100use testutil qw(
101    clearlogs
102    logmsg
103    runclient
104    shell_quote
105    subbase64
106    subsha256base64file
107    substrippemfile
108    subnewlines
109    );
110use valgrind;
111
112
113#######################################################################
114# Global variables set elsewhere but used only by this package
115# These may only be set *before* runner_init is called
116our $DBGCURL=$CURL; #"../src/.libs/curl";  # alternative for debugging
117our $valgrind_logfile="--log-file";  # the option name for valgrind >=3
118our $valgrind_tool="--tool=memcheck";
119our $gdb = checktestcmd("gdb");
120our $gdbthis = 0;  # run test case with debugger (gdb or lldb)
121our $gdbxwin;      # use windowed gdb when using gdb
122
123# torture test variables
124our $shallow;
125our $tortalloc;
126
127# local variables
128my %oldenv;       # environment variables before test is started
129my $UNITDIR="./unit";
130my $CURLLOG="$LOGDIR/commands.log"; # all command lines run
131my $defserverlogslocktimeout = 5; # timeout to await server logs lock removal
132my $defpostcommanddelay = 0; # delay between command and postcheck sections
133my $multiprocess;   # nonzero with a separate test runner process
134
135# pipes
136my $runnerr;        # pipe that runner reads from
137my $runnerw;        # pipe that runner writes to
138
139# per-runner variables, indexed by runner ID; these are used by controller only
140my %controllerr;    # pipe that controller reads from
141my %controllerw;    # pipe that controller writes to
142
143# redirected stdout/stderr to these files
144sub stdoutfilename {
145    my ($logdir, $testnum)=@_;
146    return "$logdir/stdout$testnum";
147}
148
149sub stderrfilename {
150    my ($logdir, $testnum)=@_;
151    return "$logdir/stderr$testnum";
152}
153
154#######################################################################
155# Initialize the runner and prepare it to run tests
156# The runner ID returned by this function must be passed into the other
157# runnerac_* functions
158# Called by controller
159sub runner_init {
160    my ($logdir, $jobs)=@_;
161
162    $multiprocess = !!$jobs;
163
164    # enable memory debugging if curl is compiled with it
165    $ENV{'CURL_MEMDEBUG'} = "$logdir/$MEMDUMP";
166    $ENV{'CURL_ENTROPY'}="12345678";
167    $ENV{'CURL_FORCETIME'}=1; # for debug NTLM magic
168    $ENV{'CURL_GLOBAL_INIT'}=1; # debug curl_global_init/cleanup use
169    $ENV{'HOME'}=$pwd;
170    $ENV{'CURL_HOME'}=$ENV{'HOME'};
171    $ENV{'XDG_CONFIG_HOME'}=$ENV{'HOME'};
172    $ENV{'COLUMNS'}=79; # screen width!
173
174    # Incorporate the $logdir into the random seed and re-seed the PRNG.
175    # This gives each runner a unique yet consistent seed which provides
176    # more unique port number selection in each runner, yet is deterministic
177    # across runs.
178    $randseed += unpack('%16C*', $logdir);
179    srand $randseed;
180
181    # create pipes for communication with runner
182    my ($thisrunnerr, $thiscontrollerw, $thiscontrollerr, $thisrunnerw);
183    pipe $thisrunnerr, $thiscontrollerw;
184    pipe $thiscontrollerr, $thisrunnerw;
185
186    my $thisrunnerid;
187    if($multiprocess) {
188        # Create a separate process in multiprocess mode
189        my $child = fork();
190        if(0 == $child) {
191            # TODO: set up better signal handlers
192            $SIG{INT} = 'IGNORE';
193            $SIG{TERM} = 'IGNORE';
194            eval {
195                # some msys2 perl versions don't define SIGUSR1, also missing from Win32 Perl
196                $SIG{USR1} = 'IGNORE';
197            };
198
199            $thisrunnerid = $$;
200            print "Runner $thisrunnerid starting\n" if($verbose);
201
202            # Here we are the child (runner).
203            close($thiscontrollerw);
204            close($thiscontrollerr);
205            $runnerr = $thisrunnerr;
206            $runnerw = $thisrunnerw;
207
208            # Set this directory as ours
209            $LOGDIR = $logdir;
210            mkdir("$LOGDIR/$PIDDIR", 0777);
211            mkdir("$LOGDIR/$LOCKDIR", 0777);
212
213            # Initialize various server variables
214            initserverconfig();
215
216            # handle IPC calls
217            event_loop();
218
219            # Can't rely on logmsg here in case it's buffered
220            print "Runner $thisrunnerid exiting\n" if($verbose);
221
222            # To reach this point, either the controller has sent
223            # runnerac_stopservers() and runnerac_shutdown() or we have called
224            # runnerabort(). In both cases, there are no more of our servers
225            # running and we can safely exit.
226            exit 0;
227        }
228
229        # Here we are the parent (controller).
230        close($thisrunnerw);
231        close($thisrunnerr);
232
233        $thisrunnerid = $child;
234
235    } else {
236        # Create our pid directory
237        mkdir("$LOGDIR/$PIDDIR", 0777);
238
239        # Don't create a separate process
240        $thisrunnerid = "integrated";
241    }
242
243    $controllerw{$thisrunnerid} = $thiscontrollerw;
244    $runnerr = $thisrunnerr;
245    $runnerw = $thisrunnerw;
246    $controllerr{$thisrunnerid} = $thiscontrollerr;
247
248    return $thisrunnerid;
249}
250
251#######################################################################
252# Loop to execute incoming IPC calls until the shutdown call
253sub event_loop {
254    while () {
255        if(ipcrecv()) {
256            last;
257        }
258    }
259}
260
261#######################################################################
262# Check for a command in the PATH of the machine running curl.
263#
264sub checktestcmd {
265    my ($cmd)=@_;
266    my @testpaths=("$LIBDIR/.libs", "$LIBDIR");
267    return checkcmd($cmd, @testpaths);
268}
269
270# See if Valgrind should actually be used
271sub use_valgrind {
272    if($valgrind) {
273        my @valgrindoption = getpart("verify", "valgrind");
274        if((!@valgrindoption) || ($valgrindoption[0] !~ /disable/)) {
275            return 1;
276        }
277    }
278    return 0;
279}
280
281# Massage the command result code into a useful form
282sub normalize_cmdres {
283    my $cmdres = $_[0];
284    my $signal_num  = $cmdres & 127;
285    my $dumped_core = $cmdres & 128;
286
287    if(!$anyway && ($signal_num || $dumped_core)) {
288        $cmdres = 1000;
289    }
290    else {
291        $cmdres >>= 8;
292        $cmdres = (2000 + $signal_num) if($signal_num && !$cmdres);
293    }
294    return ($cmdres, $dumped_core);
295}
296
297# 'prepro' processes the input array and replaces %-variables in the array
298# etc. Returns the processed version of the array
299sub prepro {
300    my $testnum = shift;
301    my (@entiretest) = @_;
302    my $show = 1;
303    my @out;
304    my $data_crlf;
305    my @pshow;
306    my @altshow;
307    my $plvl;
308    my $line;
309    for my $s (@entiretest) {
310        my $f = $s;
311        $line++;
312        if($s =~ /^ *%if ([A-Za-z0-9!_-]*)/) {
313            my $cond = $1;
314            my $rev = 0;
315
316            if($cond =~ /^!(.*)/) {
317                $cond = $1;
318                $rev = 1;
319            }
320            $rev ^= $feature{$cond} ? 1 : 0;
321            push @pshow, $show; # push the previous state
322            $plvl++;
323            if($show) {
324                # only if this was showing before we can allow the alternative
325                # to go showing as well
326                push @altshow, $rev ^ 1; # push the reversed show state
327            }
328            else {
329                push @altshow, 0; # the alt should still hide
330            }
331            if($show) {
332                # we only allow show if already showing
333                $show = $rev;
334            }
335            next;
336        }
337        elsif($s =~ /^ *%else/) {
338            if(!$plvl) {
339                print STDERR "error: test$testnum:$line: %else no %if\n";
340                last;
341            }
342            $show = pop @altshow;
343            push @altshow, $show; # put it back for consistency
344            next;
345        }
346        elsif($s =~ /^ *%endif/) {
347            if(!$plvl--) {
348                print STDERR "error: test$testnum:$line: %endif had no %if\n";
349                last;
350            }
351            $show = pop @pshow;
352            pop @altshow; # not used here but we must pop it
353            next;
354        }
355        if($show) {
356            # The processor does CRLF replacements in the <data*> sections if
357            # necessary since those parts might be read by separate servers.
358            if($s =~ /^ *<data(.*)\>/) {
359                if($1 =~ /crlf="yes"/ ||
360                   ($feature{"hyper"} && ($keywords{"HTTP"} || $keywords{"HTTPS"}))) {
361                    $data_crlf = 1;
362                }
363            }
364            elsif(($s =~ /^ *<\/data/) && $data_crlf) {
365                $data_crlf = 0;
366            }
367            subvariables(\$s, $testnum, "%");
368            subbase64(\$s);
369            subsha256base64file(\$s);
370            substrippemfile(\$s);
371            subnewlines(0, \$s) if($data_crlf);
372            push @out, $s;
373        }
374    }
375    return @out;
376}
377
378
379#######################################################################
380# Load test keywords into %keywords hash
381#
382sub readtestkeywords {
383    my @info_keywords = getpart("info", "keywords");
384
385    # Clear the list of keywords from the last test
386    %keywords = ();
387    for my $k (@info_keywords) {
388        chomp $k;
389        $keywords{$k} = 1;
390    }
391}
392
393
394#######################################################################
395# Return a list of log locks that still exist
396#
397sub logslocked {
398    opendir(my $lockdir, "$LOGDIR/$LOCKDIR");
399    my @locks;
400    foreach (readdir $lockdir) {
401        if(/^(.*)\.lock$/) {
402            push @locks, $1;
403        }
404    }
405    return @locks;
406}
407
408#######################################################################
409# Wait log locks to be unlocked
410#
411sub waitlockunlock {
412    # If a server logs advisor read lock file exists, it is an indication
413    # that the server has not yet finished writing out all its log files,
414    # including server request log files used for protocol verification.
415    # So, if the lock file exists the script waits here a certain amount
416    # of time until the server removes it, or the given time expires.
417    my $serverlogslocktimeout = shift;
418
419    if($serverlogslocktimeout) {
420        my $lockretry = $serverlogslocktimeout * 20;
421        my @locks;
422        while((@locks = logslocked()) && $lockretry--) {
423            portable_sleep(0.05);
424        }
425        if(($lockretry < 0) &&
426           ($serverlogslocktimeout >= $defserverlogslocktimeout)) {
427            logmsg "Warning: server logs lock timeout ",
428                   "($serverlogslocktimeout seconds) expired (locks: " .
429                   join(", ", @locks) . ")\n";
430        }
431    }
432}
433
434#######################################################################
435# Memory allocation test and failure torture testing.
436#
437sub torture {
438    my ($testcmd, $testnum, $gdbline) = @_;
439
440    # remove memdump first to be sure we get a new nice and clean one
441    unlink("$LOGDIR/$MEMDUMP");
442
443    # First get URL from test server, ignore the output/result
444    runclient($testcmd);
445
446    logmsg " CMD: $testcmd\n" if($verbose);
447
448    # memanalyze -v is our friend, get the number of allocations made
449    my $count=0;
450    my @out = `$memanalyze -v "$LOGDIR/$MEMDUMP"`;
451    for(@out) {
452        if(/^Operations: (\d+)/) {
453            $count = $1;
454            last;
455        }
456    }
457    if(!$count) {
458        logmsg " found no functions to make fail\n";
459        return 0;
460    }
461
462    my @ttests = (1 .. $count);
463    if($shallow && ($shallow < $count)) {
464        my $discard = scalar(@ttests) - $shallow;
465        my $percent = sprintf("%.2f%%", $shallow * 100 / scalar(@ttests));
466        logmsg " $count functions found, but only fail $shallow ($percent)\n";
467        while($discard) {
468            my $rm;
469            do {
470                # find a test to discard
471                $rm = rand(scalar(@ttests));
472            } while(!$ttests[$rm]);
473            $ttests[$rm] = undef;
474            $discard--;
475        }
476    }
477    else {
478        logmsg " $count functions to make fail\n";
479    }
480
481    for (@ttests) {
482        my $limit = $_;
483        my $fail;
484        my $dumped_core;
485
486        if(!defined($limit)) {
487            # --shallow can undefine them
488            next;
489        }
490        if($tortalloc && ($tortalloc != $limit)) {
491            next;
492        }
493
494        if($verbose) {
495            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
496                localtime(time());
497            my $now = sprintf("%02d:%02d:%02d ", $hour, $min, $sec);
498            logmsg "Fail function no: $limit at $now\r";
499        }
500
501        # make the memory allocation function number $limit return failure
502        $ENV{'CURL_MEMLIMIT'} = $limit;
503
504        # remove memdump first to be sure we get a new nice and clean one
505        unlink("$LOGDIR/$MEMDUMP");
506
507        my $cmd = $testcmd;
508        if($valgrind && !$gdbthis) {
509            my @valgrindoption = getpart("verify", "valgrind");
510            if((!@valgrindoption) || ($valgrindoption[0] !~ /disable/)) {
511                my $valgrindcmd = "$valgrind ";
512                $valgrindcmd .= "$valgrind_tool " if($valgrind_tool);
513                $valgrindcmd .= "--quiet --leak-check=yes ";
514                $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp ";
515                # $valgrindcmd .= "--gen-suppressions=all ";
516                $valgrindcmd .= "--num-callers=16 ";
517                $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum";
518                $cmd = "$valgrindcmd $testcmd";
519            }
520        }
521        logmsg "*** Function number $limit is now set to fail ***\n" if($gdbthis);
522
523        my $ret = 0;
524        if($gdbthis) {
525            runclient($gdbline);
526        }
527        else {
528            $ret = runclient($cmd);
529        }
530        #logmsg "$_ Returned " . ($ret >> 8) . "\n";
531
532        # Now clear the variable again
533        delete $ENV{'CURL_MEMLIMIT'} if($ENV{'CURL_MEMLIMIT'});
534
535        if(-r "core") {
536            # there's core file present now!
537            logmsg " core dumped\n";
538            $dumped_core = 1;
539            $fail = 2;
540        }
541
542        if($valgrind) {
543            my @e = valgrindparse("$LOGDIR/valgrind$testnum");
544            if(@e && $e[0]) {
545                if($automakestyle) {
546                    logmsg "FAIL: torture $testnum - valgrind\n";
547                }
548                else {
549                    logmsg " valgrind ERROR ";
550                    logmsg @e;
551                }
552                $fail = 1;
553            }
554        }
555
556        # verify that it returns a proper error code, doesn't leak memory
557        # and doesn't core dump
558        if(($ret & 255) || ($ret >> 8) >= 128) {
559            logmsg " system() returned $ret\n";
560            $fail=1;
561        }
562        else {
563            my @memdata=`$memanalyze "$LOGDIR/$MEMDUMP"`;
564            my $leak=0;
565            for(@memdata) {
566                if($_ ne "") {
567                    # well it could be other memory problems as well, but
568                    # we call it leak for short here
569                    $leak=1;
570                }
571            }
572            if($leak) {
573                logmsg "** MEMORY FAILURE\n";
574                logmsg @memdata;
575                logmsg `$memanalyze -l "$LOGDIR/$MEMDUMP"`;
576                $fail = 1;
577            }
578        }
579        if($fail) {
580            logmsg " $testnum: torture FAILED: function number $limit in test.\n",
581            " invoke with \"-t$limit\" to repeat this single case.\n";
582            stopservers($verbose);
583            return 1;
584        }
585    }
586
587    logmsg "\n" if($verbose);
588    logmsg "torture OK\n";
589    return 0;
590}
591
592
593#######################################################################
594# restore environment variables that were modified in test
595sub restore_test_env {
596    my $deleteoldenv = $_[0];   # 1 to delete the saved contents after restore
597    foreach my $var (keys %oldenv) {
598        if($oldenv{$var} eq 'notset') {
599            delete $ENV{$var} if($ENV{$var});
600        }
601        else {
602            $ENV{$var} = $oldenv{$var};
603        }
604        if($deleteoldenv) {
605            delete $oldenv{$var};
606        }
607    }
608}
609
610
611#######################################################################
612# Start the servers needed to run this test case
613sub singletest_startservers {
614    my ($testnum, $testtimings) = @_;
615
616    # remove old test server files before servers are started/verified
617    unlink("$LOGDIR/$SERVERCMD");
618    unlink("$LOGDIR/$SERVERIN");
619    unlink("$LOGDIR/$PROXYIN");
620
621    # timestamp required servers verification start
622    $$testtimings{"timesrvrini"} = Time::HiRes::time();
623
624    my $why;
625    my $error;
626    if (!$listonly) {
627        my @what = getpart("client", "server");
628        if(!$what[0]) {
629            warn "Test case $testnum has no server(s) specified";
630            $why = "no server specified";
631            $error = -1;
632        } else {
633            my $err;
634            ($why, $err) = serverfortest(@what);
635            if($err == 1) {
636                # Error indicates an actual problem starting the server
637                $error = -2;
638            } else {
639                $error = -1;
640            }
641        }
642    }
643
644    # timestamp required servers verification end
645    $$testtimings{"timesrvrend"} = Time::HiRes::time();
646
647    return ($why, $error);
648}
649
650
651#######################################################################
652# Generate preprocessed test file
653sub singletest_preprocess {
654    my $testnum = $_[0];
655
656    # Save a preprocessed version of the entire test file. This allows more
657    # "basic" test case readers to enjoy variable replacements.
658    my @entiretest = fulltest();
659    my $otest = "$LOGDIR/test$testnum";
660
661    @entiretest = prepro($testnum, @entiretest);
662
663    # save the new version
664    open(my $fulltesth, ">", "$otest") || die "Failure writing test file";
665    foreach my $bytes (@entiretest) {
666        print $fulltesth pack('a*', $bytes) or die "Failed to print '$bytes': $!";
667    }
668    close($fulltesth) || die "Failure writing test file";
669
670    # in case the process changed the file, reload it
671    loadtest("$LOGDIR/test${testnum}");
672}
673
674
675#######################################################################
676# Set up the test environment to run this test case
677sub singletest_setenv {
678    my @setenv = getpart("client", "setenv");
679    foreach my $s (@setenv) {
680        chomp $s;
681        if($s =~ /([^=]*)(.*)/) {
682            my ($var, $content) = ($1, $2);
683            # remember current setting, to restore it once test runs
684            $oldenv{$var} = ($ENV{$var})?"$ENV{$var}":'notset';
685
686            if($content =~ /^=(.*)/) {
687                # assign it
688                $content = $1;
689
690                if($var =~ /^LD_PRELOAD/) {
691                    if(exe_ext('TOOL') && (exe_ext('TOOL') eq '.exe')) {
692                        logmsg "Skipping LD_PRELOAD due to lack of OS support\n" if($verbose);
693                        next;
694                    }
695                    if($feature{"Debug"} || !$has_shared) {
696                        logmsg "Skipping LD_PRELOAD due to no release shared build\n" if($verbose);
697                        next;
698                    }
699                }
700                $ENV{$var} = "$content";
701                logmsg "setenv $var = $content\n" if($verbose);
702            }
703            else {
704                # remove it
705                delete $ENV{$var} if($ENV{$var});
706            }
707
708        }
709    }
710    if($proxy_address) {
711        $ENV{http_proxy} = $proxy_address;
712        $ENV{HTTPS_PROXY} = $proxy_address;
713    }
714}
715
716
717#######################################################################
718# Check that test environment is fine to run this test case
719sub singletest_precheck {
720    my $testnum = $_[0];
721    my $why;
722    my @precheck = getpart("client", "precheck");
723    if(@precheck) {
724        my $cmd = $precheck[0];
725        chomp $cmd;
726        if($cmd) {
727            my @p = split(/ /, $cmd);
728            if($p[0] !~ /\//) {
729                # the first word, the command, does not contain a slash so
730                # we will scan the "improved" PATH to find the command to
731                # be able to run it
732                my $fullp = checktestcmd($p[0]);
733
734                if($fullp) {
735                    $p[0] = $fullp;
736                }
737                $cmd = join(" ", @p);
738            }
739
740            my @o = `$cmd 2> $LOGDIR/precheck-$testnum`;
741            if($o[0]) {
742                $why = $o[0];
743                $why =~ s/[\r\n]//g;
744            }
745            elsif($?) {
746                $why = "precheck command error";
747            }
748            logmsg "prechecked $cmd\n" if($verbose);
749        }
750    }
751    return $why;
752}
753
754
755#######################################################################
756# Prepare the test environment to run this test case
757sub singletest_prepare {
758    my ($testnum) = @_;
759
760    if($feature{"TrackMemory"}) {
761        unlink("$LOGDIR/$MEMDUMP");
762    }
763    unlink("core");
764
765    # remove server output logfiles after servers are started/verified
766    unlink("$LOGDIR/$SERVERIN");
767    unlink("$LOGDIR/$PROXYIN");
768
769    # if this section exists, it might be FTP server instructions:
770    my @ftpservercmd = getpart("reply", "servercmd");
771    push @ftpservercmd, "Testnum $testnum\n";
772    # write the instructions to file
773    writearray("$LOGDIR/$SERVERCMD", \@ftpservercmd);
774
775    # create (possibly-empty) files before starting the test
776    for my $partsuffix (('', '1', '2', '3', '4')) {
777        my @inputfile=getpart("client", "file".$partsuffix);
778        my %fileattr = getpartattr("client", "file".$partsuffix);
779        my $filename=$fileattr{'name'};
780        if(@inputfile || $filename) {
781            if(!$filename) {
782                logmsg " $testnum: IGNORED: Section client=>file has no name attribute\n";
783                return -1;
784            }
785            my $fileContent = join('', @inputfile);
786
787            # make directories if needed
788            my $path = $filename;
789            # cut off the file name part
790            $path =~ s/^(.*)\/[^\/]*/$1/;
791            my @ldparts = split(/\//, $LOGDIR);
792            my $nparts = @ldparts;
793            my @parts = split(/\//, $path);
794            if(join("/", @parts[0..$nparts-1]) eq $LOGDIR) {
795                # the file is in $LOGDIR/
796                my $d = shift @parts;
797                for(@parts) {
798                    $d .= "/$_";
799                    mkdir $d; # 0777
800                }
801            }
802            if (open(my $outfile, ">", "$filename")) {
803                binmode $outfile; # for crapage systems, use binary
804                if($fileattr{'nonewline'}) {
805                    # cut off the final newline
806                    chomp($fileContent);
807                }
808                print $outfile $fileContent;
809                close($outfile);
810            } else {
811                logmsg "ERROR: cannot write $filename\n";
812            }
813        }
814    }
815    return 0;
816}
817
818
819#######################################################################
820# Run the test command
821sub singletest_run {
822    my ($testnum, $testtimings) = @_;
823
824    # get the command line options to use
825    my ($cmd, @blaha)= getpart("client", "command");
826    if($cmd) {
827        # make some nice replace operations
828        $cmd =~ s/\n//g; # no newlines please
829        # substitute variables in the command line
830    }
831    else {
832        # there was no command given, use something silly
833        $cmd="-";
834    }
835
836    my $CURLOUT="$LOGDIR/curl$testnum.out"; # curl output if not stdout
837
838    # if stdout section exists, we verify that the stdout contained this:
839    my $out="";
840    my %cmdhash = getpartattr("client", "command");
841    if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-output/)) {
842        #We may slap on --output!
843        if (!partexists("verify", "stdout") ||
844                ($cmdhash{'option'} && $cmdhash{'option'} =~ /force-output/)) {
845            $out=" --output $CURLOUT ";
846        }
847    }
848
849    my @codepieces = getpart("client", "tool");
850    my $tool="";
851    my $tool_name="";  # without exe extension
852    if(@codepieces) {
853        $tool_name = $codepieces[0];
854        chomp $tool_name;
855        $tool = $tool_name . exe_ext('TOOL');
856    }
857
858    my $disablevalgrind;
859    my $CMDLINE="";
860    my $cmdargs;
861    my $cmdtype = $cmdhash{'type'} || "default";
862    my $fail_due_event_based = $run_event_based;
863    if($cmdtype eq "perl") {
864        # run the command line prepended with "perl"
865        $cmdargs ="$cmd";
866        $CMDLINE = "$perl ";
867        $tool=$CMDLINE;
868        $disablevalgrind=1;
869    }
870    elsif($cmdtype eq "shell") {
871        # run the command line prepended with "/bin/sh"
872        $cmdargs ="$cmd";
873        $CMDLINE = "/bin/sh ";
874        $tool=$CMDLINE;
875        $disablevalgrind=1;
876    }
877    elsif(!$tool && !$keywords{"unittest"}) {
878        # run curl, add suitable command line options
879        my $inc="";
880        if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-include/)) {
881            $inc = " --include";
882        }
883        $cmdargs = "$out$inc ";
884
885        if($cmdhash{'option'} && ($cmdhash{'option'} =~ /binary-trace/)) {
886            $cmdargs .= "--trace $LOGDIR/trace$testnum ";
887        }
888        else {
889            $cmdargs .= "--trace-ascii $LOGDIR/trace$testnum ";
890        }
891        $cmdargs .= "--trace-config all ";
892        $cmdargs .= "--trace-time ";
893        if($run_event_based) {
894            $cmdargs .= "--test-event ";
895            $fail_due_event_based--;
896        }
897        if($run_duphandle) {
898            $cmdargs .= "--test-duphandle ";
899            my @dis = getpart("client", "disable");
900            if(@dis) {
901                chomp $dis[0] if($dis[0]);
902                if($dis[0] eq "test-duphandle") {
903                    # marked to not run with duphandle
904                    logmsg " $testnum: IGNORED: Can't run test-duphandle\n";
905                    return (-1, 0, 0, "", "", 0);
906                }
907            }
908        }
909        $cmdargs .= $cmd;
910        if ($proxy_address) {
911            $cmdargs .= " --proxy $proxy_address ";
912        }
913    }
914    else {
915        $cmdargs = " $cmd"; # $cmd is the command line for the test file
916        $CURLOUT = stdoutfilename($LOGDIR, $testnum); # sends received data to stdout
917
918        # Default the tool to a unit test with the same name as the test spec
919        if($keywords{"unittest"} && !$tool) {
920            $tool_name="unit$testnum";
921            $tool = $tool_name;
922        }
923
924        if($tool =~ /^lib/) {
925            if($bundle) {
926                $CMDLINE="$LIBDIR/libtests";
927            }
928            else {
929                $CMDLINE="$LIBDIR/$tool";
930            }
931        }
932        elsif($tool =~ /^unit/) {
933            if($bundle) {
934                $CMDLINE="$UNITDIR/units";
935            }
936            else {
937                $CMDLINE="$UNITDIR/$tool";
938            }
939        }
940
941        if(! -f $CMDLINE) {
942            logmsg " $testnum: IGNORED: The tool set in the test case for this: '$tool' does not exist\n";
943            return (-1, 0, 0, "", "", 0);
944        }
945
946        if($bundle) {
947            $CMDLINE.=" $tool_name";
948        }
949
950        $DBGCURL=$CMDLINE;
951    }
952
953    if($fail_due_event_based) {
954        logmsg " $testnum: IGNORED: This test cannot run event based\n";
955        return (-1, 0, 0, "", "", 0);
956    }
957
958    if($gdbthis) {
959        # gdb is incompatible with valgrind, so disable it when debugging
960        # Perhaps a better approach would be to run it under valgrind anyway
961        # with --db-attach=yes or --vgdb=yes.
962        $disablevalgrind=1;
963    }
964
965    my @stdintest = getpart("client", "stdin");
966
967    if(@stdintest) {
968        my $stdinfile="$LOGDIR/stdin-for-$testnum";
969
970        my %hash = getpartattr("client", "stdin");
971        if($hash{'nonewline'}) {
972            # cut off the final newline from the final line of the stdin data
973            chomp($stdintest[-1]);
974        }
975
976        writearray($stdinfile, \@stdintest);
977
978        $cmdargs .= " <$stdinfile";
979    }
980
981    if(!$tool) {
982        $CMDLINE=shell_quote($CURL);
983        if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-q/)) {
984            $CMDLINE .= " -q";
985        }
986    }
987
988    if(use_valgrind() && !$disablevalgrind) {
989        my $valgrindcmd = "$valgrind ";
990        $valgrindcmd .= "$valgrind_tool " if($valgrind_tool);
991        $valgrindcmd .= "--quiet --leak-check=yes ";
992        $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp ";
993        # $valgrindcmd .= "--gen-suppressions=all ";
994        $valgrindcmd .= "--num-callers=16 ";
995        $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum";
996        $CMDLINE = "$valgrindcmd $CMDLINE";
997    }
998
999    $CMDLINE .= "$cmdargs > " . stdoutfilename($LOGDIR, $testnum) .
1000                " 2> " . stderrfilename($LOGDIR, $testnum);
1001
1002    if($verbose) {
1003        logmsg "$CMDLINE\n";
1004    }
1005
1006    open(my $cmdlog, ">", $CURLLOG) || die "Failure writing log file";
1007    print $cmdlog "$CMDLINE\n";
1008    close($cmdlog) || die "Failure writing log file";
1009
1010    my $dumped_core;
1011    my $cmdres;
1012
1013    if($gdbthis) {
1014        my $gdbinit = "$TESTDIR/gdbinit$testnum";
1015        open(my $gdbcmd, ">", "$LOGDIR/gdbcmd") || die "Failure writing gdb file";
1016        if($gdbthis == 1) {
1017            # gdb mode
1018            print $gdbcmd "set args $cmdargs\n";
1019            print $gdbcmd "show args\n";
1020            print $gdbcmd "source $gdbinit\n" if -e $gdbinit;
1021        }
1022        else {
1023            # lldb mode
1024            print $gdbcmd "set args $cmdargs\n";
1025        }
1026        close($gdbcmd) || die "Failure writing gdb file";
1027    }
1028
1029    # Flush output.
1030    $| = 1;
1031
1032    # timestamp starting of test command
1033    $$testtimings{"timetoolini"} = Time::HiRes::time();
1034
1035    # run the command line we built
1036    if ($torture) {
1037        $cmdres = torture($CMDLINE,
1038                          $testnum,
1039                          "$gdb --directory $LIBDIR " . shell_quote($DBGCURL) . " -x $LOGDIR/gdbcmd");
1040    }
1041    elsif($gdbthis == 1) {
1042        # gdb
1043        my $GDBW = ($gdbxwin) ? "-w" : "";
1044        runclient("$gdb --directory $LIBDIR " . shell_quote($DBGCURL) . " $GDBW -x $LOGDIR/gdbcmd");
1045        $cmdres=0; # makes it always continue after a debugged run
1046    }
1047    elsif($gdbthis == 2) {
1048        # $gdb is "lldb"
1049        print "runs lldb -- $CURL $cmdargs\n";
1050        runclient("lldb -- $CURL $cmdargs");
1051        $cmdres=0; # makes it always continue after a debugged run
1052    }
1053    else {
1054        # Convert the raw result code into a more useful one
1055        ($cmdres, $dumped_core) = normalize_cmdres(runclient("$CMDLINE"));
1056    }
1057
1058    # timestamp finishing of test command
1059    $$testtimings{"timetoolend"} = Time::HiRes::time();
1060
1061    return (0, $cmdres, $dumped_core, $CURLOUT, $tool, use_valgrind() && !$disablevalgrind);
1062}
1063
1064
1065#######################################################################
1066# Clean up after test command
1067sub singletest_clean {
1068    my ($testnum, $dumped_core, $testtimings)=@_;
1069
1070    if(!$dumped_core) {
1071        if(-r "core") {
1072            # there's core file present now!
1073            $dumped_core = 1;
1074        }
1075    }
1076
1077    if($dumped_core) {
1078        logmsg "core dumped\n";
1079        if(0 && $gdb) {
1080            logmsg "running gdb for post-mortem analysis:\n";
1081            open(my $gdbcmd, ">", "$LOGDIR/gdbcmd2") || die "Failure writing gdb file";
1082            print $gdbcmd "bt\n";
1083            close($gdbcmd) || die "Failure writing gdb file";
1084            runclient("$gdb --directory libtest -x $LOGDIR/gdbcmd2 -batch " . shell_quote($DBGCURL) . " core ");
1085     #       unlink("$LOGDIR/gdbcmd2");
1086        }
1087    }
1088
1089    my $serverlogslocktimeout = $defserverlogslocktimeout;
1090    my %cmdhash = getpartattr("client", "command");
1091    if($cmdhash{'timeout'}) {
1092        # test is allowed to override default server logs lock timeout
1093        if($cmdhash{'timeout'} =~ /(\d+)/) {
1094            $serverlogslocktimeout = $1 if($1 >= 0);
1095        }
1096    }
1097
1098    # Test harness ssh server does not have this synchronization mechanism,
1099    # this implies that some ssh server based tests might need a small delay
1100    # once that the client command has run to avoid false test failures.
1101    #
1102    # gnutls-serv also lacks this synchronization mechanism, so gnutls-serv
1103    # based tests might need a small delay once that the client command has
1104    # run to avoid false test failures.
1105    my $postcommanddelay = $defpostcommanddelay;
1106    if($cmdhash{'delay'}) {
1107        # test is allowed to specify a delay after command is executed
1108        if($cmdhash{'delay'} =~ /(\d+)/) {
1109            $postcommanddelay = $1 if($1 > 0);
1110        }
1111    }
1112
1113    portable_sleep($postcommanddelay) if($postcommanddelay);
1114
1115    my @killtestservers = getpart("client", "killserver");
1116    if(@killtestservers) {
1117        foreach my $server (@killtestservers) {
1118            chomp $server;
1119            if(stopserver($server)) {
1120                logmsg " $testnum: killserver FAILED\n";
1121                return 1; # normal error if asked to fail on unexpected alive
1122            }
1123        }
1124    }
1125
1126    # wait for any servers left running to release their locks
1127    waitlockunlock($serverlogslocktimeout);
1128
1129    # timestamp removal of server logs advisor read lock
1130    $$testtimings{"timesrvrlog"} = Time::HiRes::time();
1131
1132    # test definition might instruct to stop some servers
1133    # stop also all servers relative to the given one
1134
1135    return 0;
1136}
1137
1138#######################################################################
1139# Verify that the postcheck succeeded
1140sub singletest_postcheck {
1141    my ($testnum)=@_;
1142
1143    # run the postcheck command
1144    my @postcheck= getpart("client", "postcheck");
1145    if(@postcheck) {
1146        die "test$testnum uses client/postcheck";
1147    }
1148
1149    @postcheck= getpart("verify", "postcheck");
1150    if(@postcheck) {
1151        my $cmd = join("", @postcheck);
1152        chomp $cmd;
1153        if($cmd) {
1154            logmsg "postcheck $cmd\n" if($verbose);
1155            my $rc = runclient("$cmd");
1156            # Must run the postcheck command in torture mode in order
1157            # to clean up, but the result can't be relied upon.
1158            if($rc != 0 && !$torture) {
1159                logmsg " $testnum: postcheck FAILED\n";
1160                return -1;
1161            }
1162        }
1163    }
1164    return 0;
1165}
1166
1167
1168
1169###################################################################
1170# Get ready to run a single test case
1171sub runner_test_preprocess {
1172    my ($testnum)=@_;
1173    my %testtimings;
1174
1175    if(clearlogs()) {
1176        logmsg "Warning: log messages were lost\n";
1177    }
1178
1179    # timestamp test preparation start
1180    # TODO: this metric now shows only a portion of the prep time; better would
1181    # be to time singletest_preprocess below instead
1182    $testtimings{"timeprepini"} = Time::HiRes::time();
1183
1184    ###################################################################
1185    # Load test metadata
1186    # ignore any error here--if there were one, it would have been
1187    # caught during the selection phase and this test would not be
1188    # running now
1189    loadtest("${TESTDIR}/test${testnum}");
1190    readtestkeywords();
1191
1192    ###################################################################
1193    # Restore environment variables that were modified in a previous run.
1194    # Test definition may instruct to (un)set environment vars.
1195    restore_test_env(1);
1196
1197    ###################################################################
1198    # Start the servers needed to run this test case
1199    my ($why, $error) = singletest_startservers($testnum, \%testtimings);
1200
1201    # make sure no locks left for responsive test
1202    waitlockunlock($defserverlogslocktimeout);
1203
1204    if(!$why) {
1205
1206        ###############################################################
1207        # Generate preprocessed test file
1208        # This must be done after the servers are started so server
1209        # variables are available for substitution.
1210        singletest_preprocess($testnum);
1211
1212        ###############################################################
1213        # Set up the test environment to run this test case
1214        singletest_setenv();
1215
1216        ###############################################################
1217        # Check that the test environment is fine to run this test case
1218        if (!$listonly) {
1219            $why = singletest_precheck($testnum);
1220            $error = -1;
1221        }
1222    }
1223    return ($why, $error, clearlogs(), \%testtimings);
1224}
1225
1226
1227###################################################################
1228# Run a single test case with an environment that already been prepared
1229# Returns 0=success, -1=skippable failure, -2=permanent error,
1230#   1=unskippable test failure, as first integer, plus any log messages,
1231#   plus more return values when error is 0
1232sub runner_test_run {
1233    my ($testnum)=@_;
1234
1235    if(clearlogs()) {
1236        logmsg "Warning: log messages were lost\n";
1237    }
1238
1239    #######################################################################
1240    # Prepare the test environment to run this test case
1241    my $error = singletest_prepare($testnum);
1242    if($error) {
1243        return (-2, clearlogs());
1244    }
1245
1246    #######################################################################
1247    # Run the test command
1248    my %testtimings;
1249    my $cmdres;
1250    my $dumped_core;
1251    my $CURLOUT;
1252    my $tool;
1253    my $usedvalgrind;
1254    ($error, $cmdres, $dumped_core, $CURLOUT, $tool, $usedvalgrind) = singletest_run($testnum, \%testtimings);
1255    if($error) {
1256        return (-2, clearlogs(), \%testtimings);
1257    }
1258
1259    #######################################################################
1260    # Clean up after test command
1261    $error = singletest_clean($testnum, $dumped_core, \%testtimings);
1262    if($error) {
1263        return ($error, clearlogs(), \%testtimings);
1264    }
1265
1266    #######################################################################
1267    # Verify that the postcheck succeeded
1268    $error = singletest_postcheck($testnum);
1269    if($error) {
1270        return ($error, clearlogs(), \%testtimings);
1271    }
1272
1273    #######################################################################
1274    # restore environment variables that were modified
1275    restore_test_env(0);
1276
1277    return (0, clearlogs(), \%testtimings, $cmdres, $CURLOUT, $tool, $usedvalgrind);
1278}
1279
1280# Async call runner_clearlocks
1281# Called by controller
1282sub runnerac_clearlocks {
1283    return controlleripccall(\&runner_clearlocks, @_);
1284}
1285
1286# Async call runner_shutdown
1287# This call does NOT generate an IPC response and must be the last IPC call
1288# received.
1289# Called by controller
1290sub runnerac_shutdown {
1291    my ($runnerid)=$_[0];
1292    my $err = controlleripccall(\&runner_shutdown, @_);
1293
1294    # These have no more use
1295    close($controllerw{$runnerid});
1296    undef $controllerw{$runnerid};
1297    close($controllerr{$runnerid});
1298    undef $controllerr{$runnerid};
1299    return $err;
1300}
1301
1302# Async call of runner_stopservers
1303# Called by controller
1304sub runnerac_stopservers {
1305    return controlleripccall(\&runner_stopservers, @_);
1306}
1307
1308# Async call of runner_test_preprocess
1309# Called by controller
1310sub runnerac_test_preprocess {
1311    return controlleripccall(\&runner_test_preprocess, @_);
1312}
1313
1314# Async call of runner_test_run
1315# Called by controller
1316sub runnerac_test_run {
1317    return controlleripccall(\&runner_test_run, @_);
1318}
1319
1320###################################################################
1321# Call an arbitrary function via IPC
1322# The first argument is the function reference, the second is the runner ID
1323# Returns 0 on success, -1 on error writing to runner
1324# Called by controller (indirectly, via a more specific function)
1325sub controlleripccall {
1326    my $funcref = shift @_;
1327    my $runnerid = shift @_;
1328    # Get the name of the function from the reference
1329    my $cv = svref_2object($funcref);
1330    my $gv = $cv->GV;
1331    # Prepend the name to the function arguments so it's marshalled along with them
1332    unshift @_, $gv->NAME;
1333    # Marshall the arguments into a flat string
1334    my $margs = freeze \@_;
1335
1336    # Send IPC call via pipe
1337    my $err;
1338    while(! defined ($err = syswrite($controllerw{$runnerid}, (pack "L", length($margs)) . $margs)) || $err <= 0) {
1339        if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
1340            # Runner has likely died
1341            return -1;
1342        }
1343        # system call was interrupted, probably by ^C; restart it so we stay in sync
1344    }
1345
1346    if(!$multiprocess) {
1347        # Call the remote function here in single process mode
1348        ipcrecv();
1349     }
1350     return 0;
1351}
1352
1353###################################################################
1354# Receive async response of a previous call via IPC
1355# The first return value is the runner ID or undef on error
1356# Called by controller
1357sub runnerar {
1358    my ($runnerid) = @_;
1359    my $err;
1360    my $datalen;
1361    while(! defined ($err = sysread($controllerr{$runnerid}, $datalen, 4)) || $err <= 0) {
1362        if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
1363            # Runner is likely dead and closed the pipe
1364            return undef;
1365        }
1366        # system call was interrupted, probably by ^C; restart it so we stay in sync
1367    }
1368    my $len=unpack("L", $datalen);
1369    my $buf;
1370    while(! defined ($err = sysread($controllerr{$runnerid}, $buf, $len)) || $err <= 0) {
1371        if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
1372            # Runner is likely dead and closed the pipe
1373            return undef;
1374        }
1375        # system call was interrupted, probably by ^C; restart it so we stay in sync
1376    }
1377
1378    # Decode response values
1379    my $resarrayref = thaw $buf;
1380
1381    # First argument is runner ID
1382    # TODO: remove this; it's unneeded since it's passed in
1383    unshift @$resarrayref, $runnerid;
1384    return @$resarrayref;
1385}
1386
1387###################################################################
1388# Returns runner ID if a response from an async call is ready or error
1389# First value is ready, second is error, however an error case shows up
1390# as ready in Linux, so you can't trust it.
1391# argument is 0 for nonblocking, undef for blocking, anything else for timeout
1392# Called by controller
1393sub runnerar_ready {
1394    my ($blocking) = @_;
1395    my $rin = "";
1396    my %idbyfileno;
1397    my $maxfileno=0;
1398    my @ready_runners = ();
1399    foreach my $p (keys(%controllerr)) {
1400        my $fd = fileno($controllerr{$p});
1401        vec($rin, $fd, 1) = 1;
1402        $idbyfileno{$fd} = $p;  # save the runner ID for each pipe fd
1403        if($fd > $maxfileno) {
1404            $maxfileno = $fd;
1405        }
1406    }
1407    $maxfileno || die "Internal error: no runners are available to wait on\n";
1408
1409    # Wait for any pipe from any runner to be ready
1410    # This may be interrupted and return EINTR, but this is ignored and the
1411    # caller will need to later call this function again.
1412    # TODO: this is relatively slow with hundreds of fds
1413    my $ein = $rin;
1414    if(select(my $rout=$rin, undef, my $eout=$ein, $blocking) >= 1) {
1415        for my $fd (0..$maxfileno) {
1416            # Return an error condition first in case it's both
1417            if(vec($eout, $fd, 1)) {
1418                return (undef, $idbyfileno{$fd});
1419            }
1420            if(vec($rout, $fd, 1)) {
1421                push(@ready_runners, $idbyfileno{$fd});
1422            }
1423        }
1424        die "Internal pipe readiness inconsistency\n" if(!@ready_runners);
1425        return (@ready_runners, undef);
1426    }
1427    return (undef, undef);
1428}
1429
1430
1431###################################################################
1432# Cleanly abort and exit the runner
1433# This uses print since there is no longer any controller to write logs.
1434sub runnerabort{
1435    print "Controller is gone: runner $$ for $LOGDIR exiting\n";
1436    my ($error, $logs) = runner_stopservers();
1437    print $logs;
1438    runner_shutdown();
1439}
1440
1441###################################################################
1442# Receive an IPC call in the runner and execute it
1443# The IPC is read from the $runnerr pipe and the response is
1444# written to the $runnerw pipe
1445# Returns 0 if more IPC calls are expected or 1 if the runner should exit
1446sub ipcrecv {
1447    my $err;
1448    my $datalen;
1449    while(! defined ($err = sysread($runnerr, $datalen, 4)) || $err <= 0) {
1450        if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
1451            # pipe has closed; controller is gone and we must exit
1452            runnerabort();
1453            # Special case: no response will be forthcoming
1454            return 1;
1455        }
1456        # system call was interrupted, probably by ^C; restart it so we stay in sync
1457    }
1458    my $len=unpack("L", $datalen);
1459    my $buf;
1460    while(! defined ($err = sysread($runnerr, $buf, $len)) || $err <= 0) {
1461        if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
1462            # pipe has closed; controller is gone and we must exit
1463            runnerabort();
1464            # Special case: no response will be forthcoming
1465            return 1;
1466        }
1467        # system call was interrupted, probably by ^C; restart it so we stay in sync
1468    }
1469
1470    # Decode the function name and arguments
1471    my $argsarrayref = thaw $buf;
1472
1473    # The name of the function to call is the first argument
1474    my $funcname = shift @$argsarrayref;
1475
1476    # print "ipcrecv $funcname\n";
1477    # Synchronously call the desired function
1478    my @res;
1479    if($funcname eq "runner_clearlocks") {
1480        @res = runner_clearlocks(@$argsarrayref);
1481    }
1482    elsif($funcname eq "runner_shutdown") {
1483        runner_shutdown(@$argsarrayref);
1484        # Special case: no response will be forthcoming
1485        return 1;
1486    }
1487    elsif($funcname eq "runner_stopservers") {
1488        @res = runner_stopservers(@$argsarrayref);
1489    }
1490    elsif($funcname eq "runner_test_preprocess") {
1491        @res = runner_test_preprocess(@$argsarrayref);
1492    }
1493    elsif($funcname eq "runner_test_run") {
1494        @res = runner_test_run(@$argsarrayref);
1495    } else {
1496        die "Unknown IPC function $funcname\n";
1497    }
1498    # print "ipcrecv results\n";
1499
1500    # Marshall the results to return
1501    $buf = freeze \@res;
1502
1503    while(! defined ($err = syswrite($runnerw, (pack "L", length($buf)) . $buf)) || $err <= 0) {
1504        if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) {
1505            # pipe has closed; controller is gone and we must exit
1506            runnerabort();
1507            # Special case: no response will be forthcoming
1508            return 1;
1509        }
1510        # system call was interrupted, probably by ^C; restart it so we stay in sync
1511    }
1512
1513    return 0;
1514}
1515
1516###################################################################
1517# Kill the server processes that still have lock files in a directory
1518sub runner_clearlocks {
1519    my ($lockdir)=@_;
1520    if(clearlogs()) {
1521        logmsg "Warning: log messages were lost\n";
1522    }
1523    clearlocks($lockdir);
1524    return clearlogs();
1525}
1526
1527
1528###################################################################
1529# Kill all server processes
1530sub runner_stopservers {
1531    my $error = stopservers($verbose);
1532    my $logs = clearlogs();
1533    return ($error, $logs);
1534}
1535
1536###################################################################
1537# Shut down this runner
1538sub runner_shutdown {
1539    close($runnerr);
1540    undef $runnerr;
1541    close($runnerw);
1542    undef $runnerw;
1543}
1544
1545
15461;
1547