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 $data_crlf = 1; 361 } 362 } 363 elsif(($s =~ /^ *<\/data/) && $data_crlf) { 364 $data_crlf = 0; 365 } 366 subvariables(\$s, $testnum, "%"); 367 subbase64(\$s); 368 subsha256base64file(\$s); 369 substrippemfile(\$s); 370 subnewlines(0, \$s) if($data_crlf); 371 push @out, $s; 372 } 373 } 374 return @out; 375} 376 377 378####################################################################### 379# Load test keywords into %keywords hash 380# 381sub readtestkeywords { 382 my @info_keywords = getpart("info", "keywords"); 383 384 # Clear the list of keywords from the last test 385 %keywords = (); 386 for my $k (@info_keywords) { 387 chomp $k; 388 $keywords{$k} = 1; 389 } 390} 391 392 393####################################################################### 394# Return a list of log locks that still exist 395# 396sub logslocked { 397 opendir(my $lockdir, "$LOGDIR/$LOCKDIR"); 398 my @locks; 399 foreach (readdir $lockdir) { 400 if(/^(.*)\.lock$/) { 401 push @locks, $1; 402 } 403 } 404 return @locks; 405} 406 407####################################################################### 408# Wait log locks to be unlocked 409# 410sub waitlockunlock { 411 # If a server logs advisor read lock file exists, it is an indication 412 # that the server has not yet finished writing out all its log files, 413 # including server request log files used for protocol verification. 414 # So, if the lock file exists the script waits here a certain amount 415 # of time until the server removes it, or the given time expires. 416 my $serverlogslocktimeout = shift; 417 418 if($serverlogslocktimeout) { 419 my $lockretry = $serverlogslocktimeout * 20; 420 my @locks; 421 while((@locks = logslocked()) && $lockretry--) { 422 portable_sleep(0.05); 423 } 424 if(($lockretry < 0) && 425 ($serverlogslocktimeout >= $defserverlogslocktimeout)) { 426 logmsg "Warning: server logs lock timeout ", 427 "($serverlogslocktimeout seconds) expired (locks: " . 428 join(", ", @locks) . ")\n"; 429 } 430 } 431} 432 433####################################################################### 434# Memory allocation test and failure torture testing. 435# 436sub torture { 437 my ($testcmd, $testnum, $gdbline) = @_; 438 439 # remove memdump first to be sure we get a new nice and clean one 440 unlink("$LOGDIR/$MEMDUMP"); 441 442 # First get URL from test server, ignore the output/result 443 runclient($testcmd); 444 445 logmsg " CMD: $testcmd\n" if($verbose); 446 447 # memanalyze -v is our friend, get the number of allocations made 448 my $count=0; 449 my @out = `$memanalyze -v "$LOGDIR/$MEMDUMP"`; 450 for(@out) { 451 if(/^Operations: (\d+)/) { 452 $count = $1; 453 last; 454 } 455 } 456 if(!$count) { 457 logmsg " found no functions to make fail\n"; 458 return 0; 459 } 460 461 my @ttests = (1 .. $count); 462 if($shallow && ($shallow < $count)) { 463 my $discard = scalar(@ttests) - $shallow; 464 my $percent = sprintf("%.2f%%", $shallow * 100 / scalar(@ttests)); 465 logmsg " $count functions found, but only fail $shallow ($percent)\n"; 466 while($discard) { 467 my $rm; 468 do { 469 # find a test to discard 470 $rm = rand(scalar(@ttests)); 471 } while(!$ttests[$rm]); 472 $ttests[$rm] = undef; 473 $discard--; 474 } 475 } 476 else { 477 logmsg " $count functions to make fail\n"; 478 } 479 480 for (@ttests) { 481 my $limit = $_; 482 my $fail; 483 my $dumped_core; 484 485 if(!defined($limit)) { 486 # --shallow can undefine them 487 next; 488 } 489 if($tortalloc && ($tortalloc != $limit)) { 490 next; 491 } 492 493 if($verbose) { 494 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 495 localtime(time()); 496 my $now = sprintf("%02d:%02d:%02d ", $hour, $min, $sec); 497 logmsg "Fail function no: $limit at $now\r"; 498 } 499 500 # make the memory allocation function number $limit return failure 501 $ENV{'CURL_MEMLIMIT'} = $limit; 502 503 # remove memdump first to be sure we get a new nice and clean one 504 unlink("$LOGDIR/$MEMDUMP"); 505 506 my $cmd = $testcmd; 507 if($valgrind && !$gdbthis) { 508 my @valgrindoption = getpart("verify", "valgrind"); 509 if((!@valgrindoption) || ($valgrindoption[0] !~ /disable/)) { 510 my $valgrindcmd = "$valgrind "; 511 $valgrindcmd .= "$valgrind_tool " if($valgrind_tool); 512 $valgrindcmd .= "--quiet --leak-check=yes "; 513 $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp "; 514 # $valgrindcmd .= "--gen-suppressions=all "; 515 $valgrindcmd .= "--num-callers=16 "; 516 $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum"; 517 $cmd = "$valgrindcmd $testcmd"; 518 } 519 } 520 logmsg "*** Function number $limit is now set to fail ***\n" if($gdbthis); 521 522 my $ret = 0; 523 if($gdbthis) { 524 runclient($gdbline); 525 } 526 else { 527 $ret = runclient($cmd); 528 } 529 #logmsg "$_ Returned " . ($ret >> 8) . "\n"; 530 531 # Now clear the variable again 532 delete $ENV{'CURL_MEMLIMIT'} if($ENV{'CURL_MEMLIMIT'}); 533 534 if(-r "core") { 535 # there's core file present now! 536 logmsg " core dumped\n"; 537 $dumped_core = 1; 538 $fail = 2; 539 } 540 541 if($valgrind) { 542 my @e = valgrindparse("$LOGDIR/valgrind$testnum"); 543 if(@e && $e[0]) { 544 if($automakestyle) { 545 logmsg "FAIL: torture $testnum - valgrind\n"; 546 } 547 else { 548 logmsg " valgrind ERROR "; 549 logmsg @e; 550 } 551 $fail = 1; 552 } 553 } 554 555 # verify that it returns a proper error code, doesn't leak memory 556 # and doesn't core dump 557 if(($ret & 255) || ($ret >> 8) >= 128) { 558 logmsg " system() returned $ret\n"; 559 $fail=1; 560 } 561 else { 562 my @memdata=`$memanalyze "$LOGDIR/$MEMDUMP"`; 563 my $leak=0; 564 for(@memdata) { 565 if($_ ne "") { 566 # well it could be other memory problems as well, but 567 # we call it leak for short here 568 $leak=1; 569 } 570 } 571 if($leak) { 572 logmsg "** MEMORY FAILURE\n"; 573 logmsg @memdata; 574 logmsg `$memanalyze -l "$LOGDIR/$MEMDUMP"`; 575 $fail = 1; 576 } 577 } 578 if($fail) { 579 logmsg " $testnum: torture FAILED: function number $limit in test.\n", 580 " invoke with \"-t$limit\" to repeat this single case.\n"; 581 stopservers($verbose); 582 return 1; 583 } 584 } 585 586 logmsg "\n" if($verbose); 587 logmsg "torture OK\n"; 588 return 0; 589} 590 591 592####################################################################### 593# restore environment variables that were modified in test 594sub restore_test_env { 595 my $deleteoldenv = $_[0]; # 1 to delete the saved contents after restore 596 foreach my $var (keys %oldenv) { 597 if($oldenv{$var} eq 'notset') { 598 delete $ENV{$var} if($ENV{$var}); 599 } 600 else { 601 $ENV{$var} = $oldenv{$var}; 602 } 603 if($deleteoldenv) { 604 delete $oldenv{$var}; 605 } 606 } 607} 608 609 610####################################################################### 611# Start the servers needed to run this test case 612sub singletest_startservers { 613 my ($testnum, $testtimings) = @_; 614 615 # remove old test server files before servers are started/verified 616 unlink("$LOGDIR/$SERVERCMD"); 617 unlink("$LOGDIR/$SERVERIN"); 618 unlink("$LOGDIR/$PROXYIN"); 619 620 # timestamp required servers verification start 621 $$testtimings{"timesrvrini"} = Time::HiRes::time(); 622 623 my $why; 624 my $error; 625 if (!$listonly) { 626 my @what = getpart("client", "server"); 627 if(!$what[0]) { 628 warn "Test case $testnum has no server(s) specified"; 629 $why = "no server specified"; 630 $error = -1; 631 } else { 632 my $err; 633 ($why, $err) = serverfortest(@what); 634 if($err == 1) { 635 # Error indicates an actual problem starting the server 636 $error = -2; 637 } else { 638 $error = -1; 639 } 640 } 641 } 642 643 # timestamp required servers verification end 644 $$testtimings{"timesrvrend"} = Time::HiRes::time(); 645 646 return ($why, $error); 647} 648 649 650####################################################################### 651# Generate preprocessed test file 652sub singletest_preprocess { 653 my $testnum = $_[0]; 654 655 # Save a preprocessed version of the entire test file. This allows more 656 # "basic" test case readers to enjoy variable replacements. 657 my @entiretest = fulltest(); 658 my $otest = "$LOGDIR/test$testnum"; 659 660 @entiretest = prepro($testnum, @entiretest); 661 662 # save the new version 663 open(my $fulltesth, ">", "$otest") || die "Failure writing test file"; 664 foreach my $bytes (@entiretest) { 665 print $fulltesth pack('a*', $bytes) or die "Failed to print '$bytes': $!"; 666 } 667 close($fulltesth) || die "Failure writing test file"; 668 669 # in case the process changed the file, reload it 670 loadtest("$LOGDIR/test${testnum}"); 671} 672 673 674####################################################################### 675# Set up the test environment to run this test case 676sub singletest_setenv { 677 my @setenv = getpart("client", "setenv"); 678 foreach my $s (@setenv) { 679 chomp $s; 680 if($s =~ /([^=]*)(.*)/) { 681 my ($var, $content) = ($1, $2); 682 # remember current setting, to restore it once test runs 683 $oldenv{$var} = ($ENV{$var})?"$ENV{$var}":'notset'; 684 685 if($content =~ /^=(.*)/) { 686 # assign it 687 $content = $1; 688 689 if($var =~ /^LD_PRELOAD/) { 690 if(exe_ext('TOOL') && (exe_ext('TOOL') eq '.exe')) { 691 logmsg "Skipping LD_PRELOAD due to lack of OS support\n" if($verbose); 692 next; 693 } 694 if($feature{"Debug"} || !$has_shared) { 695 logmsg "Skipping LD_PRELOAD due to no release shared build\n" if($verbose); 696 next; 697 } 698 } 699 $ENV{$var} = "$content"; 700 logmsg "setenv $var = $content\n" if($verbose); 701 } 702 else { 703 # remove it 704 delete $ENV{$var} if($ENV{$var}); 705 } 706 707 } 708 } 709 if($proxy_address) { 710 $ENV{http_proxy} = $proxy_address; 711 $ENV{HTTPS_PROXY} = $proxy_address; 712 } 713} 714 715 716####################################################################### 717# Check that test environment is fine to run this test case 718sub singletest_precheck { 719 my $testnum = $_[0]; 720 my $why; 721 my @precheck = getpart("client", "precheck"); 722 if(@precheck) { 723 my $cmd = $precheck[0]; 724 chomp $cmd; 725 if($cmd) { 726 my @p = split(/ /, $cmd); 727 if($p[0] !~ /\//) { 728 # the first word, the command, does not contain a slash so 729 # we will scan the "improved" PATH to find the command to 730 # be able to run it 731 my $fullp = checktestcmd($p[0]); 732 733 if($fullp) { 734 $p[0] = $fullp; 735 } 736 $cmd = join(" ", @p); 737 } 738 739 my @o = `$cmd 2> $LOGDIR/precheck-$testnum`; 740 if($o[0]) { 741 $why = $o[0]; 742 $why =~ s/[\r\n]//g; 743 } 744 elsif($?) { 745 $why = "precheck command error"; 746 } 747 logmsg "prechecked $cmd\n" if($verbose); 748 } 749 } 750 return $why; 751} 752 753 754####################################################################### 755# Prepare the test environment to run this test case 756sub singletest_prepare { 757 my ($testnum) = @_; 758 759 if($feature{"TrackMemory"}) { 760 unlink("$LOGDIR/$MEMDUMP"); 761 } 762 unlink("core"); 763 764 # remove server output logfiles after servers are started/verified 765 unlink("$LOGDIR/$SERVERIN"); 766 unlink("$LOGDIR/$PROXYIN"); 767 768 # if this section exists, it might be FTP server instructions: 769 my @ftpservercmd = getpart("reply", "servercmd"); 770 push @ftpservercmd, "Testnum $testnum\n"; 771 # write the instructions to file 772 writearray("$LOGDIR/$SERVERCMD", \@ftpservercmd); 773 774 # create (possibly-empty) files before starting the test 775 for my $partsuffix (('', '1', '2', '3', '4')) { 776 my @inputfile=getpart("client", "file".$partsuffix); 777 my %fileattr = getpartattr("client", "file".$partsuffix); 778 my $filename=$fileattr{'name'}; 779 if(@inputfile || $filename) { 780 if(!$filename) { 781 logmsg " $testnum: IGNORED: Section client=>file has no name attribute\n"; 782 return -1; 783 } 784 my $fileContent = join('', @inputfile); 785 786 # make directories if needed 787 my $path = $filename; 788 # cut off the file name part 789 $path =~ s/^(.*)\/[^\/]*/$1/; 790 my @ldparts = split(/\//, $LOGDIR); 791 my $nparts = @ldparts; 792 my @parts = split(/\//, $path); 793 if(join("/", @parts[0..$nparts-1]) eq $LOGDIR) { 794 # the file is in $LOGDIR/ 795 my $d = shift @parts; 796 for(@parts) { 797 $d .= "/$_"; 798 mkdir $d; # 0777 799 } 800 } 801 if (open(my $outfile, ">", "$filename")) { 802 binmode $outfile; # for crapage systems, use binary 803 if($fileattr{'nonewline'}) { 804 # cut off the final newline 805 chomp($fileContent); 806 } 807 print $outfile $fileContent; 808 close($outfile); 809 } else { 810 logmsg "ERROR: cannot write $filename\n"; 811 } 812 } 813 } 814 return 0; 815} 816 817 818####################################################################### 819# Run the test command 820sub singletest_run { 821 my ($testnum, $testtimings) = @_; 822 823 # get the command line options to use 824 my ($cmd, @blaha)= getpart("client", "command"); 825 if($cmd) { 826 # make some nice replace operations 827 $cmd =~ s/\n//g; # no newlines please 828 # substitute variables in the command line 829 } 830 else { 831 # there was no command given, use something silly 832 $cmd="-"; 833 } 834 835 my $CURLOUT="$LOGDIR/curl$testnum.out"; # curl output if not stdout 836 837 # if stdout section exists, we verify that the stdout contained this: 838 my $out=""; 839 my %cmdhash = getpartattr("client", "command"); 840 if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-output/)) { 841 #We may slap on --output! 842 if (!partexists("verify", "stdout") || 843 ($cmdhash{'option'} && $cmdhash{'option'} =~ /force-output/)) { 844 $out=" --output $CURLOUT "; 845 } 846 } 847 848 my @codepieces = getpart("client", "tool"); 849 my $tool=""; 850 my $tool_name=""; # without exe extension 851 if(@codepieces) { 852 $tool_name = $codepieces[0]; 853 chomp $tool_name; 854 $tool = $tool_name . exe_ext('TOOL'); 855 } 856 857 my $disablevalgrind; 858 my $CMDLINE=""; 859 my $cmdargs; 860 my $cmdtype = $cmdhash{'type'} || "default"; 861 my $fail_due_event_based = $run_event_based; 862 if($cmdtype eq "perl") { 863 # run the command line prepended with "perl" 864 $cmdargs ="$cmd"; 865 $CMDLINE = "$perl "; 866 $tool=$CMDLINE; 867 $disablevalgrind=1; 868 } 869 elsif($cmdtype eq "shell") { 870 # run the command line prepended with "/bin/sh" 871 $cmdargs ="$cmd"; 872 $CMDLINE = "/bin/sh "; 873 $tool=$CMDLINE; 874 $disablevalgrind=1; 875 } 876 elsif(!$tool && !$keywords{"unittest"}) { 877 # run curl, add suitable command line options 878 my $inc=""; 879 if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-include/)) { 880 $inc = " --include"; 881 } 882 $cmdargs = "$out$inc "; 883 884 if($cmdhash{'option'} && ($cmdhash{'option'} =~ /binary-trace/)) { 885 $cmdargs .= "--trace $LOGDIR/trace$testnum "; 886 } 887 else { 888 $cmdargs .= "--trace-ascii $LOGDIR/trace$testnum "; 889 } 890 $cmdargs .= "--trace-config all "; 891 $cmdargs .= "--trace-time "; 892 if($run_event_based) { 893 $cmdargs .= "--test-event "; 894 $fail_due_event_based--; 895 } 896 if($run_duphandle) { 897 $cmdargs .= "--test-duphandle "; 898 my @dis = getpart("client", "disable"); 899 if(@dis) { 900 chomp $dis[0] if($dis[0]); 901 if($dis[0] eq "test-duphandle") { 902 # marked to not run with duphandle 903 logmsg " $testnum: IGNORED: Can't run test-duphandle\n"; 904 return (-1, 0, 0, "", "", 0); 905 } 906 } 907 } 908 $cmdargs .= $cmd; 909 if ($proxy_address) { 910 $cmdargs .= " --proxy $proxy_address "; 911 } 912 } 913 else { 914 $cmdargs = " $cmd"; # $cmd is the command line for the test file 915 $CURLOUT = stdoutfilename($LOGDIR, $testnum); # sends received data to stdout 916 917 # Default the tool to a unit test with the same name as the test spec 918 if($keywords{"unittest"} && !$tool) { 919 $tool_name="unit$testnum"; 920 $tool = $tool_name; 921 } 922 923 if($tool =~ /^lib/) { 924 if($bundle) { 925 $CMDLINE="$LIBDIR/libtests"; 926 } 927 else { 928 $CMDLINE="$LIBDIR/$tool"; 929 } 930 } 931 elsif($tool =~ /^unit/) { 932 if($bundle) { 933 $CMDLINE="$UNITDIR/units"; 934 } 935 else { 936 $CMDLINE="$UNITDIR/$tool"; 937 } 938 } 939 940 if(! -f $CMDLINE) { 941 logmsg " $testnum: IGNORED: The tool set in the test case for this: '$tool' does not exist\n"; 942 return (-1, 0, 0, "", "", 0); 943 } 944 945 if($bundle) { 946 $CMDLINE.=" $tool_name"; 947 } 948 949 $DBGCURL=$CMDLINE; 950 } 951 952 if($fail_due_event_based) { 953 logmsg " $testnum: IGNORED: This test cannot run event based\n"; 954 return (-1, 0, 0, "", "", 0); 955 } 956 957 if($gdbthis) { 958 # gdb is incompatible with valgrind, so disable it when debugging 959 # Perhaps a better approach would be to run it under valgrind anyway 960 # with --db-attach=yes or --vgdb=yes. 961 $disablevalgrind=1; 962 } 963 964 my @stdintest = getpart("client", "stdin"); 965 966 if(@stdintest) { 967 my $stdinfile="$LOGDIR/stdin-for-$testnum"; 968 969 my %hash = getpartattr("client", "stdin"); 970 if($hash{'nonewline'}) { 971 # cut off the final newline from the final line of the stdin data 972 chomp($stdintest[-1]); 973 } 974 975 writearray($stdinfile, \@stdintest); 976 977 $cmdargs .= " <$stdinfile"; 978 } 979 980 if(!$tool) { 981 $CMDLINE=shell_quote($CURL); 982 if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-q/)) { 983 $CMDLINE .= " -q"; 984 } 985 } 986 987 if(use_valgrind() && !$disablevalgrind) { 988 my $valgrindcmd = "$valgrind "; 989 $valgrindcmd .= "$valgrind_tool " if($valgrind_tool); 990 $valgrindcmd .= "--quiet --leak-check=yes "; 991 $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp "; 992 # $valgrindcmd .= "--gen-suppressions=all "; 993 $valgrindcmd .= "--num-callers=16 "; 994 $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum"; 995 $CMDLINE = "$valgrindcmd $CMDLINE"; 996 } 997 998 $CMDLINE .= "$cmdargs > " . stdoutfilename($LOGDIR, $testnum) . 999 " 2> " . stderrfilename($LOGDIR, $testnum); 1000 1001 if($verbose) { 1002 logmsg "$CMDLINE\n"; 1003 } 1004 1005 open(my $cmdlog, ">", $CURLLOG) || die "Failure writing log file"; 1006 print $cmdlog "$CMDLINE\n"; 1007 close($cmdlog) || die "Failure writing log file"; 1008 1009 my $dumped_core; 1010 my $cmdres; 1011 1012 if($gdbthis) { 1013 my $gdbinit = "$TESTDIR/gdbinit$testnum"; 1014 open(my $gdbcmd, ">", "$LOGDIR/gdbcmd") || die "Failure writing gdb file"; 1015 if($gdbthis == 1) { 1016 # gdb mode 1017 print $gdbcmd "set args $cmdargs\n"; 1018 print $gdbcmd "show args\n"; 1019 print $gdbcmd "source $gdbinit\n" if -e $gdbinit; 1020 } 1021 else { 1022 # lldb mode 1023 print $gdbcmd "set args $cmdargs\n"; 1024 } 1025 close($gdbcmd) || die "Failure writing gdb file"; 1026 } 1027 1028 # Flush output. 1029 $| = 1; 1030 1031 # timestamp starting of test command 1032 $$testtimings{"timetoolini"} = Time::HiRes::time(); 1033 1034 # run the command line we built 1035 if ($torture) { 1036 $cmdres = torture($CMDLINE, 1037 $testnum, 1038 "$gdb --directory $LIBDIR " . shell_quote($DBGCURL) . " -x $LOGDIR/gdbcmd"); 1039 } 1040 elsif($gdbthis == 1) { 1041 # gdb 1042 my $GDBW = ($gdbxwin) ? "-w" : ""; 1043 runclient("$gdb --directory $LIBDIR " . shell_quote($DBGCURL) . " $GDBW -x $LOGDIR/gdbcmd"); 1044 $cmdres=0; # makes it always continue after a debugged run 1045 } 1046 elsif($gdbthis == 2) { 1047 # $gdb is "lldb" 1048 print "runs lldb -- $CURL $cmdargs\n"; 1049 runclient("lldb -- $CURL $cmdargs"); 1050 $cmdres=0; # makes it always continue after a debugged run 1051 } 1052 else { 1053 # Convert the raw result code into a more useful one 1054 ($cmdres, $dumped_core) = normalize_cmdres(runclient("$CMDLINE")); 1055 } 1056 1057 # timestamp finishing of test command 1058 $$testtimings{"timetoolend"} = Time::HiRes::time(); 1059 1060 return (0, $cmdres, $dumped_core, $CURLOUT, $tool, use_valgrind() && !$disablevalgrind); 1061} 1062 1063 1064####################################################################### 1065# Clean up after test command 1066sub singletest_clean { 1067 my ($testnum, $dumped_core, $testtimings)=@_; 1068 1069 if(!$dumped_core) { 1070 if(-r "core") { 1071 # there's core file present now! 1072 $dumped_core = 1; 1073 } 1074 } 1075 1076 if($dumped_core) { 1077 logmsg "core dumped\n"; 1078 if(0 && $gdb) { 1079 logmsg "running gdb for post-mortem analysis:\n"; 1080 open(my $gdbcmd, ">", "$LOGDIR/gdbcmd2") || die "Failure writing gdb file"; 1081 print $gdbcmd "bt\n"; 1082 close($gdbcmd) || die "Failure writing gdb file"; 1083 runclient("$gdb --directory libtest -x $LOGDIR/gdbcmd2 -batch " . shell_quote($DBGCURL) . " core "); 1084 # unlink("$LOGDIR/gdbcmd2"); 1085 } 1086 } 1087 1088 my $serverlogslocktimeout = $defserverlogslocktimeout; 1089 my %cmdhash = getpartattr("client", "command"); 1090 if($cmdhash{'timeout'}) { 1091 # test is allowed to override default server logs lock timeout 1092 if($cmdhash{'timeout'} =~ /(\d+)/) { 1093 $serverlogslocktimeout = $1 if($1 >= 0); 1094 } 1095 } 1096 1097 # Test harness ssh server does not have this synchronization mechanism, 1098 # this implies that some ssh server based tests might need a small delay 1099 # once that the client command has run to avoid false test failures. 1100 # 1101 # gnutls-serv also lacks this synchronization mechanism, so gnutls-serv 1102 # based tests might need a small delay once that the client command has 1103 # run to avoid false test failures. 1104 my $postcommanddelay = $defpostcommanddelay; 1105 if($cmdhash{'delay'}) { 1106 # test is allowed to specify a delay after command is executed 1107 if($cmdhash{'delay'} =~ /(\d+)/) { 1108 $postcommanddelay = $1 if($1 > 0); 1109 } 1110 } 1111 1112 portable_sleep($postcommanddelay) if($postcommanddelay); 1113 1114 my @killtestservers = getpart("client", "killserver"); 1115 if(@killtestservers) { 1116 foreach my $server (@killtestservers) { 1117 chomp $server; 1118 if(stopserver($server)) { 1119 logmsg " $testnum: killserver FAILED\n"; 1120 return 1; # normal error if asked to fail on unexpected alive 1121 } 1122 } 1123 } 1124 1125 # wait for any servers left running to release their locks 1126 waitlockunlock($serverlogslocktimeout); 1127 1128 # timestamp removal of server logs advisor read lock 1129 $$testtimings{"timesrvrlog"} = Time::HiRes::time(); 1130 1131 # test definition might instruct to stop some servers 1132 # stop also all servers relative to the given one 1133 1134 return 0; 1135} 1136 1137####################################################################### 1138# Verify that the postcheck succeeded 1139sub singletest_postcheck { 1140 my ($testnum)=@_; 1141 1142 # run the postcheck command 1143 my @postcheck= getpart("client", "postcheck"); 1144 if(@postcheck) { 1145 die "test$testnum uses client/postcheck"; 1146 } 1147 1148 @postcheck= getpart("verify", "postcheck"); 1149 if(@postcheck) { 1150 my $cmd = join("", @postcheck); 1151 chomp $cmd; 1152 if($cmd) { 1153 logmsg "postcheck $cmd\n" if($verbose); 1154 my $rc = runclient("$cmd"); 1155 # Must run the postcheck command in torture mode in order 1156 # to clean up, but the result can't be relied upon. 1157 if($rc != 0 && !$torture) { 1158 logmsg " $testnum: postcheck FAILED\n"; 1159 return -1; 1160 } 1161 } 1162 } 1163 return 0; 1164} 1165 1166 1167 1168################################################################### 1169# Get ready to run a single test case 1170sub runner_test_preprocess { 1171 my ($testnum)=@_; 1172 my %testtimings; 1173 1174 if(clearlogs()) { 1175 logmsg "Warning: log messages were lost\n"; 1176 } 1177 1178 # timestamp test preparation start 1179 # TODO: this metric now shows only a portion of the prep time; better would 1180 # be to time singletest_preprocess below instead 1181 $testtimings{"timeprepini"} = Time::HiRes::time(); 1182 1183 ################################################################### 1184 # Load test metadata 1185 # ignore any error here--if there were one, it would have been 1186 # caught during the selection phase and this test would not be 1187 # running now 1188 loadtest("${TESTDIR}/test${testnum}"); 1189 readtestkeywords(); 1190 1191 ################################################################### 1192 # Restore environment variables that were modified in a previous run. 1193 # Test definition may instruct to (un)set environment vars. 1194 restore_test_env(1); 1195 1196 ################################################################### 1197 # Start the servers needed to run this test case 1198 my ($why, $error) = singletest_startservers($testnum, \%testtimings); 1199 1200 # make sure no locks left for responsive test 1201 waitlockunlock($defserverlogslocktimeout); 1202 1203 if(!$why) { 1204 1205 ############################################################### 1206 # Generate preprocessed test file 1207 # This must be done after the servers are started so server 1208 # variables are available for substitution. 1209 singletest_preprocess($testnum); 1210 1211 ############################################################### 1212 # Set up the test environment to run this test case 1213 singletest_setenv(); 1214 1215 ############################################################### 1216 # Check that the test environment is fine to run this test case 1217 if (!$listonly) { 1218 $why = singletest_precheck($testnum); 1219 $error = -1; 1220 } 1221 } 1222 return ($why, $error, clearlogs(), \%testtimings); 1223} 1224 1225 1226################################################################### 1227# Run a single test case with an environment that already been prepared 1228# Returns 0=success, -1=skippable failure, -2=permanent error, 1229# 1=unskippable test failure, as first integer, plus any log messages, 1230# plus more return values when error is 0 1231sub runner_test_run { 1232 my ($testnum)=@_; 1233 1234 if(clearlogs()) { 1235 logmsg "Warning: log messages were lost\n"; 1236 } 1237 1238 ####################################################################### 1239 # Prepare the test environment to run this test case 1240 my $error = singletest_prepare($testnum); 1241 if($error) { 1242 return (-2, clearlogs()); 1243 } 1244 1245 ####################################################################### 1246 # Run the test command 1247 my %testtimings; 1248 my $cmdres; 1249 my $dumped_core; 1250 my $CURLOUT; 1251 my $tool; 1252 my $usedvalgrind; 1253 ($error, $cmdres, $dumped_core, $CURLOUT, $tool, $usedvalgrind) = singletest_run($testnum, \%testtimings); 1254 if($error) { 1255 return (-2, clearlogs(), \%testtimings); 1256 } 1257 1258 ####################################################################### 1259 # Clean up after test command 1260 $error = singletest_clean($testnum, $dumped_core, \%testtimings); 1261 if($error) { 1262 return ($error, clearlogs(), \%testtimings); 1263 } 1264 1265 ####################################################################### 1266 # Verify that the postcheck succeeded 1267 $error = singletest_postcheck($testnum); 1268 if($error) { 1269 return ($error, clearlogs(), \%testtimings); 1270 } 1271 1272 ####################################################################### 1273 # restore environment variables that were modified 1274 restore_test_env(0); 1275 1276 return (0, clearlogs(), \%testtimings, $cmdres, $CURLOUT, $tool, $usedvalgrind); 1277} 1278 1279# Async call runner_clearlocks 1280# Called by controller 1281sub runnerac_clearlocks { 1282 return controlleripccall(\&runner_clearlocks, @_); 1283} 1284 1285# Async call runner_shutdown 1286# This call does NOT generate an IPC response and must be the last IPC call 1287# received. 1288# Called by controller 1289sub runnerac_shutdown { 1290 my ($runnerid)=$_[0]; 1291 my $err = controlleripccall(\&runner_shutdown, @_); 1292 1293 # These have no more use 1294 close($controllerw{$runnerid}); 1295 undef $controllerw{$runnerid}; 1296 close($controllerr{$runnerid}); 1297 undef $controllerr{$runnerid}; 1298 return $err; 1299} 1300 1301# Async call of runner_stopservers 1302# Called by controller 1303sub runnerac_stopservers { 1304 return controlleripccall(\&runner_stopservers, @_); 1305} 1306 1307# Async call of runner_test_preprocess 1308# Called by controller 1309sub runnerac_test_preprocess { 1310 return controlleripccall(\&runner_test_preprocess, @_); 1311} 1312 1313# Async call of runner_test_run 1314# Called by controller 1315sub runnerac_test_run { 1316 return controlleripccall(\&runner_test_run, @_); 1317} 1318 1319################################################################### 1320# Call an arbitrary function via IPC 1321# The first argument is the function reference, the second is the runner ID 1322# Returns 0 on success, -1 on error writing to runner 1323# Called by controller (indirectly, via a more specific function) 1324sub controlleripccall { 1325 my $funcref = shift @_; 1326 my $runnerid = shift @_; 1327 # Get the name of the function from the reference 1328 my $cv = svref_2object($funcref); 1329 my $gv = $cv->GV; 1330 # Prepend the name to the function arguments so it's marshalled along with them 1331 unshift @_, $gv->NAME; 1332 # Marshall the arguments into a flat string 1333 my $margs = freeze \@_; 1334 1335 # Send IPC call via pipe 1336 my $err; 1337 while(! defined ($err = syswrite($controllerw{$runnerid}, (pack "L", length($margs)) . $margs)) || $err <= 0) { 1338 if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { 1339 # Runner has likely died 1340 return -1; 1341 } 1342 # system call was interrupted, probably by ^C; restart it so we stay in sync 1343 } 1344 1345 if(!$multiprocess) { 1346 # Call the remote function here in single process mode 1347 ipcrecv(); 1348 } 1349 return 0; 1350} 1351 1352################################################################### 1353# Receive async response of a previous call via IPC 1354# The first return value is the runner ID or undef on error 1355# Called by controller 1356sub runnerar { 1357 my ($runnerid) = @_; 1358 my $err; 1359 my $datalen; 1360 while(! defined ($err = sysread($controllerr{$runnerid}, $datalen, 4)) || $err <= 0) { 1361 if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { 1362 # Runner is likely dead and closed the pipe 1363 return undef; 1364 } 1365 # system call was interrupted, probably by ^C; restart it so we stay in sync 1366 } 1367 my $len=unpack("L", $datalen); 1368 my $buf; 1369 while(! defined ($err = sysread($controllerr{$runnerid}, $buf, $len)) || $err <= 0) { 1370 if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { 1371 # Runner is likely dead and closed the pipe 1372 return undef; 1373 } 1374 # system call was interrupted, probably by ^C; restart it so we stay in sync 1375 } 1376 1377 # Decode response values 1378 my $resarrayref = thaw $buf; 1379 1380 # First argument is runner ID 1381 # TODO: remove this; it's unneeded since it's passed in 1382 unshift @$resarrayref, $runnerid; 1383 return @$resarrayref; 1384} 1385 1386################################################################### 1387# Returns runner ID if a response from an async call is ready or error 1388# First value is ready, second is error, however an error case shows up 1389# as ready in Linux, so you can't trust it. 1390# argument is 0 for nonblocking, undef for blocking, anything else for timeout 1391# Called by controller 1392sub runnerar_ready { 1393 my ($blocking) = @_; 1394 my $rin = ""; 1395 my %idbyfileno; 1396 my $maxfileno=0; 1397 my @ready_runners = (); 1398 foreach my $p (keys(%controllerr)) { 1399 my $fd = fileno($controllerr{$p}); 1400 vec($rin, $fd, 1) = 1; 1401 $idbyfileno{$fd} = $p; # save the runner ID for each pipe fd 1402 if($fd > $maxfileno) { 1403 $maxfileno = $fd; 1404 } 1405 } 1406 $maxfileno || die "Internal error: no runners are available to wait on\n"; 1407 1408 # Wait for any pipe from any runner to be ready 1409 # This may be interrupted and return EINTR, but this is ignored and the 1410 # caller will need to later call this function again. 1411 # TODO: this is relatively slow with hundreds of fds 1412 my $ein = $rin; 1413 if(select(my $rout=$rin, undef, my $eout=$ein, $blocking) >= 1) { 1414 for my $fd (0..$maxfileno) { 1415 # Return an error condition first in case it's both 1416 if(vec($eout, $fd, 1)) { 1417 return (undef, $idbyfileno{$fd}); 1418 } 1419 if(vec($rout, $fd, 1)) { 1420 push(@ready_runners, $idbyfileno{$fd}); 1421 } 1422 } 1423 die "Internal pipe readiness inconsistency\n" if(!@ready_runners); 1424 return (@ready_runners, undef); 1425 } 1426 return (undef, undef); 1427} 1428 1429 1430################################################################### 1431# Cleanly abort and exit the runner 1432# This uses print since there is no longer any controller to write logs. 1433sub runnerabort{ 1434 print "Controller is gone: runner $$ for $LOGDIR exiting\n"; 1435 my ($error, $logs) = runner_stopservers(); 1436 print $logs; 1437 runner_shutdown(); 1438} 1439 1440################################################################### 1441# Receive an IPC call in the runner and execute it 1442# The IPC is read from the $runnerr pipe and the response is 1443# written to the $runnerw pipe 1444# Returns 0 if more IPC calls are expected or 1 if the runner should exit 1445sub ipcrecv { 1446 my $err; 1447 my $datalen; 1448 while(! defined ($err = sysread($runnerr, $datalen, 4)) || $err <= 0) { 1449 if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { 1450 # pipe has closed; controller is gone and we must exit 1451 runnerabort(); 1452 # Special case: no response will be forthcoming 1453 return 1; 1454 } 1455 # system call was interrupted, probably by ^C; restart it so we stay in sync 1456 } 1457 my $len=unpack("L", $datalen); 1458 my $buf; 1459 while(! defined ($err = sysread($runnerr, $buf, $len)) || $err <= 0) { 1460 if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { 1461 # pipe has closed; controller is gone and we must exit 1462 runnerabort(); 1463 # Special case: no response will be forthcoming 1464 return 1; 1465 } 1466 # system call was interrupted, probably by ^C; restart it so we stay in sync 1467 } 1468 1469 # Decode the function name and arguments 1470 my $argsarrayref = thaw $buf; 1471 1472 # The name of the function to call is the first argument 1473 my $funcname = shift @$argsarrayref; 1474 1475 # print "ipcrecv $funcname\n"; 1476 # Synchronously call the desired function 1477 my @res; 1478 if($funcname eq "runner_clearlocks") { 1479 @res = runner_clearlocks(@$argsarrayref); 1480 } 1481 elsif($funcname eq "runner_shutdown") { 1482 runner_shutdown(@$argsarrayref); 1483 # Special case: no response will be forthcoming 1484 return 1; 1485 } 1486 elsif($funcname eq "runner_stopservers") { 1487 @res = runner_stopservers(@$argsarrayref); 1488 } 1489 elsif($funcname eq "runner_test_preprocess") { 1490 @res = runner_test_preprocess(@$argsarrayref); 1491 } 1492 elsif($funcname eq "runner_test_run") { 1493 @res = runner_test_run(@$argsarrayref); 1494 } else { 1495 die "Unknown IPC function $funcname\n"; 1496 } 1497 # print "ipcrecv results\n"; 1498 1499 # Marshall the results to return 1500 $buf = freeze \@res; 1501 1502 while(! defined ($err = syswrite($runnerw, (pack "L", length($buf)) . $buf)) || $err <= 0) { 1503 if((!defined $err && ! $!{EINTR}) || (defined $err && $err == 0)) { 1504 # pipe has closed; controller is gone and we must exit 1505 runnerabort(); 1506 # Special case: no response will be forthcoming 1507 return 1; 1508 } 1509 # system call was interrupted, probably by ^C; restart it so we stay in sync 1510 } 1511 1512 return 0; 1513} 1514 1515################################################################### 1516# Kill the server processes that still have lock files in a directory 1517sub runner_clearlocks { 1518 my ($lockdir)=@_; 1519 if(clearlogs()) { 1520 logmsg "Warning: log messages were lost\n"; 1521 } 1522 clearlocks($lockdir); 1523 return clearlogs(); 1524} 1525 1526 1527################################################################### 1528# Kill all server processes 1529sub runner_stopservers { 1530 my $error = stopservers($verbose); 1531 my $logs = clearlogs(); 1532 return ($error, $logs); 1533} 1534 1535################################################################### 1536# Shut down this runner 1537sub runner_shutdown { 1538 close($runnerr); 1539 undef $runnerr; 1540 close($runnerw); 1541 undef $runnerw; 1542} 1543 1544 15451; 1546