quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

runtests.pl (107758B)


      1 #!/usr/bin/env perl
      2 #***************************************************************************
      3 #                                  _   _ ____  _
      4 #  Project                     ___| | | |  _ \| |
      5 #                             / __| | | | |_) | |
      6 #                            | (__| |_| |  _ <| |___
      7 #                             \___|\___/|_| \_\_____|
      8 #
      9 # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
     10 #
     11 # This software is licensed as described in the file COPYING, which
     12 # you should have received as part of this distribution. The terms
     13 # are also available at https://curl.se/docs/copyright.html.
     14 #
     15 # You may opt to use, copy, modify, merge, publish, distribute and/or sell
     16 # copies of the Software, and permit persons to whom the Software is
     17 # furnished to do so, under the terms of the COPYING file.
     18 #
     19 # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     20 # KIND, either express or implied.
     21 #
     22 # SPDX-License-Identifier: curl
     23 #
     24 ###########################################################################
     25 
     26 # For documentation, run `man ./runtests.1` and see README.md.
     27 
     28 # Experimental hooks are available to run tests remotely on machines that
     29 # are able to run curl but are unable to run the test harness.
     30 # The following sections need to be modified:
     31 #
     32 #  $HOSTIP, $HOST6IP - Set to the address of the host running the test suite
     33 #  $CLIENTIP, $CLIENT6IP - Set to the address of the host running curl
     34 #  runclient, runclientoutput - Modify to copy all the files in the log/
     35 #    directory to the system running curl, run the given command remotely
     36 #    and save the return code or returned stdout (respectively), then
     37 #    copy all the files from the remote system's log/ directory back to
     38 #    the host running the test suite.  This can be done a few ways, such
     39 #    as using scp & ssh, rsync & telnet, or using a NFS shared directory
     40 #    and ssh.
     41 #
     42 # 'make && make test' needs to be done on both machines before making the
     43 # above changes and running runtests.pl manually.  In the shared NFS case,
     44 # the contents of the tests/server/ directory must be from the host
     45 # running the test suite, while the rest must be from the host running curl.
     46 #
     47 # Note that even with these changes a number of tests will still fail (mainly
     48 # to do with cookies, those that set environment variables, or those that
     49 # do more than touch the file system in a <precheck> or <postcheck>
     50 # section). These can be added to the $TESTCASES line below,
     51 # e.g. $TESTCASES="!8 !31 !63 !cookies..."
     52 #
     53 # Finally, to properly support -g and -n, checktestcmd needs to change
     54 # to check the remote system's PATH, and the places in the code where
     55 # the curl binary is read directly to determine its type also need to be
     56 # fixed. As long as the -g option is never given, and the -n is always
     57 # given, this won't be a problem.
     58 
     59 use strict;
     60 use warnings;
     61 use 5.006;
     62 use POSIX qw(strftime);
     63 
     64 # These should be the only variables that might be needed to get edited:
     65 
     66 BEGIN {
     67     # Define srcdir to the location of the tests source directory. This is
     68     # usually set by the Makefile, but for out-of-tree builds with direct
     69     # invocation of runtests.pl, it may not be set.
     70     if(!defined $ENV{'srcdir'}) {
     71         use File::Basename;
     72         $ENV{'srcdir'} = dirname(__FILE__);
     73     }
     74     push(@INC, $ENV{'srcdir'});
     75     # run time statistics needs Time::HiRes
     76     eval {
     77         no warnings "all";
     78         require Time::HiRes;
     79         import  Time::HiRes qw( time );
     80     }
     81 }
     82 
     83 use Digest::MD5 qw(md5);
     84 use List::Util 'sum';
     85 use I18N::Langinfo qw(langinfo CODESET);
     86 
     87 use serverhelp qw(
     88     server_exe
     89     );
     90 use pathhelp qw(
     91     exe_ext
     92     sys_native_current_path
     93     );
     94 use processhelp qw(
     95     portable_sleep
     96     );
     97 
     98 use appveyor;
     99 use azure;
    100 use getpart;   # array functions
    101 use servers;
    102 use valgrind;  # valgrind report parser
    103 use globalconfig;
    104 use runner;
    105 use testutil;
    106 
    107 my %custom_skip_reasons;
    108 
    109 my $ACURL=$VCURL;  # what curl binary to use to talk to APIs (relevant for CI)
    110                    # ACURL is handy to set to the system one for reliability
    111 my $CURLCONFIG="../curl-config"; # curl-config from current build
    112 
    113 # Normally, all test cases should be run, but at times it is handy to
    114 # simply run a particular one:
    115 my $TESTCASES="all";
    116 
    117 # To run specific test cases, set them like:
    118 # $TESTCASES="1 2 3 7 8";
    119 
    120 #######################################################################
    121 # No variables below this point should need to be modified
    122 #
    123 
    124 my $libtool;
    125 my $repeat = 0;
    126 my $retry = 0;
    127 
    128 my $start;          # time at which testing started
    129 my $args;           # command-line arguments
    130 
    131 my $uname_release = `uname -r`;
    132 my $is_wsl = $uname_release =~ /Microsoft$/;
    133 
    134 my $http_ipv6;      # set if HTTP server has IPv6 support
    135 my $http_unix;      # set if HTTP server has Unix sockets support
    136 my $ftp_ipv6;       # set if FTP server has IPv6 support
    137 
    138 my $resolver;       # name of the resolver backend (for human presentation)
    139 
    140 my %skipped;    # skipped{reason}=counter, reasons for skip
    141 my @teststat;   # teststat[testnum]=reason, reasons for skip
    142 my %disabled_keywords;  # key words of tests to skip
    143 my %ignored_keywords;   # key words of tests to ignore results
    144 my %enabled_keywords;   # key words of tests to run
    145 my %disabled;           # disabled test cases
    146 my %ignored;            # ignored results of test cases
    147 my %ignoretestcodes;    # if test results are to be ignored
    148 
    149 my $passedign;   # tests passed with results ignored
    150 
    151 my $timestats;   # time stamping and stats generation
    152 my $fullstats;   # show time stats for every single test
    153 my %timeprepini; # timestamp for each test preparation start
    154 my %timesrvrini; # timestamp for each test required servers verification start
    155 my %timesrvrend; # timestamp for each test required servers verification end
    156 my %timetoolini; # timestamp for each test command run starting
    157 my %timetoolend; # timestamp for each test command run stopping
    158 my %timesrvrlog; # timestamp for each test server logs lock removal
    159 my %timevrfyend; # timestamp for each test result verification end
    160 my $globalabort; # flag signalling program abort
    161 
    162 # values for $singletest_state
    163 use constant {
    164     ST_INIT => 0,
    165     ST_INITED => 2,
    166     ST_PREPROCESS => 3,
    167     ST_RUN => 4,
    168 };
    169 my %singletest_state;  # current state of singletest() by runner ID
    170 my %singletest_logs;   # log messages while in singletest array ref by runner
    171 my $singletest_bufferedrunner; # runner ID which is buffering logs
    172 my %runnerids;         # runner IDs by number
    173 my @runnersidle;       # runner IDs idle and ready to execute a test
    174 my %countforrunner;    # test count by runner ID
    175 my %runnersrunning;    # tests currently running by runner ID
    176 
    177 #######################################################################
    178 # variables that command line options may set
    179 #
    180 my $short;
    181 my $no_debuginfod;
    182 my $keepoutfiles; # keep stdout and stderr files after tests
    183 my $postmortem;   # display detailed info about failed tests
    184 my $run_disabled; # run the specific tests even if listed in DISABLED
    185 my $scrambleorder;
    186 my $jobs = 0;
    187 
    188 # Azure Pipelines specific variables
    189 my $AZURE_RUN_ID = 0;
    190 my $AZURE_RESULT_ID = 0;
    191 
    192 #######################################################################
    193 # logmsg is our general message logging subroutine.
    194 #
    195 sub logmsg {
    196     if($singletest_bufferedrunner) {
    197         # Logs are currently being buffered
    198         return singletest_logmsg(@_);
    199     }
    200     for(@_) {
    201         my $line = $_;
    202         if(!$line) {
    203             next;
    204         }
    205         if($is_wsl) {
    206             # use \r\n for WSL shell
    207             $line =~ s/\r?\n$/\r\n/g;
    208         }
    209         print "$line";
    210     }
    211 }
    212 
    213 #######################################################################
    214 # enable logmsg buffering for the given runner ID
    215 #
    216 sub logmsg_bufferfortest {
    217     my ($runnerid)=@_;
    218     if($jobs) {
    219         # Only enable buffering in multiprocess mode
    220         $singletest_bufferedrunner = $runnerid;
    221     }
    222 }
    223 #######################################################################
    224 # Store a log message in a buffer for this test
    225 # The messages can then be displayed all at once at the end of the test
    226 # which prevents messages from different tests from being interleaved.
    227 sub singletest_logmsg {
    228     if(!exists $singletest_logs{$singletest_bufferedrunner}) {
    229         # initialize to a reference to an empty anonymous array
    230         $singletest_logs{$singletest_bufferedrunner} = [];
    231     }
    232     my $logsref = $singletest_logs{$singletest_bufferedrunner};
    233     push @$logsref, @_;
    234 }
    235 
    236 #######################################################################
    237 # Stop buffering log messages, but don't touch them
    238 sub singletest_unbufferlogs {
    239     undef $singletest_bufferedrunner;
    240 }
    241 
    242 #######################################################################
    243 # Clear the buffered log messages & stop buffering after returning them
    244 sub singletest_dumplogs {
    245     if(!defined $singletest_bufferedrunner) {
    246         # probably not multiprocess mode and logs weren't buffered
    247         return undef;
    248     }
    249     my $logsref = $singletest_logs{$singletest_bufferedrunner};
    250     my $msg = join("", @$logsref);
    251     delete $singletest_logs{$singletest_bufferedrunner};
    252     singletest_unbufferlogs();
    253     return $msg;
    254 }
    255 
    256 sub catch_zap {
    257     my $signame = shift;
    258     print "runtests.pl received SIG$signame, exiting\r\n";
    259     $globalabort = 1;
    260 }
    261 $SIG{INT} = \&catch_zap;
    262 $SIG{TERM} = \&catch_zap;
    263 
    264 sub catch_usr1 {
    265     print "runtests.pl internal state:\r\n";
    266     print scalar(%runnersrunning) . " busy test runner(s) of " . scalar(keys %runnerids) . "\r\n";
    267     foreach my $rid (sort(keys(%runnersrunning))) {
    268         my $runnernum = "unknown";
    269         foreach my $rnum (keys %runnerids) {
    270             if($runnerids{$rnum} == $rid) {
    271                 $runnernum = $rnum;
    272                 last;
    273             }
    274         }
    275         print "Runner $runnernum (id $rid) running test $runnersrunning{$rid} in state $singletest_state{$rid}\r\n";
    276     }
    277 }
    278 
    279 eval {
    280     # some msys2 perl versions don't define SIGUSR1
    281     $SIG{USR1} = \&catch_usr1;
    282 };
    283 $SIG{PIPE} = 'IGNORE';  # these errors are captured in the read/write calls
    284 
    285 ##########################################################################
    286 # Clear all possible '*_proxy' environment variables for various protocols
    287 # to prevent them to interfere with our testing!
    288 
    289 foreach my $protocol (('ftp', 'http', 'ftps', 'https', 'no', 'all')) {
    290     my $proxy = "${protocol}_proxy";
    291     # clear lowercase version
    292     delete $ENV{$proxy} if($ENV{$proxy});
    293     # clear uppercase version
    294     delete $ENV{uc($proxy)} if($ENV{uc($proxy)});
    295 }
    296 
    297 # make sure we don't get affected by other variables that control our
    298 # behavior
    299 
    300 delete $ENV{'SSL_CERT_DIR'} if($ENV{'SSL_CERT_DIR'});
    301 delete $ENV{'SSL_CERT_PATH'} if($ENV{'SSL_CERT_PATH'});
    302 delete $ENV{'CURL_CA_BUNDLE'} if($ENV{'CURL_CA_BUNDLE'});
    303 
    304 # provide defaults from our config file for ENV vars not explicitly
    305 # set by the caller
    306 if(open(my $fd, "<", "config")) {
    307     while(my $line = <$fd>) {
    308         next if($line =~ /^#/);
    309         chomp $line;
    310         my ($name, $val) = split(/\s*:\s*/, $line, 2);
    311         $ENV{$name} = $val if(!$ENV{$name});
    312     }
    313     close($fd);
    314 }
    315 
    316 # Check if we have nghttpx available and if it talks http/3
    317 my $nghttpx_h3 = 0;
    318 if(!$ENV{"NGHTTPX"}) {
    319     $ENV{"NGHTTPX"} = checktestcmd("nghttpx");
    320 }
    321 if($ENV{"NGHTTPX"}) {
    322     my $cmd = "\"$ENV{'NGHTTPX'}\" -v 2>$dev_null";
    323     my $nghttpx_version=join(' ', `$cmd`);
    324     $nghttpx_h3 = $nghttpx_version =~ /nghttp3\//;
    325     chomp $nghttpx_h3;
    326 }
    327 
    328 
    329 #######################################################################
    330 # Get the list of tests that the tests/data/Makefile.am knows about!
    331 #
    332 my $disttests = "";
    333 sub get_disttests {
    334     # If a non-default $TESTDIR is being used there may not be any
    335     # Makefile.am in which case there's nothing to do.
    336     open(my $dh, "<", "$TESTDIR/Makefile.am") or return;
    337     while(<$dh>) {
    338         chomp $_;
    339         if(($_ =~ /^#/) ||($_ !~ /test/)) {
    340             next;
    341         }
    342         $disttests .= $_;
    343     }
    344     close($dh);
    345 }
    346 
    347 
    348 #######################################################################
    349 # Remove all files in the specified directory
    350 #
    351 sub cleardir {
    352     my $dir = $_[0];
    353     my $done = 1;  # success
    354     my $file;
    355 
    356     # Get all files
    357     opendir(my $dh, $dir) ||
    358         return 0; # can't open dir
    359     while($file = readdir($dh)) {
    360         # Don't clear the $PIDDIR or $LOCKDIR since those need to live beyond
    361         # one test
    362         if(($file !~ /^(\.|\.\.)\z/) &&
    363             "$file" ne $PIDDIR && "$file" ne $LOCKDIR) {
    364             if(-d "$dir/$file") {
    365                 if(!cleardir("$dir/$file")) {
    366                     $done = 0;
    367                 }
    368                 if(!rmdir("$dir/$file")) {
    369                     $done = 0;
    370                 }
    371             }
    372             else {
    373                 # Ignore stunnel since we cannot do anything about its locks
    374                 if(!unlink("$dir/$file") && "$file" !~ /_stunnel\.log$/) {
    375                     $done = 0;
    376                 }
    377             }
    378         }
    379     }
    380     closedir $dh;
    381     return $done;
    382 }
    383 
    384 
    385 #######################################################################
    386 # Given two array references, this function will store them in two temporary
    387 # files, run 'diff' on them, store the result and return the diff output!
    388 sub showdiff {
    389     my ($logdir, $firstref, $secondref)=@_;
    390 
    391     my $file1="$logdir/check-generated";
    392     my $file2="$logdir/check-expected";
    393 
    394     open(my $temp, ">", "$file1") || die "Failure writing diff file";
    395     for(@$firstref) {
    396         my $l = $_;
    397         $l =~ s/\r/[CR]/g;
    398         $l =~ s/\n/[LF]/g;
    399         $l =~ s/([^\x20-\x7f])/sprintf "%%%02x", ord $1/eg;
    400         print $temp $l;
    401         print $temp "\n";
    402     }
    403     close($temp) || die "Failure writing diff file";
    404 
    405     open($temp, ">", "$file2") || die "Failure writing diff file";
    406     for(@$secondref) {
    407         my $l = $_;
    408         $l =~ s/\r/[CR]/g;
    409         $l =~ s/\n/[LF]/g;
    410         $l =~ s/([^\x20-\x7f])/sprintf "%%%02x", ord $1/eg;
    411         print $temp $l;
    412         print $temp "\n";
    413     }
    414     close($temp) || die "Failure writing diff file";
    415     my @out = `diff -u $file2 $file1 2>$dev_null`;
    416 
    417     if(!$out[0]) {
    418         @out = `diff -c $file2 $file1 2>$dev_null`;
    419         if(!$out[0]) {
    420             logmsg "Failed to show diff. The diff tool may be missing.\n";
    421         }
    422     }
    423 
    424     return @out;
    425 }
    426 
    427 
    428 #######################################################################
    429 # compare test results with the expected output, we might filter off
    430 # some pattern that is allowed to differ, output test results
    431 #
    432 sub compare {
    433     my ($runnerid, $testnum, $testname, $subject, $firstref, $secondref)=@_;
    434 
    435     my $result = compareparts($firstref, $secondref);
    436 
    437     if($result) {
    438         # timestamp test result verification end
    439         $timevrfyend{$testnum} = Time::HiRes::time();
    440 
    441         if(!$short) {
    442             logmsg "\n $testnum: $subject FAILED:\n";
    443             my $logdir = getrunnerlogdir($runnerid);
    444             logmsg showdiff($logdir, $firstref, $secondref);
    445         }
    446         elsif(!$automakestyle) {
    447             logmsg "FAILED\n";
    448         }
    449         else {
    450             # automakestyle
    451             logmsg "FAIL: $testnum - $testname - $subject\n";
    452         }
    453     }
    454     return $result;
    455 }
    456 
    457 #######################################################################
    458 # Numeric-sort words in a string
    459 sub numsortwords {
    460     my ($string)=@_;
    461     return join(' ', sort { $a <=> $b } split(' ', $string));
    462 }
    463 
    464 #######################################################################
    465 # Parse and store the protocols in curl's Protocols: line
    466 sub parseprotocols {
    467     my ($line)=@_;
    468 
    469     @protocols = split(' ', lc($line));
    470 
    471     # Generate a "proto-ipv6" version of each protocol to match the
    472     # IPv6 <server> name and a "proto-unix" to match the variant which
    473     # uses Unix domain sockets. This works even if support isn't
    474     # compiled in because the <features> test will fail.
    475     push @protocols, map(("$_-ipv6", "$_-unix"), @protocols);
    476 
    477     # 'http-proxy' is used in test cases to do CONNECT through
    478     push @protocols, 'http-proxy';
    479 
    480     # 'https-mtls' is used for client certificate auth testing
    481     push @protocols, 'https-mtls';
    482 
    483     # 'none' is used in test cases to mean no server
    484     push @protocols, 'none';
    485 }
    486 
    487 
    488 #######################################################################
    489 # Check & display information about curl and the host the test suite runs on.
    490 # Information to do with servers is displayed in displayserverfeatures, after
    491 # the server initialization is performed.
    492 sub checksystemfeatures {
    493     my $proto;
    494     my $feat;
    495     my $curl;
    496     my $libcurl;
    497     my $versretval;
    498     my $versnoexec;
    499     my @version=();
    500     my @disabled;
    501     my $dis = "";
    502 
    503     my $curlverout="$LOGDIR/curlverout.log";
    504     my $curlvererr="$LOGDIR/curlvererr.log";
    505     my $versioncmd=exerunner() . shell_quote($CURL) . " --version 1>$curlverout 2>$curlvererr";
    506 
    507     unlink($curlverout);
    508     unlink($curlvererr);
    509 
    510     $versretval = runclient($versioncmd);
    511     $versnoexec = $!;
    512 
    513     my $current_time = int(time());
    514     $ENV{'SOURCE_DATE_EPOCH'} = $current_time;
    515     $DATE = strftime "%Y-%m-%d", gmtime($current_time);
    516 
    517     open(my $versout, "<", "$curlverout");
    518     @version = <$versout>;
    519     close($versout);
    520 
    521     open(my $disabledh, "-|", exerunner() . shell_quote($CURLINFO));
    522     while(<$disabledh>) {
    523         if($_ =~ /([^:]*): ([ONF]*)/) {
    524             my ($val, $toggle) = ($1, $2);
    525             push @disabled, $val if($toggle eq "OFF");
    526             $feature{$val} = 1 if($toggle eq "ON");
    527         }
    528     }
    529     close($disabledh);
    530 
    531     if($disabled[0]) {
    532         s/[\r\n]//g for @disabled;
    533         $dis = join(", ", @disabled);
    534     }
    535 
    536     $resolver="stock";
    537     for(@version) {
    538         chomp;
    539 
    540         if($_ =~ /^curl ([^ ]*)/) {
    541             $curl = $_;
    542             $CURLVERSION = $1;
    543             $CURLVERNUM = $CURLVERSION;
    544             $CURLVERNUM =~ s/^([0-9.]+)(.*)/$1/; # leading dots and numbers
    545             $curl =~ s/^(.*)(libcurl.*)/$1/g || die "Failure determining curl binary version";
    546 
    547             $libcurl = $2;
    548             if($curl =~ /win32|Windows|windows|mingw(32|64)/) {
    549                 # This is a Windows MinGW build or native build, we need to use
    550                 # Windows-style path.
    551                 $pwd = sys_native_current_path();
    552                 $feature{"win32"} = 1;
    553             }
    554             if($libcurl =~ /\sschannel\b/i) {
    555                 $feature{"Schannel"} = 1;
    556                 $feature{"SSLpinning"} = 1;
    557             }
    558             elsif($libcurl =~ /\sopenssl\b/i) {
    559                 $feature{"OpenSSL"} = 1;
    560                 $feature{"SSLpinning"} = 1;
    561             }
    562             elsif($libcurl =~ /\sgnutls\b/i) {
    563                 $feature{"GnuTLS"} = 1;
    564                 $feature{"SSLpinning"} = 1;
    565             }
    566             elsif($libcurl =~ /\srustls-ffi\b/i) {
    567                 $feature{"rustls"} = 1;
    568             }
    569             elsif($libcurl =~ /\swolfssl\b/i) {
    570                 $feature{"wolfssl"} = 1;
    571                 $feature{"SSLpinning"} = 1;
    572             }
    573             elsif($libcurl =~ /\s(BoringSSL|AWS-LC)\b/i) {
    574                 # OpenSSL compatible API
    575                 $feature{"OpenSSL"} = 1;
    576                 $feature{"SSLpinning"} = 1;
    577             }
    578             elsif($libcurl =~ /\slibressl\b/i) {
    579                 # OpenSSL compatible API
    580                 $feature{"OpenSSL"} = 1;
    581                 $feature{"SSLpinning"} = 1;
    582             }
    583             elsif($libcurl =~ /\squictls\b/i) {
    584                 # OpenSSL compatible API
    585                 $feature{"OpenSSL"} = 1;
    586                 $feature{"SSLpinning"} = 1;
    587             }
    588             elsif($libcurl =~ /\smbedTLS\b/i) {
    589                 $feature{"mbedtls"} = 1;
    590                 $feature{"SSLpinning"} = 1;
    591             }
    592             if($libcurl =~ /ares/i) {
    593                 $feature{"c-ares"} = 1;
    594                 $resolver="c-ares";
    595             }
    596             if($libcurl =~ /nghttp2/i) {
    597                 # nghttp2 supports h2c
    598                 $feature{"h2c"} = 1;
    599             }
    600             if($libcurl =~ /AppleIDN/) {
    601                 $feature{"AppleIDN"} = 1;
    602             }
    603             if($libcurl =~ /WinIDN/) {
    604                 $feature{"WinIDN"} = 1;
    605             }
    606             if($libcurl =~ /libidn2/) {
    607                 $feature{"libidn2"} = 1;
    608             }
    609             if($libcurl =~ /libssh2/i) {
    610                 $feature{"libssh2"} = 1;
    611             }
    612             if($libcurl =~ /libssh\/([0-9.]*)\//i) {
    613                 $feature{"libssh"} = 1;
    614                 if($1 =~ /(\d+)\.(\d+).(\d+)/) {
    615                     my $v = $1 * 100 + $2 * 10 + $3;
    616                     if($v < 94) {
    617                         # before 0.9.4
    618                         $feature{"oldlibssh"} = 1;
    619                     }
    620                 }
    621             }
    622             if($libcurl =~ /wolfssh/i) {
    623                 $feature{"wolfssh"} = 1;
    624             }
    625         }
    626         elsif($_ =~ /^Protocols: (.*)/i) {
    627             $proto = $1;
    628             # these are the protocols compiled in to this libcurl
    629             parseprotocols($proto);
    630         }
    631         elsif($_ =~ /^Features: (.*)/i) {
    632             $feat = $1;
    633 
    634             # built with memory tracking support (--enable-curldebug); may be disabled later
    635             $feature{"TrackMemory"} = $feat =~ /TrackMemory/i;
    636             # curl was built with --enable-debug
    637             $feature{"Debug"} = $feat =~ /Debug/i;
    638             # ssl enabled
    639             $feature{"SSL"} = $feat =~ /SSL/i;
    640             # multiple ssl backends available.
    641             $feature{"MultiSSL"} = $feat =~ /MultiSSL/i;
    642             # large file support
    643             $feature{"Largefile"} = $feat =~ /Largefile/i;
    644             # IDN support
    645             $feature{"IDN"} = $feat =~ /IDN/i;
    646             # IPv6 support
    647             $feature{"IPv6"} = $feat =~ /IPv6/i;
    648             # Unix sockets support
    649             $feature{"UnixSockets"} = $feat =~ /UnixSockets/i;
    650             # libz compression
    651             $feature{"libz"} = $feat =~ /libz/i;
    652             # Brotli compression
    653             $feature{"brotli"} = $feat =~ /brotli/i;
    654             # Zstd compression
    655             $feature{"zstd"} = $feat =~ /zstd/i;
    656             # NTLM enabled
    657             $feature{"NTLM"} = $feat =~ /NTLM/i;
    658             # NTLM delegation to winbind daemon ntlm_auth helper enabled
    659             $feature{"NTLM_WB"} = $feat =~ /NTLM_WB/i;
    660             # SSPI enabled
    661             $feature{"SSPI"} = $feat =~ /SSPI/i;
    662             # GSS-API enabled
    663             $feature{"GSS-API"} = $feat =~ /GSS-API/i;
    664             # Kerberos enabled
    665             $feature{"Kerberos"} = $feat =~ /Kerberos/i;
    666             # SPNEGO enabled
    667             $feature{"SPNEGO"} = $feat =~ /SPNEGO/i;
    668             # TLS-SRP enabled
    669             $feature{"TLS-SRP"} = $feat =~ /TLS-SRP/i;
    670             # PSL enabled
    671             $feature{"PSL"} = $feat =~ /PSL/i;
    672             # alt-svc enabled
    673             $feature{"alt-svc"} = $feat =~ /alt-svc/i;
    674             # HSTS support
    675             $feature{"HSTS"} = $feat =~ /HSTS/i;
    676             $feature{"asyn-rr"} = $feat =~ /asyn-rr/;
    677             if($feat =~ /AsynchDNS/i) {
    678                 if(!$feature{"c-ares"} || $feature{"asyn-rr"}) {
    679                     # this means threaded resolver
    680                     $feature{"threaded-resolver"} = 1;
    681                     $resolver="threaded";
    682 
    683                     # does not count as "real" c-ares
    684                     $feature{"c-ares"} = 0;
    685                 }
    686             }
    687             # http2 enabled
    688             $feature{"http/2"} = $feat =~ /HTTP2/;
    689             if($feature{"http/2"}) {
    690                 push @protocols, 'http/2';
    691             }
    692             # http3 enabled
    693             $feature{"http/3"} = $feat =~ /HTTP3/;
    694             if($feature{"http/3"}) {
    695                 push @protocols, 'http/3';
    696             }
    697             # https proxy support
    698             $feature{"HTTPS-proxy"} = $feat =~ /HTTPS-proxy/;
    699             if($feature{"HTTPS-proxy"}) {
    700                 # 'https-proxy' is used as "server" so consider it a protocol
    701                 push @protocols, 'https-proxy';
    702             }
    703             # Unicode support
    704             $feature{"Unicode"} = $feat =~ /Unicode/i;
    705             # Thread-safe init
    706             $feature{"threadsafe"} = $feat =~ /threadsafe/i;
    707             $feature{"HTTPSRR"} = $feat =~ /HTTPSRR/;
    708             $feature{"ECH"} = $feat =~ /ECH/;
    709         }
    710         #
    711         # Test harness currently uses a non-stunnel server in order to
    712         # run HTTP TLS-SRP tests required when curl is built with https
    713         # protocol support and TLS-SRP feature enabled. For convenience
    714         # 'httptls' may be included in the test harness protocols array
    715         # to differentiate this from classic stunnel based 'https' test
    716         # harness server.
    717         #
    718         if($feature{"TLS-SRP"}) {
    719             my $add_httptls;
    720             for(@protocols) {
    721                 if($_ =~ /^https(-ipv6|)$/) {
    722                     $add_httptls=1;
    723                     last;
    724                 }
    725             }
    726             if($add_httptls && (! grep /^httptls$/, @protocols)) {
    727                 push @protocols, 'httptls';
    728                 push @protocols, 'httptls-ipv6';
    729             }
    730         }
    731     }
    732 
    733     if(!$curl) {
    734         logmsg "unable to get curl's version, further details are:\n";
    735         logmsg "issued command: \n";
    736         logmsg "$versioncmd \n";
    737         if($versretval == -1) {
    738             logmsg "command failed with: \n";
    739             logmsg "$versnoexec \n";
    740         }
    741         elsif($versretval & 127) {
    742             logmsg sprintf("command died with signal %d, and %s coredump.\n",
    743                            ($versretval & 127), ($versretval & 128)?"a":"no");
    744         }
    745         else {
    746             logmsg sprintf("command exited with value %d \n", $versretval >> 8);
    747         }
    748         logmsg "contents of $curlverout: \n";
    749         displaylogcontent("$curlverout");
    750         logmsg "contents of $curlvererr: \n";
    751         displaylogcontent("$curlvererr");
    752         die "couldn't get curl's version";
    753     }
    754 
    755     if(-r "../lib/curl_config.h") {
    756         open(my $conf, "<", "../lib/curl_config.h");
    757         while(<$conf>) {
    758             if($_ =~ /^\#define HAVE_GETRLIMIT/) {
    759                 # set if system has getrlimit()
    760                 $feature{"getrlimit"} = 1;
    761             }
    762         }
    763         close($conf);
    764     }
    765 
    766     if($feature{"IPv6"}) {
    767         # client has IPv6 support
    768 
    769         # check if the HTTP server has it!
    770         my $cmd = server_exe('sws')." --version";
    771         my @sws = `$cmd`;
    772         if($sws[0] =~ /IPv6/) {
    773             # HTTP server has IPv6 support!
    774             $http_ipv6 = 1;
    775         }
    776 
    777         # check if the FTP server has it!
    778         $cmd = server_exe('sockfilt')." --version";
    779         @sws = `$cmd`;
    780         if($sws[0] =~ /IPv6/) {
    781             # FTP server has IPv6 support!
    782             $ftp_ipv6 = 1;
    783         }
    784     }
    785 
    786     if($feature{"UnixSockets"}) {
    787         # client has Unix sockets support, check whether the HTTP server has it
    788         my $cmd = server_exe('sws')." --version";
    789         my @sws = `$cmd`;
    790         $http_unix = 1 if($sws[0] =~ /unix/);
    791     }
    792 
    793     open(my $manh, "-|", shell_quote($CURL) . " -M 2>&1");
    794     while(my $s = <$manh>) {
    795         if($s =~ /built-in manual was disabled at build-time/) {
    796             $feature{"manual"} = 0;
    797             last;
    798         }
    799         $feature{"manual"} = 1;
    800         last;
    801     }
    802     close($manh);
    803 
    804     $feature{"unittest"} = 1;
    805     $feature{"nghttpx"} = !!$ENV{'NGHTTPX'};
    806     $feature{"nghttpx-h3"} = !!$nghttpx_h3;
    807 
    808     # Use this as a proxy for any cryptographic authentication
    809     $feature{"crypto"} = $feature{"NTLM"} || $feature{"Kerberos"} || $feature{"SPNEGO"};
    810     $feature{"local-http"} = servers::localhttp();
    811     $feature{"codeset-utf8"} = lc(langinfo(CODESET())) eq "utf-8";
    812 
    813     # make each protocol an enabled "feature"
    814     for my $p (@protocols) {
    815         $feature{$p} = 1;
    816     }
    817     # 'socks' was once here but is now removed
    818 
    819     if($torture) {
    820         if(!$feature{"TrackMemory"}) {
    821             die "can't run torture tests since curl was built without ".
    822                 "TrackMemory feature (--enable-curldebug)";
    823         }
    824         if($feature{"threaded-resolver"} && !$valgrind) {
    825             die "can't run torture tests since curl was built with the ".
    826                 "threaded resolver, and we aren't running with valgrind";
    827         }
    828     }
    829 
    830     my $hostname=join(' ', runclientoutput("hostname"));
    831     chomp $hostname;
    832     my $hosttype=join(' ', runclientoutput("uname -a"));
    833     chomp $hosttype;
    834     my $hostos=$^O;
    835 
    836     my $havediff;
    837     if(system("diff $TESTDIR/DISABLED $TESTDIR/DISABLED 2>$dev_null") == 0) {
    838       $havediff = 'available';
    839     }
    840     else {
    841       $havediff = 'missing';
    842     }
    843 
    844     # display summary information about curl and the test host
    845     logmsg ("********* System characteristics ******** \n",
    846             "* $curl\n",
    847             "* $libcurl\n",
    848             "* Protocols: $proto\n",
    849             "* Features: $feat\n",
    850             "* Disabled: $dis\n",
    851             "* Host: $hostname\n",
    852             "* System: $hosttype\n",
    853             "* OS: $hostos\n",
    854             "* Perl: $^V ($^X)\n",
    855             "* diff: $havediff\n",
    856             "* Args: $args\n");
    857 
    858     if($jobs) {
    859         # Only show if not the default for now
    860         logmsg "* Jobs: $jobs\n";
    861     }
    862     # Disable memory tracking when using threaded resolver
    863     if($feature{"TrackMemory"} && $feature{"threaded-resolver"}) {
    864         logmsg("*\n",
    865                "*** DISABLES TrackMemory (memory tracking) when using threaded resolver\n",
    866                "*\n");
    867         $feature{"TrackMemory"} = 0;
    868     }
    869 
    870     logmsg sprintf("* Env: %s%s%s%s", $valgrind?"Valgrind ":"",
    871                    $run_duphandle?"test-duphandle ":"",
    872                    $run_event_based?"event-based ":"",
    873                    $nghttpx_h3);
    874     logmsg sprintf("%s\n", $libtool?"Libtool ":"");
    875     logmsg ("* Seed: $randseed\n");
    876 }
    877 
    878 #######################################################################
    879 # display information about server features
    880 #
    881 sub displayserverfeatures {
    882     logmsg sprintf("* Servers: %s", $stunnel?"SSL ":"");
    883     logmsg sprintf("%s", $http_ipv6?"HTTP-IPv6 ":"");
    884     logmsg sprintf("%s", $http_unix?"HTTP-unix ":"");
    885     logmsg sprintf("%s\n", $ftp_ipv6?"FTP-IPv6 ":"");
    886     logmsg "***************************************** \n";
    887 }
    888 
    889 #######################################################################
    890 # Provide time stamps for single test skipped events
    891 #
    892 sub timestampskippedevents {
    893     my $testnum = $_[0];
    894 
    895     return if((not defined($testnum)) || ($testnum < 1));
    896 
    897     if($timestats) {
    898 
    899         if($timevrfyend{$testnum}) {
    900             return;
    901         }
    902         elsif($timesrvrlog{$testnum}) {
    903             $timevrfyend{$testnum} = $timesrvrlog{$testnum};
    904             return;
    905         }
    906         elsif($timetoolend{$testnum}) {
    907             $timevrfyend{$testnum} = $timetoolend{$testnum};
    908             $timesrvrlog{$testnum} = $timetoolend{$testnum};
    909         }
    910         elsif($timetoolini{$testnum}) {
    911             $timevrfyend{$testnum} = $timetoolini{$testnum};
    912             $timesrvrlog{$testnum} = $timetoolini{$testnum};
    913             $timetoolend{$testnum} = $timetoolini{$testnum};
    914         }
    915         elsif($timesrvrend{$testnum}) {
    916             $timevrfyend{$testnum} = $timesrvrend{$testnum};
    917             $timesrvrlog{$testnum} = $timesrvrend{$testnum};
    918             $timetoolend{$testnum} = $timesrvrend{$testnum};
    919             $timetoolini{$testnum} = $timesrvrend{$testnum};
    920         }
    921         elsif($timesrvrini{$testnum}) {
    922             $timevrfyend{$testnum} = $timesrvrini{$testnum};
    923             $timesrvrlog{$testnum} = $timesrvrini{$testnum};
    924             $timetoolend{$testnum} = $timesrvrini{$testnum};
    925             $timetoolini{$testnum} = $timesrvrini{$testnum};
    926             $timesrvrend{$testnum} = $timesrvrini{$testnum};
    927         }
    928         elsif($timeprepini{$testnum}) {
    929             $timevrfyend{$testnum} = $timeprepini{$testnum};
    930             $timesrvrlog{$testnum} = $timeprepini{$testnum};
    931             $timetoolend{$testnum} = $timeprepini{$testnum};
    932             $timetoolini{$testnum} = $timeprepini{$testnum};
    933             $timesrvrend{$testnum} = $timeprepini{$testnum};
    934             $timesrvrini{$testnum} = $timeprepini{$testnum};
    935         }
    936     }
    937 }
    938 
    939 
    940 # Setup CI Test Run
    941 sub citest_starttestrun {
    942     if(azure_check_environment()) {
    943         $AZURE_RUN_ID = azure_create_test_run($ACURL);
    944         logmsg "Azure Run ID: $AZURE_RUN_ID\n" if($verbose);
    945     }
    946     # Appveyor doesn't require anything here
    947 }
    948 
    949 
    950 # Register the test case with the CI runner
    951 sub citest_starttest {
    952     my $testnum = $_[0];
    953 
    954     # get the name of the test early
    955     my $testname= (getpart("client", "name"))[0];
    956     chomp $testname;
    957 
    958     # create test result in CI services
    959     if(azure_check_environment() && $AZURE_RUN_ID) {
    960         $AZURE_RESULT_ID = azure_create_test_result($ACURL, $AZURE_RUN_ID, $testnum, $testname);
    961     }
    962     elsif(appveyor_check_environment()) {
    963         appveyor_create_test_result($ACURL, $testnum, $testname);
    964     }
    965 }
    966 
    967 
    968 # Submit the test case result with the CI runner
    969 sub citest_finishtest {
    970     my ($testnum, $error) = @_;
    971     # update test result in CI services
    972     if(azure_check_environment() && $AZURE_RUN_ID && $AZURE_RESULT_ID) {
    973         $AZURE_RESULT_ID = azure_update_test_result($ACURL, $AZURE_RUN_ID, $AZURE_RESULT_ID, $testnum, $error,
    974                                                     $timeprepini{$testnum}, $timevrfyend{$testnum});
    975     }
    976     elsif(appveyor_check_environment()) {
    977         appveyor_update_test_result($ACURL, $testnum, $error, $timeprepini{$testnum}, $timevrfyend{$testnum});
    978     }
    979 }
    980 
    981 # Complete CI test run
    982 sub citest_finishtestrun {
    983     if(azure_check_environment() && $AZURE_RUN_ID) {
    984         $AZURE_RUN_ID = azure_update_test_run($ACURL, $AZURE_RUN_ID);
    985     }
    986     # Appveyor doesn't require anything here
    987 }
    988 
    989 
    990 # add one set of test timings from the runner to global set
    991 sub updatetesttimings {
    992     my ($testnum, %testtimings)=@_;
    993 
    994     if(defined $testtimings{"timeprepini"}) {
    995         $timeprepini{$testnum} = $testtimings{"timeprepini"};
    996     }
    997     if(defined $testtimings{"timesrvrini"}) {
    998         $timesrvrini{$testnum} = $testtimings{"timesrvrini"};
    999     }
   1000     if(defined $testtimings{"timesrvrend"}) {
   1001         $timesrvrend{$testnum} = $testtimings{"timesrvrend"};
   1002     }
   1003     if(defined $testtimings{"timetoolini"}) {
   1004         $timetoolini{$testnum} = $testtimings{"timetoolini"};
   1005     }
   1006     if(defined $testtimings{"timetoolend"}) {
   1007         $timetoolend{$testnum} = $testtimings{"timetoolend"};
   1008     }
   1009     if(defined $testtimings{"timesrvrlog"}) {
   1010         $timesrvrlog{$testnum} = $testtimings{"timesrvrlog"};
   1011     }
   1012 }
   1013 
   1014 
   1015 #######################################################################
   1016 # Return the log directory for the given test runner
   1017 sub getrunnernumlogdir {
   1018     my $runnernum = $_[0];
   1019     return $jobs > 1 ? "$LOGDIR/$runnernum" : $LOGDIR;
   1020 }
   1021 
   1022 #######################################################################
   1023 # Return the log directory for the given test runner ID
   1024 sub getrunnerlogdir {
   1025     my $runnerid = $_[0];
   1026     if($jobs <= 1) {
   1027         return $LOGDIR;
   1028     }
   1029     # TODO: speed up this O(n) operation
   1030     for my $runnernum (keys %runnerids) {
   1031         if($runnerid eq $runnerids{$runnernum}) {
   1032             return "$LOGDIR/$runnernum";
   1033         }
   1034     }
   1035     die "Internal error: runner ID $runnerid not found";
   1036 }
   1037 
   1038 
   1039 #######################################################################
   1040 # Verify that this test case should be run
   1041 sub singletest_shouldrun {
   1042     my $testnum = $_[0];
   1043     my $why;   # why the test won't be run
   1044     my $errorreturncode = 1; # 1 means normal error, 2 means ignored error
   1045     my @what;  # what features are needed
   1046 
   1047     if($disttests !~ /test$testnum(\W|\z)/ ) {
   1048         logmsg "Warning: test$testnum not present in tests/data/Makefile.am\n";
   1049     }
   1050     if($disabled{$testnum}) {
   1051         if(!$run_disabled) {
   1052             $why = "listed in DISABLED";
   1053         }
   1054         else {
   1055             logmsg "Warning: test$testnum is explicitly disabled\n";
   1056         }
   1057     }
   1058     if($ignored{$testnum}) {
   1059         logmsg "Warning: test$testnum result is ignored\n";
   1060         $errorreturncode = 2;
   1061     }
   1062 
   1063     if(loadtest("${TESTDIR}/test${testnum}")) {
   1064         if($verbose) {
   1065             # this is not a test
   1066             logmsg "RUN: $testnum doesn't look like a test case\n";
   1067         }
   1068         $why = "no test";
   1069     }
   1070     else {
   1071         @what = getpart("client", "features");
   1072     }
   1073 
   1074     # We require a feature to be present
   1075     for(@what) {
   1076         my $f = $_;
   1077         $f =~ s/\s//g;
   1078 
   1079         if($f =~ /^([^!].*)$/) {
   1080             if($feature{$1}) {
   1081                 next;
   1082             }
   1083 
   1084             $why = "curl lacks $1 support";
   1085             last;
   1086         }
   1087     }
   1088 
   1089     # We require a feature to not be present
   1090     if(!$why) {
   1091         for(@what) {
   1092             my $f = $_;
   1093             $f =~ s/\s//g;
   1094 
   1095             if($f =~ /^!(.*)$/) {
   1096                 if(!$feature{$1}) {
   1097                     next;
   1098                 }
   1099             }
   1100             else {
   1101                 next;
   1102             }
   1103 
   1104             $why = "curl has $1 support";
   1105             last;
   1106         }
   1107     }
   1108 
   1109     my @info_keywords;
   1110     if(!$why) {
   1111         @info_keywords = getpart("info", "keywords");
   1112 
   1113         if(!$info_keywords[0]) {
   1114             $why = "missing the <keywords> section!";
   1115         }
   1116         # Only evaluate keywords if the section is present.
   1117         else {
   1118             # Prefix features with "feat:" and add to keywords list.
   1119             push @info_keywords, map { "feat:" . lc($_) } getpart("client", "features");
   1120 
   1121             my $match;
   1122             for my $k (@info_keywords) {
   1123                 chomp $k;
   1124                 if($disabled_keywords{lc($k)}) {
   1125                     if($k =~ /^feat:/) {
   1126                         $why = "disabled by feature";
   1127                     }
   1128                     else {
   1129                         $why = "disabled by keyword";
   1130                     }
   1131                 }
   1132                 elsif($enabled_keywords{lc($k)}) {
   1133                     $match = 1;
   1134                 }
   1135                 if($ignored_keywords{lc($k)}) {
   1136                     logmsg "Warning: test$testnum result is ignored due to $k\n";
   1137                     $errorreturncode = 2;
   1138                 }
   1139             }
   1140 
   1141             if(!$why && !$match && %enabled_keywords) {
   1142                 if(grep { /^feat:/ } keys %enabled_keywords) {
   1143                     $why = "disabled by missing feature";
   1144                 }
   1145                 else {
   1146                     $why = "disabled by missing keyword";
   1147                 }
   1148             }
   1149         }
   1150     }
   1151 
   1152     if(!$why && defined $custom_skip_reasons{test}{$testnum}) {
   1153         $why = $custom_skip_reasons{test}{$testnum};
   1154     }
   1155 
   1156     if(!$why && defined $custom_skip_reasons{tool}) {
   1157         foreach my $tool (getpart("client", "tool")) {
   1158             foreach my $tool_skip_pattern (keys %{$custom_skip_reasons{tool}}) {
   1159                 if($tool =~ /$tool_skip_pattern/i) {
   1160                     $why = $custom_skip_reasons{tool}{$tool_skip_pattern};
   1161                 }
   1162             }
   1163         }
   1164     }
   1165 
   1166     if(!$why && defined $custom_skip_reasons{keyword}) {
   1167         foreach my $keyword (@info_keywords) {
   1168             foreach my $keyword_skip_pattern (keys %{$custom_skip_reasons{keyword}}) {
   1169                 if($keyword =~ /$keyword_skip_pattern/i) {
   1170                     $why = $custom_skip_reasons{keyword}{$keyword_skip_pattern};
   1171                 }
   1172             }
   1173         }
   1174     }
   1175 
   1176     return ($why, $errorreturncode);
   1177 }
   1178 
   1179 
   1180 #######################################################################
   1181 # Print the test name and count tests
   1182 sub singletest_count {
   1183     my ($testnum, $why) = @_;
   1184 
   1185     if($why && !$listonly) {
   1186         # there's a problem, count it as "skipped"
   1187         $skipped{$why}++;
   1188         $teststat[$testnum]=$why; # store reason for this test case
   1189 
   1190         if(!$short) {
   1191             if($skipped{$why} <= 3) {
   1192                 # show only the first three skips for each reason
   1193                 logmsg sprintf("test %04d SKIPPED: $why\n", $testnum);
   1194             }
   1195         }
   1196 
   1197         timestampskippedevents($testnum);
   1198         return -1;
   1199     }
   1200 
   1201     # At this point we've committed to run this test
   1202     logmsg sprintf("test %04d...", $testnum) if(!$automakestyle);
   1203 
   1204     # name of the test
   1205     my $testname= (getpart("client", "name"))[0];
   1206     chomp $testname;
   1207     logmsg "[$testname]\n" if(!$short);
   1208 
   1209     if($listonly) {
   1210         timestampskippedevents($testnum);
   1211     }
   1212     return 0;
   1213 }
   1214 
   1215 # Make sure all line endings in the array are the same: CRLF
   1216 sub normalize_text {
   1217     my ($ref) = @_;
   1218     s/\r\n/\n/g for @$ref;
   1219     s/\n/\r\n/g for @$ref;
   1220 }
   1221 
   1222 #######################################################################
   1223 # Verify test succeeded
   1224 sub singletest_check {
   1225     my ($runnerid, $testnum, $cmdres, $CURLOUT, $tool, $usedvalgrind)=@_;
   1226 
   1227     # Skip all the verification on torture tests
   1228     if($torture) {
   1229         # timestamp test result verification end
   1230         $timevrfyend{$testnum} = Time::HiRes::time();
   1231         return -2;
   1232     }
   1233 
   1234     my $logdir = getrunnerlogdir($runnerid);
   1235     my @err = getpart("verify", "errorcode");
   1236     my $errorcode = $err[0] || "0";
   1237     my $ok="";
   1238     my $res;
   1239     chomp $errorcode;
   1240     my $testname= (getpart("client", "name"))[0];
   1241     chomp $testname;
   1242     # what parts to cut off from stdout/stderr
   1243     my @stripfile = getpart("verify", "stripfile");
   1244 
   1245     my @validstdout = getpart("verify", "stdout");
   1246     # get all attributes
   1247     my %hash = getpartattr("verify", "stdout");
   1248 
   1249     my $loadfile = $hash{'loadfile'};
   1250     if($loadfile) {
   1251         open(my $tmp, "<", "$loadfile") || die "Cannot open file $loadfile: $!";
   1252         @validstdout = <$tmp>;
   1253         close($tmp);
   1254 
   1255         # Enforce LF newlines on load
   1256         s/\r\n/\n/g for @validstdout;
   1257     }
   1258 
   1259     if(@validstdout) {
   1260         # verify redirected stdout
   1261         my @actual = loadarray(stdoutfilename($logdir, $testnum));
   1262 
   1263         foreach my $strip (@stripfile) {
   1264             chomp $strip;
   1265             my @newgen;
   1266             for(@actual) {
   1267                 eval $strip;
   1268                 if($_) {
   1269                     push @newgen, $_;
   1270                 }
   1271             }
   1272             # this is to get rid of array entries that vanished (zero
   1273             # length) because of replacements
   1274             @actual = @newgen;
   1275         }
   1276 
   1277         # get the mode attribute
   1278         my $filemode=$hash{'mode'};
   1279         if($filemode && ($filemode eq "text")) {
   1280             normalize_text(\@validstdout);
   1281             normalize_text(\@actual);
   1282         }
   1283 
   1284         if($hash{'nonewline'}) {
   1285             # Yes, we must cut off the final newline from the final line
   1286             # of the protocol data
   1287             chomp($validstdout[-1]);
   1288         }
   1289 
   1290         if($hash{'crlf'}) {
   1291             subnewlines(0, \$_) for @validstdout;
   1292         }
   1293 
   1294         $res = compare($runnerid, $testnum, $testname, "stdout", \@actual, \@validstdout);
   1295         if($res) {
   1296             return -1;
   1297         }
   1298         $ok .= "s";
   1299     }
   1300     else {
   1301         $ok .= "-"; # stdout not checked
   1302     }
   1303 
   1304     my @validstderr = getpart("verify", "stderr");
   1305     if(@validstderr) {
   1306         # verify redirected stderr
   1307         my @actual = loadarray(stderrfilename($logdir, $testnum));
   1308 
   1309         foreach my $strip (@stripfile) {
   1310             chomp $strip;
   1311             my @newgen;
   1312             for(@actual) {
   1313                 eval $strip;
   1314                 if($_) {
   1315                     push @newgen, $_;
   1316                 }
   1317             }
   1318             # this is to get rid of array entries that vanished (zero
   1319             # length) because of replacements
   1320             @actual = @newgen;
   1321         }
   1322 
   1323         # get all attributes
   1324         my %hash = getpartattr("verify", "stderr");
   1325 
   1326         # get the mode attribute
   1327         my $filemode=$hash{'mode'};
   1328         if($filemode && ($filemode eq "text")) {
   1329             normalize_text(\@validstderr);
   1330             normalize_text(\@actual);
   1331         }
   1332 
   1333         if($hash{'nonewline'}) {
   1334             # Yes, we must cut off the final newline from the final line
   1335             # of the protocol data
   1336             chomp($validstderr[-1]);
   1337         }
   1338 
   1339         if($hash{'crlf'}) {
   1340             subnewlines(0, \$_) for @validstderr;
   1341         }
   1342 
   1343         $res = compare($runnerid, $testnum, $testname, "stderr", \@actual, \@validstderr);
   1344         if($res) {
   1345             return -1;
   1346         }
   1347         $ok .= "r";
   1348     }
   1349     else {
   1350         $ok .= "-"; # stderr not checked
   1351     }
   1352 
   1353     # what to cut off from the live protocol sent by curl
   1354     my @strip = getpart("verify", "strip");
   1355 
   1356     # what parts to cut off from the protocol & upload
   1357     my @strippart = getpart("verify", "strippart");
   1358 
   1359     # this is the valid protocol blurb curl should generate
   1360     my @protocol= getpart("verify", "protocol");
   1361     if(@protocol) {
   1362         # Verify the sent request
   1363         my @out = loadarray("$logdir/$SERVERIN");
   1364 
   1365         # check if there's any attributes on the verify/protocol section
   1366         my %hash = getpartattr("verify", "protocol");
   1367 
   1368         if($hash{'nonewline'}) {
   1369             # Yes, we must cut off the final newline from the final line
   1370             # of the protocol data
   1371             chomp($protocol[-1]);
   1372         }
   1373 
   1374         for(@strip) {
   1375             # strip off all lines that match the patterns from both arrays
   1376             chomp $_;
   1377             @out = striparray( $_, \@out);
   1378             @protocol= striparray( $_, \@protocol);
   1379         }
   1380 
   1381         for my $strip (@strippart) {
   1382             chomp $strip;
   1383             for(@out) {
   1384                 eval $strip;
   1385             }
   1386         }
   1387 
   1388         if($hash{'crlf'}) {
   1389             subnewlines(1, \$_) for @protocol;
   1390         }
   1391 
   1392         if((!$out[0] || ($out[0] eq "")) && $protocol[0]) {
   1393             logmsg "\n $testnum: protocol FAILED!\n".
   1394                 " There was no content at all in the file $logdir/$SERVERIN.\n".
   1395                 " Server glitch? Total curl failure? Returned: $cmdres\n";
   1396             # timestamp test result verification end
   1397             $timevrfyend{$testnum} = Time::HiRes::time();
   1398             return -1;
   1399         }
   1400 
   1401         $res = compare($runnerid, $testnum, $testname, "protocol", \@out, \@protocol);
   1402         if($res) {
   1403             return -1;
   1404         }
   1405 
   1406         $ok .= "p";
   1407 
   1408     }
   1409     else {
   1410         $ok .= "-"; # protocol not checked
   1411     }
   1412 
   1413     my %replyattr = getpartattr("reply", "data");
   1414     my @reply;
   1415     if(partexists("reply", "datacheck")) {
   1416         for my $partsuffix (('', '1', '2', '3', '4')) {
   1417             my @replycheckpart = getpart("reply", "datacheck".$partsuffix);
   1418             if(@replycheckpart) {
   1419                 my %replycheckpartattr = getpartattr("reply", "datacheck".$partsuffix);
   1420                 # get the mode attribute
   1421                 my $filemode=$replycheckpartattr{'mode'};
   1422                 if($filemode && ($filemode eq "text")) {
   1423                     normalize_text(\@replycheckpart);
   1424                 }
   1425                 if($replycheckpartattr{'nonewline'}) {
   1426                     # Yes, we must cut off the final newline from the final line
   1427                     # of the datacheck
   1428                     chomp($replycheckpart[-1]);
   1429                 }
   1430                 if($replycheckpartattr{'crlf'}) {
   1431                     subnewlines(0, \$_) for @replycheckpart;
   1432                 }
   1433                 push(@reply, @replycheckpart);
   1434             }
   1435         }
   1436     }
   1437     else {
   1438         # check against the data section
   1439         @reply = getpart("reply", "data");
   1440         if(@reply) {
   1441             if($replyattr{'nonewline'}) {
   1442                 # cut off the final newline from the final line of the data
   1443                 chomp($reply[-1]);
   1444             }
   1445         }
   1446         # get the mode attribute
   1447         my $filemode=$replyattr{'mode'};
   1448         if($filemode && ($filemode eq "text")) {
   1449             normalize_text(\@reply);
   1450         }
   1451         if($replyattr{'crlf'}) {
   1452             subnewlines(0, \$_) for @reply;
   1453         }
   1454     }
   1455 
   1456     if(!$replyattr{'nocheck'} && (@reply || $replyattr{'sendzero'})) {
   1457         # verify the received data
   1458         my @out = loadarray($CURLOUT);
   1459 
   1460         # get the mode attribute
   1461         my $filemode=$replyattr{'mode'};
   1462         if($filemode && ($filemode eq "text")) {
   1463             normalize_text(\@out);
   1464         }
   1465         $res = compare($runnerid, $testnum, $testname, "data", \@out, \@reply);
   1466         if($res) {
   1467             return -1;
   1468         }
   1469         $ok .= "d";
   1470     }
   1471     else {
   1472         $ok .= "-"; # data not checked
   1473     }
   1474 
   1475     # if this section exists, we verify upload
   1476     my @upload = getpart("verify", "upload");
   1477     if(@upload) {
   1478         my %hash = getpartattr("verify", "upload");
   1479         if($hash{'nonewline'}) {
   1480             # cut off the final newline from the final line of the upload data
   1481             chomp($upload[-1]);
   1482         }
   1483         for my $line (@upload) {
   1484             subbase64(\$line);
   1485             subsha256base64file(\$line);
   1486             substrippemfile(\$line);
   1487         }
   1488 
   1489         # verify uploaded data
   1490         my @out = loadarray("$logdir/upload.$testnum");
   1491         for my $strip (@strippart) {
   1492             chomp $strip;
   1493             for(@out) {
   1494                 eval $strip;
   1495             }
   1496         }
   1497         if($hash{'crlf'}) {
   1498             subnewlines(1, \$_) for @upload;
   1499         }
   1500         if($hash{'nonewline'}) {
   1501             # Yes, we must cut off the final newline from the final line
   1502             # of the upload data
   1503             chomp($upload[-1]);
   1504         }
   1505 
   1506         $res = compare($runnerid, $testnum, $testname, "upload", \@out, \@upload);
   1507         if($res) {
   1508             return -1;
   1509         }
   1510         $ok .= "u";
   1511     }
   1512     else {
   1513         $ok .= "-"; # upload not checked
   1514     }
   1515 
   1516     # this is the valid protocol blurb curl should generate to a proxy
   1517     my @proxyprot = getpart("verify", "proxy");
   1518     if(@proxyprot) {
   1519         # Verify the sent proxy request
   1520         # check if there's any attributes on the verify/protocol section
   1521         my %hash = getpartattr("verify", "proxy");
   1522 
   1523         if($hash{'nonewline'}) {
   1524             # Yes, we must cut off the final newline from the final line
   1525             # of the protocol data
   1526             chomp($proxyprot[-1]);
   1527         }
   1528 
   1529         my @out = loadarray("$logdir/$PROXYIN");
   1530         for(@strip) {
   1531             # strip off all lines that match the patterns from both arrays
   1532             chomp $_;
   1533             @out = striparray( $_, \@out);
   1534             @proxyprot= striparray( $_, \@proxyprot);
   1535         }
   1536 
   1537         for my $strip (@strippart) {
   1538             chomp $strip;
   1539             for(@out) {
   1540                 eval $strip;
   1541             }
   1542         }
   1543 
   1544         if($hash{'crlf'}) {
   1545             subnewlines(0, \$_) for @proxyprot;
   1546         }
   1547 
   1548         $res = compare($runnerid, $testnum, $testname, "proxy", \@out, \@proxyprot);
   1549         if($res) {
   1550             return -1;
   1551         }
   1552 
   1553         $ok .= "P";
   1554 
   1555     }
   1556     else {
   1557         $ok .= "-"; # proxy not checked
   1558     }
   1559 
   1560     my $outputok;
   1561     for my $partsuffix (('', '1', '2', '3', '4')) {
   1562         my @outfile=getpart("verify", "file".$partsuffix);
   1563         if(@outfile || partexists("verify", "file".$partsuffix) ) {
   1564             # we're supposed to verify a dynamically generated file!
   1565             my %hash = getpartattr("verify", "file".$partsuffix);
   1566 
   1567             my $filename=$hash{'name'};
   1568             if(!$filename) {
   1569                 logmsg " $testnum: IGNORED: section verify=>file$partsuffix ".
   1570                        "has no name attribute\n";
   1571                 if(runnerac_stopservers($runnerid)) {
   1572                     logmsg "ERROR: runner $runnerid seems to have died\n";
   1573                 } else {
   1574 
   1575                     # TODO: this is a blocking call that will stall the controller,
   1576                     if($verbose) {
   1577                         logmsg "WARNING: blocking call in async function\n";
   1578                     }
   1579                     # but this error condition should never happen except during
   1580                     # development.
   1581                     my ($rid, $unexpected, $logs) = runnerar($runnerid);
   1582                     if(!$rid) {
   1583                         logmsg "ERROR: runner $runnerid seems to have died\n";
   1584                     } else {
   1585                         logmsg $logs;
   1586                     }
   1587                 }
   1588                 # timestamp test result verification end
   1589                 $timevrfyend{$testnum} = Time::HiRes::time();
   1590                 return -1;
   1591             }
   1592             my @generated=loadarray($filename);
   1593 
   1594             # what parts to cut off from the file
   1595             my @stripfilepar = getpart("verify", "stripfile".$partsuffix);
   1596 
   1597             my $filemode=$hash{'mode'};
   1598             if($filemode && ($filemode eq "text")) {
   1599                 normalize_text(\@outfile);
   1600                 normalize_text(\@generated);
   1601             }
   1602             if($hash{'crlf'}) {
   1603                 subnewlines(0, \$_) for @outfile;
   1604             }
   1605 
   1606             for my $strip (@stripfilepar) {
   1607                 chomp $strip;
   1608                 my @newgen;
   1609                 for(@generated) {
   1610                     eval $strip;
   1611                     if($_) {
   1612                         push @newgen, $_;
   1613                     }
   1614                 }
   1615                 # this is to get rid of array entries that vanished (zero
   1616                 # length) because of replacements
   1617                 @generated = @newgen;
   1618             }
   1619 
   1620             if($hash{'nonewline'}) {
   1621                 # cut off the final newline from the final line of the
   1622                 # output data
   1623                 chomp($outfile[-1]);
   1624             }
   1625 
   1626             $res = compare($runnerid, $testnum, $testname, "output ($filename)",
   1627                            \@generated, \@outfile);
   1628             if($res) {
   1629                 return -1;
   1630             }
   1631 
   1632             $outputok = 1; # output checked
   1633         }
   1634     }
   1635     $ok .= ($outputok) ? "o" : "-"; # output checked or not
   1636 
   1637     # verify SOCKS proxy details
   1638     my @socksprot = getpart("verify", "socks");
   1639     if(@socksprot) {
   1640         # Verify the sent SOCKS proxy details
   1641         my @out = loadarray("$logdir/$SOCKSIN");
   1642         $res = compare($runnerid, $testnum, $testname, "socks", \@out, \@socksprot);
   1643         if($res) {
   1644             return -1;
   1645         }
   1646     }
   1647 
   1648     # accept multiple comma-separated error codes
   1649     my @splerr = split(/ *, */, $errorcode);
   1650     my $errok;
   1651     foreach my $e (@splerr) {
   1652         if($e == $cmdres) {
   1653             # a fine error code
   1654             $errok = 1;
   1655             last;
   1656         }
   1657     }
   1658 
   1659     if($errok) {
   1660         $ok .= "e";
   1661     }
   1662     else {
   1663         if(!$short) {
   1664             logmsg sprintf("\n%s returned $cmdres, when expecting %s\n",
   1665                            (!$tool)?"curl":$tool, $errorcode);
   1666         }
   1667         logmsg " $testnum: exit FAILED\n";
   1668         # timestamp test result verification end
   1669         $timevrfyend{$testnum} = Time::HiRes::time();
   1670         return -1;
   1671     }
   1672 
   1673     if($feature{"TrackMemory"}) {
   1674         if(! -f "$logdir/$MEMDUMP") {
   1675             my %cmdhash = getpartattr("client", "command");
   1676             my $cmdtype = $cmdhash{'type'} || "default";
   1677             logmsg "\n** ALERT! memory tracking with no output file?\n"
   1678                 if($cmdtype ne "perl");
   1679             $ok .= "-"; # problem with memory checking
   1680         }
   1681         else {
   1682             my @memdata=`$memanalyze "$logdir/$MEMDUMP"`;
   1683             my $leak=0;
   1684             for(@memdata) {
   1685                 if($_ ne "") {
   1686                     # well it could be other memory problems as well, but
   1687                     # we call it leak for short here
   1688                     $leak=1;
   1689                 }
   1690             }
   1691             if($leak) {
   1692                 logmsg "\n** MEMORY FAILURE\n";
   1693                 logmsg @memdata;
   1694                 # timestamp test result verification end
   1695                 $timevrfyend{$testnum} = Time::HiRes::time();
   1696                 return -1;
   1697             }
   1698             else {
   1699                 $ok .= "m";
   1700             }
   1701             my @more=`$memanalyze -v "$logdir/$MEMDUMP"`;
   1702             my $allocs;
   1703             my $max = 0;
   1704             for(@more) {
   1705                 if(/^Allocations: (\d+)/) {
   1706                     $allocs = $1;
   1707                 }
   1708                 elsif(/^Maximum allocated: (\d+)/) {
   1709                     $max = $1;
   1710                 }
   1711             }
   1712             my @limits = getpart("verify", "limits");
   1713             my $lim_allocs = 1000; # high default values
   1714             my $lim_max = 1000000;
   1715             for(@limits) {
   1716                 if(/^Allocations: (\d+)/i) {
   1717                     $lim_allocs = $1;
   1718                 }
   1719                 elsif(/^Maximum allocated: (\d+)/i) {
   1720                     $lim_max = $1;
   1721                 }
   1722             }
   1723             logmsg "did $allocs allocations, $lim_allocs allowed\n"
   1724                 if($verbose);
   1725 
   1726             logmsg "allocated $max maximum, $lim_max allowed\n"
   1727                 if($verbose);
   1728 
   1729             if($allocs > $lim_allocs) {
   1730                 logmsg "\n** TOO MANY ALLOCS\n";
   1731                 logmsg "$lim_allocs allocations allowed, did $allocs\n";
   1732                 # timestamp test result verification end
   1733                 $timevrfyend{$testnum} = Time::HiRes::time();
   1734                 return -1;
   1735             }
   1736             if($max > $lim_max) {
   1737                 logmsg "\n** TOO MUCH TOTAL ALLOCATION\n";
   1738                 logmsg "$lim_max maximum allocation allowed, did $max\n";
   1739                 # timestamp test result verification end
   1740                 $timevrfyend{$testnum} = Time::HiRes::time();
   1741                 return -1;
   1742             }
   1743         }
   1744     }
   1745     else {
   1746         $ok .= "-"; # memory not checked
   1747     }
   1748 
   1749     my @notexists = getpart("verify", "notexists");
   1750     if(@notexists) {
   1751         # a list of directory entries that must not exist
   1752         my $err;
   1753         while(@notexists) {
   1754             my $fname = shift @notexists;
   1755             chomp $fname;
   1756             if(-e $fname) {
   1757                 logmsg "Found '$fname' when not supposed to exist.\n";
   1758                 $err++;
   1759             }
   1760             elsif($verbose) {
   1761                 logmsg "Found '$fname' confirmed to not exist.\n";
   1762             }
   1763         }
   1764         if($err) {
   1765             return -1;
   1766         }
   1767     }
   1768     if($valgrind) {
   1769         if($usedvalgrind) {
   1770             if(!opendir(DIR, "$logdir")) {
   1771                 logmsg "ERROR: unable to read $logdir\n";
   1772                 # timestamp test result verification end
   1773                 $timevrfyend{$testnum} = Time::HiRes::time();
   1774                 return -1;
   1775             }
   1776             my @files = readdir(DIR);
   1777             closedir(DIR);
   1778             my $vgfile;
   1779             foreach my $file (@files) {
   1780                 if($file =~ /^valgrind$testnum(\..*|)$/) {
   1781                     $vgfile = $file;
   1782                     last;
   1783                 }
   1784             }
   1785             if(!$vgfile) {
   1786                 logmsg "ERROR: valgrind log file missing for test $testnum\n";
   1787                 # timestamp test result verification end
   1788                 $timevrfyend{$testnum} = Time::HiRes::time();
   1789                 return -1;
   1790             }
   1791             my @e = valgrindparse("$logdir/$vgfile");
   1792             if(@e && $e[0]) {
   1793                 if($automakestyle) {
   1794                     logmsg "FAIL: $testnum - $testname - valgrind\n";
   1795                 }
   1796                 else {
   1797                     logmsg " valgrind ERROR ";
   1798                     logmsg @e;
   1799                 }
   1800                 # timestamp test result verification end
   1801                 $timevrfyend{$testnum} = Time::HiRes::time();
   1802                 return -1;
   1803             }
   1804             $ok .= "v";
   1805         }
   1806         else {
   1807             if($verbose) {
   1808                 logmsg " valgrind SKIPPED\n";
   1809             }
   1810             $ok .= "-"; # skipped
   1811         }
   1812     }
   1813     else {
   1814         $ok .= "-"; # valgrind not checked
   1815     }
   1816     # add 'E' for event-based
   1817     $ok .= $run_event_based ? "E" : "-";
   1818 
   1819     logmsg "$ok " if(!$short);
   1820 
   1821     # timestamp test result verification end
   1822     $timevrfyend{$testnum} = Time::HiRes::time();
   1823 
   1824     return 0;
   1825 }
   1826 
   1827 
   1828 #######################################################################
   1829 # Report a successful test
   1830 sub singletest_success {
   1831     my ($testnum, $count, $total, $errorreturncode)=@_;
   1832 
   1833     my $sofar= time()-$start;
   1834     my $esttotal = $sofar/$count * $total;
   1835     my $estleft = $esttotal - $sofar;
   1836     my $timeleft=sprintf("remaining: %02d:%02d",
   1837                      $estleft/60,
   1838                      $estleft%60);
   1839     my $took = $timevrfyend{$testnum} - $timeprepini{$testnum};
   1840     my $duration = sprintf("duration: %02d:%02d",
   1841                            $sofar/60, $sofar%60);
   1842     if(!$automakestyle) {
   1843         logmsg sprintf("OK (%-3d out of %-3d, %s, took %.3fs, %s)\n",
   1844                        $count, $total, $timeleft, $took, $duration);
   1845     }
   1846     else {
   1847         my $testname= (getpart("client", "name"))[0];
   1848         chomp $testname;
   1849         logmsg "PASS: $testnum - $testname\n";
   1850     }
   1851 
   1852     if($errorreturncode==2) {
   1853         # ignored test success
   1854         $passedign .= "$testnum ";
   1855         logmsg "Warning: test$testnum result is ignored, but passed!\n";
   1856     }
   1857 }
   1858 
   1859 #######################################################################
   1860 # Run a single specified test case
   1861 # This is structured as a state machine which changes state after an
   1862 # asynchronous call is made that awaits a response. The function returns with
   1863 # an error code and a flag that indicates if the state machine has completed,
   1864 # which means (if not) the function must be called again once the response has
   1865 # arrived.
   1866 #
   1867 sub singletest {
   1868     my ($runnerid, $testnum, $count, $total)=@_;
   1869 
   1870     # start buffering logmsg; stop it on return
   1871     logmsg_bufferfortest($runnerid);
   1872     if(!exists $singletest_state{$runnerid}) {
   1873         # First time in singletest() for this test
   1874         $singletest_state{$runnerid} = ST_INIT;
   1875     }
   1876 
   1877     if($singletest_state{$runnerid} == ST_INIT) {
   1878         my $logdir = getrunnerlogdir($runnerid);
   1879         # first, remove all lingering log & lock files
   1880         if(!cleardir($logdir)) {
   1881             logmsg "Warning: $runnerid: cleardir($logdir) failed\n";
   1882         }
   1883         if(!cleardir("$logdir/$LOCKDIR")) {
   1884             logmsg "Warning: $runnerid: cleardir($logdir/$LOCKDIR) failed\n";
   1885         }
   1886 
   1887         $singletest_state{$runnerid} = ST_INITED;
   1888         # Recursively call the state machine again because there is no
   1889         # event expected that would otherwise trigger a new call.
   1890         return singletest(@_);
   1891 
   1892     } elsif($singletest_state{$runnerid} == ST_INITED) {
   1893         ###################################################################
   1894         # Restore environment variables that were modified in a previous run.
   1895         # Test definition may instruct to (un)set environment vars.
   1896         # This is done this early so that leftover variables don't affect
   1897         # starting servers or CI registration.
   1898         # restore_test_env(1);
   1899 
   1900         ###################################################################
   1901         # Load test file so CI registration can get the right data before the
   1902         # runner is called
   1903         loadtest("${TESTDIR}/test${testnum}");
   1904 
   1905         ###################################################################
   1906         # Register the test case with the CI environment
   1907         citest_starttest($testnum);
   1908 
   1909         if(runnerac_test_preprocess($runnerid, $testnum)) {
   1910             logmsg "ERROR: runner $runnerid seems to have died\n";
   1911             $singletest_state{$runnerid} = ST_INIT;
   1912             return (-1, 0);
   1913         }
   1914         $singletest_state{$runnerid} = ST_PREPROCESS;
   1915 
   1916     } elsif($singletest_state{$runnerid} == ST_PREPROCESS) {
   1917         my ($rid, $why, $error, $logs, $testtimings) = runnerar($runnerid);
   1918         if(!$rid) {
   1919             logmsg "ERROR: runner $runnerid seems to have died\n";
   1920             $singletest_state{$runnerid} = ST_INIT;
   1921             return (-1, 0);
   1922         }
   1923         logmsg $logs;
   1924         updatetesttimings($testnum, %$testtimings);
   1925         if($error == -2) {
   1926             if($postmortem) {
   1927                 # Error indicates an actual problem starting the server, so
   1928                 # display the server logs
   1929                 displaylogs($rid, $testnum);
   1930             }
   1931         }
   1932 
   1933         #######################################################################
   1934         # Load test file for this test number
   1935         my $logdir = getrunnerlogdir($runnerid);
   1936         loadtest("${logdir}/test${testnum}");
   1937 
   1938         #######################################################################
   1939         # Print the test name and count tests
   1940         $error = singletest_count($testnum, $why);
   1941         if($error) {
   1942             # Submit the test case result with the CI environment
   1943             citest_finishtest($testnum, $error);
   1944             $singletest_state{$runnerid} = ST_INIT;
   1945             logmsg singletest_dumplogs();
   1946             return ($error, 0);
   1947         }
   1948 
   1949         #######################################################################
   1950         # Execute this test number
   1951         my $cmdres;
   1952         my $CURLOUT;
   1953         my $tool;
   1954         my $usedvalgrind;
   1955         if(runnerac_test_run($runnerid, $testnum)) {
   1956             logmsg "ERROR: runner $runnerid seems to have died\n";
   1957             $singletest_state{$runnerid} = ST_INIT;
   1958             return (-1, 0);
   1959         }
   1960         $singletest_state{$runnerid} = ST_RUN;
   1961 
   1962     } elsif($singletest_state{$runnerid} == ST_RUN) {
   1963         my ($rid, $error, $logs, $testtimings, $cmdres, $CURLOUT, $tool, $usedvalgrind) = runnerar($runnerid);
   1964         if(!$rid) {
   1965             logmsg "ERROR: runner $runnerid seems to have died\n";
   1966             $singletest_state{$runnerid} = ST_INIT;
   1967             return (-1, 0);
   1968         }
   1969         logmsg $logs;
   1970         updatetesttimings($testnum, %$testtimings);
   1971         if($error == -1) {
   1972             # no further verification will occur
   1973             $timevrfyend{$testnum} = Time::HiRes::time();
   1974             my $err = ignoreresultcode($testnum);
   1975             # Submit the test case result with the CI environment
   1976             citest_finishtest($testnum, $err);
   1977             $singletest_state{$runnerid} = ST_INIT;
   1978             logmsg singletest_dumplogs();
   1979             # return a test failure, either to be reported or to be ignored
   1980             return ($err, 0);
   1981         }
   1982         elsif($error == -2) {
   1983             # fill in the missing timings on error
   1984             timestampskippedevents($testnum);
   1985             # Submit the test case result with the CI environment
   1986             citest_finishtest($testnum, $error);
   1987             $singletest_state{$runnerid} = ST_INIT;
   1988             logmsg singletest_dumplogs();
   1989             return ($error, 0);
   1990         }
   1991         elsif($error > 0) {
   1992             # no further verification will occur
   1993             $timevrfyend{$testnum} = Time::HiRes::time();
   1994             # Submit the test case result with the CI environment
   1995             citest_finishtest($testnum, $error);
   1996             $singletest_state{$runnerid} = ST_INIT;
   1997             logmsg singletest_dumplogs();
   1998             return ($error, 0);
   1999         }
   2000 
   2001         #######################################################################
   2002         # Verify that the test succeeded
   2003         #
   2004         # Load test file for this test number
   2005         my $logdir = getrunnerlogdir($runnerid);
   2006         loadtest("${logdir}/test${testnum}");
   2007         readtestkeywords();
   2008 
   2009         $error = singletest_check($runnerid, $testnum, $cmdres, $CURLOUT, $tool, $usedvalgrind);
   2010         if($error == -1) {
   2011             my $err = ignoreresultcode($testnum);
   2012             # Submit the test case result with the CI environment
   2013             citest_finishtest($testnum, $err);
   2014             $singletest_state{$runnerid} = ST_INIT;
   2015             logmsg singletest_dumplogs();
   2016             # return a test failure, either to be reported or to be ignored
   2017             return ($err, 0);
   2018         }
   2019         elsif($error == -2) {
   2020             # torture test; there is no verification, so the run result holds the
   2021             # test success code
   2022             # Submit the test case result with the CI environment
   2023             citest_finishtest($testnum, $cmdres);
   2024             $singletest_state{$runnerid} = ST_INIT;
   2025             logmsg singletest_dumplogs();
   2026             return ($cmdres, 0);
   2027         }
   2028 
   2029 
   2030         #######################################################################
   2031         # Report a successful test
   2032         singletest_success($testnum, $count, $total, ignoreresultcode($testnum));
   2033 
   2034         # Submit the test case result with the CI environment
   2035         citest_finishtest($testnum, 0);
   2036         $singletest_state{$runnerid} = ST_INIT;
   2037 
   2038         logmsg singletest_dumplogs();
   2039         return (0, 0);  # state machine is finished
   2040     }
   2041     singletest_unbufferlogs();
   2042     return (0, 1);  # state machine must be called again on event
   2043 }
   2044 
   2045 #######################################################################
   2046 # runtimestats displays test-suite run time statistics
   2047 #
   2048 sub runtimestats {
   2049     my $lasttest = $_[0];
   2050 
   2051     return if(not $timestats);
   2052 
   2053     logmsg "::group::Run Time Stats\n";
   2054 
   2055     logmsg "\nTest suite total running time breakdown per task...\n\n";
   2056 
   2057     my @timesrvr;
   2058     my @timeprep;
   2059     my @timetool;
   2060     my @timelock;
   2061     my @timevrfy;
   2062     my @timetest;
   2063     my $timesrvrtot = 0.0;
   2064     my $timepreptot = 0.0;
   2065     my $timetooltot = 0.0;
   2066     my $timelocktot = 0.0;
   2067     my $timevrfytot = 0.0;
   2068     my $timetesttot = 0.0;
   2069     my $counter;
   2070 
   2071     for my $testnum (1 .. $lasttest) {
   2072         if($timesrvrini{$testnum}) {
   2073             $timesrvrtot += $timesrvrend{$testnum} - $timesrvrini{$testnum};
   2074             $timepreptot +=
   2075                 (($timetoolini{$testnum} - $timeprepini{$testnum}) -
   2076                  ($timesrvrend{$testnum} - $timesrvrini{$testnum}));
   2077             $timetooltot += $timetoolend{$testnum} - $timetoolini{$testnum};
   2078             $timelocktot += $timesrvrlog{$testnum} - $timetoolend{$testnum};
   2079             $timevrfytot += $timevrfyend{$testnum} - $timesrvrlog{$testnum};
   2080             $timetesttot += $timevrfyend{$testnum} - $timeprepini{$testnum};
   2081             push @timesrvr, sprintf("%06.3f  %04d",
   2082                 $timesrvrend{$testnum} - $timesrvrini{$testnum}, $testnum);
   2083             push @timeprep, sprintf("%06.3f  %04d",
   2084                 ($timetoolini{$testnum} - $timeprepini{$testnum}) -
   2085                 ($timesrvrend{$testnum} - $timesrvrini{$testnum}), $testnum);
   2086             push @timetool, sprintf("%06.3f  %04d",
   2087                 $timetoolend{$testnum} - $timetoolini{$testnum}, $testnum);
   2088             push @timelock, sprintf("%06.3f  %04d",
   2089                 $timesrvrlog{$testnum} - $timetoolend{$testnum}, $testnum);
   2090             push @timevrfy, sprintf("%06.3f  %04d",
   2091                 $timevrfyend{$testnum} - $timesrvrlog{$testnum}, $testnum);
   2092             push @timetest, sprintf("%06.3f  %04d",
   2093                 $timevrfyend{$testnum} - $timeprepini{$testnum}, $testnum);
   2094         }
   2095     }
   2096 
   2097     {
   2098         no warnings 'numeric';
   2099         @timesrvr = sort { $b <=> $a } @timesrvr;
   2100         @timeprep = sort { $b <=> $a } @timeprep;
   2101         @timetool = sort { $b <=> $a } @timetool;
   2102         @timelock = sort { $b <=> $a } @timelock;
   2103         @timevrfy = sort { $b <=> $a } @timevrfy;
   2104         @timetest = sort { $b <=> $a } @timetest;
   2105     }
   2106 
   2107     logmsg "Spent ". sprintf("%08.3f ", $timesrvrtot) .
   2108            "seconds starting and verifying test harness servers.\n";
   2109     logmsg "Spent ". sprintf("%08.3f ", $timepreptot) .
   2110            "seconds reading definitions and doing test preparations.\n";
   2111     logmsg "Spent ". sprintf("%08.3f ", $timetooltot) .
   2112            "seconds actually running test tools.\n";
   2113     logmsg "Spent ". sprintf("%08.3f ", $timelocktot) .
   2114            "seconds awaiting server logs lock removal.\n";
   2115     logmsg "Spent ". sprintf("%08.3f ", $timevrfytot) .
   2116            "seconds verifying test results.\n";
   2117     logmsg "Spent ". sprintf("%08.3f ", $timetesttot) .
   2118            "seconds doing all of the above.\n";
   2119 
   2120     $counter = 25;
   2121     logmsg "\nTest server starting and verification time per test ".
   2122         sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
   2123     logmsg "-time-  test\n";
   2124     logmsg "------  ----\n";
   2125     foreach my $txt (@timesrvr) {
   2126         last if((not $fullstats) && (not $counter--));
   2127         logmsg "$txt\n";
   2128     }
   2129 
   2130     $counter = 10;
   2131     logmsg "\nTest definition reading and preparation time per test ".
   2132         sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
   2133     logmsg "-time-  test\n";
   2134     logmsg "------  ----\n";
   2135     foreach my $txt (@timeprep) {
   2136         last if((not $fullstats) && (not $counter--));
   2137         logmsg "$txt\n";
   2138     }
   2139 
   2140     $counter = 25;
   2141     logmsg "\nTest tool execution time per test ".
   2142         sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
   2143     logmsg "-time-  test\n";
   2144     logmsg "------  ----\n";
   2145     foreach my $txt (@timetool) {
   2146         last if((not $fullstats) && (not $counter--));
   2147         logmsg "$txt\n";
   2148     }
   2149 
   2150     $counter = 15;
   2151     logmsg "\nTest server logs lock removal time per test ".
   2152         sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
   2153     logmsg "-time-  test\n";
   2154     logmsg "------  ----\n";
   2155     foreach my $txt (@timelock) {
   2156         last if((not $fullstats) && (not $counter--));
   2157         logmsg "$txt\n";
   2158     }
   2159 
   2160     $counter = 10;
   2161     logmsg "\nTest results verification time per test ".
   2162         sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
   2163     logmsg "-time-  test\n";
   2164     logmsg "------  ----\n";
   2165     foreach my $txt (@timevrfy) {
   2166         last if((not $fullstats) && (not $counter--));
   2167         logmsg "$txt\n";
   2168     }
   2169 
   2170     $counter = 50;
   2171     logmsg "\nTotal time per test ".
   2172         sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
   2173     logmsg "-time-  test\n";
   2174     logmsg "------  ----\n";
   2175     foreach my $txt (@timetest) {
   2176         last if((not $fullstats) && (not $counter--));
   2177         logmsg "$txt\n";
   2178     }
   2179 
   2180     logmsg "\n";
   2181 
   2182     logmsg "::endgroup::\n";
   2183 }
   2184 
   2185 #######################################################################
   2186 # returns code indicating why a test was skipped
   2187 # 0=unknown test, 1=use test result, 2=ignore test result
   2188 #
   2189 sub ignoreresultcode {
   2190     my ($testnum)=@_;
   2191     if(defined $ignoretestcodes{$testnum}) {
   2192         return $ignoretestcodes{$testnum};
   2193     }
   2194     return 0;
   2195 }
   2196 
   2197 #######################################################################
   2198 # Put the given runner ID onto the queue of runners ready for a new task
   2199 #
   2200 sub runnerready {
   2201     my ($runnerid)=@_;
   2202     push @runnersidle, $runnerid;
   2203 }
   2204 
   2205 #######################################################################
   2206 # Create test runners
   2207 #
   2208 sub createrunners {
   2209     my ($numrunners)=@_;
   2210     if(! $numrunners) {
   2211         $numrunners++;
   2212     }
   2213     # create $numrunners runners with minimum 1
   2214     for my $runnernum (1..$numrunners) {
   2215         my $dir = getrunnernumlogdir($runnernum);
   2216         cleardir($dir);
   2217         mkdir($dir, 0777);
   2218         $runnerids{$runnernum} = runner_init($dir, $jobs);
   2219         runnerready($runnerids{$runnernum});
   2220     }
   2221 }
   2222 
   2223 #######################################################################
   2224 # Pick a test runner for the given test
   2225 #
   2226 sub pickrunner {
   2227     my ($testnum)=@_;
   2228     scalar(@runnersidle) || die "No runners available";
   2229 
   2230     return pop @runnersidle;
   2231 }
   2232 
   2233 #######################################################################
   2234 # Check options to this test program
   2235 #
   2236 
   2237 # Special case for CMake: replace '$TFLAGS' by the contents of the
   2238 # environment variable (if any).
   2239 if(@ARGV && $ARGV[-1] eq '$TFLAGS') {
   2240     pop @ARGV;
   2241     push(@ARGV, split(' ', $ENV{'TFLAGS'})) if defined($ENV{'TFLAGS'});
   2242 }
   2243 
   2244 $args = join(' ', @ARGV);
   2245 
   2246 $valgrind = checktestcmd("valgrind");
   2247 my $number=0;
   2248 my $fromnum=-1;
   2249 my @testthis;
   2250 while(@ARGV) {
   2251     if($ARGV[0] eq "-v") {
   2252         # verbose output
   2253         $verbose=1;
   2254     }
   2255     elsif($ARGV[0] eq "-c") {
   2256         # use this path to curl instead of default
   2257         $DBGCURL=$CURL=$ARGV[1];
   2258         shift @ARGV;
   2259     }
   2260     elsif($ARGV[0] eq "-vc") {
   2261         # use this path to a curl used to verify servers
   2262 
   2263         # Particularly useful when you introduce a crashing bug somewhere in
   2264         # the development version as then it won't be able to run any tests
   2265         # since it can't verify the servers!
   2266 
   2267         $VCURL=shell_quote($ARGV[1]);
   2268         shift @ARGV;
   2269     }
   2270     elsif($ARGV[0] eq "-ac") {
   2271         # use this curl only to talk to APIs (currently only CI test APIs)
   2272         $ACURL=shell_quote($ARGV[1]);
   2273         shift @ARGV;
   2274     }
   2275     elsif($ARGV[0] eq "-d") {
   2276         # have the servers display protocol output
   2277         $debugprotocol=1;
   2278     }
   2279     elsif(($ARGV[0] eq "-e") || ($ARGV[0] eq "--test-event")) {
   2280         # run the tests cases event based if possible
   2281         $run_event_based=1;
   2282     }
   2283     elsif($ARGV[0] eq "--test-duphandle") {
   2284         # run the tests with --test-duphandle
   2285         $run_duphandle=1;
   2286     }
   2287     elsif($ARGV[0] eq "-f") {
   2288         # force - run the test case even if listed in DISABLED
   2289         $run_disabled=1;
   2290     }
   2291     elsif($ARGV[0] eq "-E") {
   2292         # load additional reasons to skip tests
   2293         shift @ARGV;
   2294         my $exclude_file = $ARGV[0];
   2295         open(my $fd, "<", $exclude_file) or die "Couldn't open '$exclude_file': $!";
   2296         while(my $line = <$fd>) {
   2297             next if($line =~ /^#/);
   2298             chomp $line;
   2299             my ($type, $patterns, $skip_reason) = split(/\s*:\s*/, $line, 3);
   2300 
   2301             die "Unsupported type: $type\n" if($type !~ /^keyword|test|tool$/);
   2302 
   2303             foreach my $pattern (split(/,/, $patterns)) {
   2304                 if($type eq "test") {
   2305                     # Strip leading zeros in the test number
   2306                     $pattern = int($pattern);
   2307                 }
   2308                 $custom_skip_reasons{$type}{$pattern} = $skip_reason;
   2309             }
   2310         }
   2311         close($fd);
   2312     }
   2313     elsif($ARGV[0] eq "-g") {
   2314         # run this test with gdb
   2315         $gdbthis=1;
   2316     }
   2317     elsif($ARGV[0] eq "-gl") {
   2318         # run this test with lldb
   2319         $gdbthis=2;
   2320     }
   2321     elsif($ARGV[0] eq "-gw") {
   2322         # run this test with windowed gdb
   2323         $gdbthis=1;
   2324         $gdbxwin=1;
   2325     }
   2326     elsif($ARGV[0] eq "-s") {
   2327         # short output
   2328         $short=1;
   2329     }
   2330     elsif($ARGV[0] eq "-am") {
   2331         # automake-style output
   2332         $short=1;
   2333         $automakestyle=1;
   2334     }
   2335     elsif($ARGV[0] eq "-n") {
   2336         # no valgrind
   2337         undef $valgrind;
   2338     }
   2339     elsif($ARGV[0] eq "--no-debuginfod") {
   2340         # disable the valgrind debuginfod functionality
   2341         $no_debuginfod = 1;
   2342     }
   2343     elsif($ARGV[0] eq "-R") {
   2344         # execute in scrambled order
   2345         $scrambleorder=1;
   2346     }
   2347     elsif($ARGV[0] =~ /^-t(.*)/) {
   2348         # torture
   2349         $torture=1;
   2350         my $xtra = $1;
   2351 
   2352         if($xtra =~ s/(\d+)$//) {
   2353             $tortalloc = $1;
   2354         }
   2355     }
   2356     elsif($ARGV[0] =~ /--shallow=(\d+)/) {
   2357         # Fail no more than this amount per tests when running
   2358         # torture.
   2359         my ($num)=($1);
   2360         $shallow=$num;
   2361     }
   2362     elsif($ARGV[0] =~ /--repeat=(\d+)/) {
   2363         # Repeat-run the given tests this many times
   2364         $repeat = $1;
   2365     }
   2366     elsif($ARGV[0] =~ /--retry=(\d+)/) {
   2367         # Number of attempts for the whole test run to retry failed tests
   2368         $retry = $1;
   2369     }
   2370     elsif($ARGV[0] =~ /--seed=(\d+)/) {
   2371         # Set a fixed random seed (used for -R and --shallow)
   2372         $randseed = $1;
   2373     }
   2374     elsif($ARGV[0] eq "-a") {
   2375         # continue anyway, even if a test fail
   2376         $anyway=1;
   2377     }
   2378     elsif($ARGV[0] eq "-o") {
   2379         shift @ARGV;
   2380         if($ARGV[0] =~ /^(\w+)=([\w.:\/\[\]-]+)$/) {
   2381             my ($variable, $value) = ($1, $2);
   2382             eval "\$$variable='$value'" or die "Failed to set \$$variable to $value: $@";
   2383         } else {
   2384             die "Failed to parse '-o $ARGV[0]'. May contain unexpected characters.\n";
   2385         }
   2386     }
   2387     elsif($ARGV[0] eq "-p") {
   2388         $postmortem=1;
   2389     }
   2390     elsif($ARGV[0] eq "-P") {
   2391         shift @ARGV;
   2392         $proxy_address=$ARGV[0];
   2393     }
   2394     elsif($ARGV[0] eq "-L") {
   2395         # require additional library file
   2396         shift @ARGV;
   2397         require $ARGV[0];
   2398     }
   2399     elsif($ARGV[0] eq "-l") {
   2400         # lists the test case names only
   2401         $listonly=1;
   2402     }
   2403     elsif($ARGV[0] =~ /^-j(.*)/) {
   2404         # parallel jobs
   2405         $jobs=1;
   2406         my $xtra = $1;
   2407         if($xtra =~ s/(\d+)$//) {
   2408             $jobs = $1;
   2409         }
   2410     }
   2411     elsif($ARGV[0] eq "-k") {
   2412         # keep stdout and stderr files after tests
   2413         $keepoutfiles=1;
   2414     }
   2415     elsif($ARGV[0] eq "-r") {
   2416         # run time statistics needs Time::HiRes
   2417         if($Time::HiRes::VERSION) {
   2418             # presize hashes appropriately to hold an entire test run
   2419             keys(%timeprepini) = 2000;
   2420             keys(%timesrvrini) = 2000;
   2421             keys(%timesrvrend) = 2000;
   2422             keys(%timetoolini) = 2000;
   2423             keys(%timetoolend) = 2000;
   2424             keys(%timesrvrlog) = 2000;
   2425             keys(%timevrfyend) = 2000;
   2426             $timestats=1;
   2427             $fullstats=0;
   2428         }
   2429     }
   2430     elsif($ARGV[0] eq "-rf") {
   2431         # run time statistics needs Time::HiRes
   2432         if($Time::HiRes::VERSION) {
   2433             # presize hashes appropriately to hold an entire test run
   2434             keys(%timeprepini) = 2000;
   2435             keys(%timesrvrini) = 2000;
   2436             keys(%timesrvrend) = 2000;
   2437             keys(%timetoolini) = 2000;
   2438             keys(%timetoolend) = 2000;
   2439             keys(%timesrvrlog) = 2000;
   2440             keys(%timevrfyend) = 2000;
   2441             $timestats=1;
   2442             $fullstats=1;
   2443         }
   2444     }
   2445     elsif($ARGV[0] eq "-u") {
   2446         # error instead of warning on server unexpectedly alive
   2447         $err_unexpected=1;
   2448     }
   2449     elsif(($ARGV[0] eq "-h") || ($ARGV[0] eq "--help")) {
   2450         # show help text
   2451         print <<"EOHELP"
   2452 Usage: runtests.pl [options] [test selection(s)]
   2453   -a       continue even if a test fails
   2454   -ac path use this curl only to talk to APIs (currently only CI test APIs)
   2455   -am      automake style output PASS/FAIL: [number] [name]
   2456   -c path  use this curl executable
   2457   -d       display server debug info
   2458   -e, --test-event  event-based execution
   2459   --test-duphandle  duplicate handles before use
   2460   -E file  load the specified file to exclude certain tests
   2461   -f       forcibly run even if disabled
   2462   -g       run the test case with gdb
   2463   -gw      run the test case with gdb as a windowed application
   2464   -h       this help text
   2465   -j[N]    spawn this number of processes to run tests (default 0)
   2466   -k       keep stdout and stderr files present after tests
   2467   -L path  require an additional perl library file to replace certain functions
   2468   -l       list all test case names/descriptions
   2469   -n       no valgrind
   2470   --no-debuginfod disable the valgrind debuginfod functionality
   2471   -o variable=value set internal variable to the specified value
   2472   -P proxy use the specified proxy
   2473   -p       print log file contents when a test fails
   2474   -R       scrambled order (uses the random seed, see --seed)
   2475   -r       run time statistics
   2476   -rf      full run time statistics
   2477   --repeat=[num] run the given tests this many times
   2478   --retry=[num] number of attempts for the whole test run to retry failed tests
   2479   -s       short output
   2480   --seed=[num] set the random seed to a fixed number
   2481   --shallow=[num] randomly makes the torture tests "thinner"
   2482   -t[N]    torture (simulate function failures); N means fail Nth function
   2483   -u       error instead of warning on server unexpectedly alive
   2484   -v       verbose output
   2485   -vc path use this curl only to verify the existing servers
   2486   [num]    like "5 6 9" or " 5 to 22 " to run those tests only
   2487   [!num]   like "!5 !6 !9" to disable those tests
   2488   [~num]   like "~5 ~6 ~9" to ignore the result of those tests
   2489   [keyword] like "IPv6" to select only tests containing the key word
   2490   [!keyword] like "!cookies" to disable any tests containing the key word
   2491   [~keyword] like "~cookies" to ignore results of tests containing key word
   2492 EOHELP
   2493     ;
   2494         exit;
   2495     }
   2496     elsif($ARGV[0] =~ /^(\d+)/) {
   2497         $number = $1;
   2498         if($fromnum >= 0) {
   2499             for my $n ($fromnum .. $number) {
   2500                 push @testthis, $n;
   2501             }
   2502             $fromnum = -1;
   2503         }
   2504         else {
   2505             push @testthis, $1;
   2506         }
   2507     }
   2508     elsif($ARGV[0] =~ /^to$/i) {
   2509         $fromnum = $number+1;
   2510     }
   2511     elsif($ARGV[0] =~ /^!(\d+)/) {
   2512         $fromnum = -1;
   2513         $disabled{$1}=$1;
   2514     }
   2515     elsif($ARGV[0] =~ /^~(\d+)/) {
   2516         $fromnum = -1;
   2517         $ignored{$1}=$1;
   2518     }
   2519     elsif($ARGV[0] =~ /^!(.+)/) {
   2520         $disabled_keywords{lc($1)}=$1;
   2521     }
   2522     elsif($ARGV[0] =~ /^~(.+)/) {
   2523         $ignored_keywords{lc($1)}=$1;
   2524     }
   2525     elsif($ARGV[0] =~ /^([-[{a-zA-Z].*)/) {
   2526         $enabled_keywords{lc($1)}=$1;
   2527     }
   2528     else {
   2529         print "Unknown option: $ARGV[0]\n";
   2530         exit;
   2531     }
   2532     shift @ARGV;
   2533 }
   2534 
   2535 delete $ENV{'DEBUGINFOD_URLS'} if($ENV{'DEBUGINFOD_URLS'} && $no_debuginfod);
   2536 
   2537 if(!$randseed) {
   2538     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
   2539         localtime(time);
   2540     # seed of the month. December 2019 becomes 201912
   2541     $randseed = ($year+1900)*100 + $mon+1;
   2542     print "Using curl: $CURL\n";
   2543     open(my $curlvh, "-|", exerunner() . shell_quote($CURL) . " --version 2>$dev_null") ||
   2544         die "could not get curl version!";
   2545     my @c = <$curlvh>;
   2546     close($curlvh) || die "could not get curl version!";
   2547     # use the first line of output and get the md5 out of it
   2548     my $str = md5($c[0]);
   2549     $randseed += unpack('S', $str);  # unsigned 16 bit value
   2550 }
   2551 srand $randseed;
   2552 
   2553 if(@testthis && ($testthis[0] ne "")) {
   2554     $TESTCASES=join(" ", @testthis);
   2555 }
   2556 
   2557 if($valgrind) {
   2558     # we have found valgrind on the host, use it
   2559 
   2560     # verify that we can invoke it fine
   2561     my $code = runclient("valgrind >$dev_null 2>&1");
   2562 
   2563     if(($code>>8) != 1) {
   2564         #logmsg "Valgrind failure, disable it\n";
   2565         undef $valgrind;
   2566     } else {
   2567 
   2568         # since valgrind 2.1.x, '--tool' option is mandatory
   2569         # use it, if it is supported by the version installed on the system
   2570         # (this happened in 2003, so we could probably don't need to care about
   2571         # that old version any longer and just delete this check)
   2572         runclient("valgrind --help 2>&1 | grep -- --tool >$dev_null 2>&1");
   2573         if(($? >> 8)) {
   2574             $valgrind_tool="";
   2575         }
   2576         open(my $curlh, "<", "$CURL");
   2577         my $l = <$curlh>;
   2578         if($l =~ /^\#\!/) {
   2579             # A shell script. This is typically when built with libtool,
   2580             $valgrind="../libtool --mode=execute $valgrind";
   2581         }
   2582         close($curlh);
   2583 
   2584         # valgrind 3 renamed the --logfile option to --log-file!!!
   2585         # (this happened in 2005, so we could probably don't need to care about
   2586         # that old version any longer and just delete this check)
   2587         my $ver=join(' ', runclientoutput("valgrind --version"));
   2588         # cut off all but digits and dots
   2589         $ver =~ s/[^0-9.]//g;
   2590 
   2591         if($ver =~ /^(\d+)/) {
   2592             $ver = $1;
   2593             if($ver < 3) {
   2594                 $valgrind_logfile="--logfile";
   2595             }
   2596         }
   2597     }
   2598 }
   2599 
   2600 if($gdbthis) {
   2601     # open the executable curl and read the first 4 bytes of it
   2602     open(my $check, "<", "$CURL");
   2603     my $c;
   2604     sysread $check, $c, 4;
   2605     close($check);
   2606     if($c eq "#! /") {
   2607         # A shell script. This is typically when built with libtool,
   2608         $libtool = 1;
   2609         $gdb = "../libtool --mode=execute gdb";
   2610     }
   2611 }
   2612 
   2613 #######################################################################
   2614 # clear and create logging directory:
   2615 #
   2616 
   2617 # TODO: figure how to get around this. This dir is needed for checksystemfeatures()
   2618 # Maybe create & use & delete a temporary directory in that function
   2619 cleardir($LOGDIR);
   2620 mkdir($LOGDIR, 0777);
   2621 mkdir("$LOGDIR/$LOCKDIR", 0777);
   2622 
   2623 #######################################################################
   2624 # initialize some variables
   2625 #
   2626 
   2627 get_disttests();
   2628 if(!$jobs) {
   2629     # Disable buffered logging with only one test job
   2630     setlogfunc(\&logmsg);
   2631 }
   2632 
   2633 #######################################################################
   2634 # Output curl version and host info being tested
   2635 #
   2636 
   2637 if(!$listonly) {
   2638     checksystemfeatures();
   2639 }
   2640 
   2641 #######################################################################
   2642 # Output information about the curl build
   2643 #
   2644 if(!$listonly) {
   2645     if(open(my $fd, "<", "../buildinfo.txt")) {
   2646         while(my $line = <$fd>) {
   2647             chomp $line;
   2648             if($line && $line !~ /^#/) {
   2649                 logmsg("* $line\n");
   2650             }
   2651         }
   2652         close($fd);
   2653     }
   2654 }
   2655 
   2656 #######################################################################
   2657 # initialize configuration needed to set up servers
   2658 # TODO: rearrange things so this can be called only in runner_init()
   2659 #
   2660 initserverconfig();
   2661 
   2662 if(!$listonly) {
   2663     # these can only be displayed after initserverconfig() has been called
   2664     displayserverfeatures();
   2665 
   2666     # globally disabled tests
   2667     disabledtests("$TESTDIR/DISABLED");
   2668 }
   2669 
   2670 #######################################################################
   2671 # Fetch all disabled tests, if there are any
   2672 #
   2673 
   2674 sub disabledtests {
   2675     my ($file) = @_;
   2676     my @input;
   2677 
   2678     if(open(my $disabledh, "<", "$file")) {
   2679         while(<$disabledh>) {
   2680             if(/^ *\#/) {
   2681                 # allow comments
   2682                 next;
   2683             }
   2684             push @input, $_;
   2685         }
   2686         close($disabledh);
   2687 
   2688         # preprocess the input to make conditionally disabled tests depending
   2689         # on variables
   2690         my @pp = prepro(0, @input);
   2691         for my $t (@pp) {
   2692             if($t =~ /(\d+)/) {
   2693                 my ($n) = $1;
   2694                 $disabled{$n}=$n; # disable this test number
   2695                 if(! -f "$srcdir/data/test$n") {
   2696                     print STDERR "WARNING! Non-existing test $n in $file!\n";
   2697                     # fail hard to make user notice
   2698                     exit 1;
   2699                 }
   2700                 logmsg "DISABLED: test $n\n" if($verbose);
   2701             }
   2702             else {
   2703                 print STDERR "$file: rubbish content: $t\n";
   2704                 exit 2;
   2705             }
   2706         }
   2707     }
   2708     else {
   2709         print STDERR "Cannot open $file, exiting\n";
   2710         exit 3;
   2711     }
   2712 }
   2713 
   2714 #######################################################################
   2715 # If 'all' tests are requested, find out all test numbers
   2716 #
   2717 
   2718 if($TESTCASES eq "all") {
   2719     # Get all commands and find out their test numbers
   2720     opendir(DIR, $TESTDIR) || die "can't opendir $TESTDIR: $!";
   2721     my @cmds = grep { /^test([0-9]+)$/ && -f "$TESTDIR/$_" } readdir(DIR);
   2722     closedir(DIR);
   2723 
   2724     $TESTCASES=""; # start with no test cases
   2725 
   2726     # cut off everything but the digits
   2727     for(@cmds) {
   2728         $_ =~ s/[a-z\/\.]*//g;
   2729     }
   2730     # sort the numbers from low to high
   2731     foreach my $n (sort { $a <=> $b } @cmds) {
   2732         if($disabled{$n}) {
   2733             # skip disabled test cases
   2734             my $why = "configured as DISABLED";
   2735             $skipped{$why}++;
   2736             $teststat[$n]=$why; # store reason for this test case
   2737             next;
   2738         }
   2739         $TESTCASES .= " $n";
   2740     }
   2741 }
   2742 else {
   2743     my $verified="";
   2744     for(split(" ", $TESTCASES)) {
   2745         if(-e "$TESTDIR/test$_") {
   2746             $verified.="$_ ";
   2747         }
   2748     }
   2749     if($verified eq "") {
   2750         print "No existing test cases were specified\n";
   2751         exit;
   2752     }
   2753     $TESTCASES = $verified;
   2754 }
   2755 if($repeat) {
   2756     my $s;
   2757     for(1 .. $repeat) {
   2758         $s .= $TESTCASES;
   2759     }
   2760     $TESTCASES = $s;
   2761 }
   2762 
   2763 if($scrambleorder) {
   2764     # scramble the order of the test cases
   2765     my @rand;
   2766     while($TESTCASES) {
   2767         my @all = split(/ +/, $TESTCASES);
   2768         if(!$all[0]) {
   2769             # if the first is blank, shift away it
   2770             shift @all;
   2771         }
   2772         my $r = rand @all;
   2773         push @rand, $all[$r];
   2774         $all[$r]="";
   2775         $TESTCASES = join(" ", @all);
   2776     }
   2777     $TESTCASES = join(" ", @rand);
   2778 }
   2779 
   2780 # Display the contents of the given file.  Line endings are canonicalized
   2781 # and excessively long files are elided
   2782 sub displaylogcontent {
   2783     my ($file)=@_;
   2784     if(open(my $single, "<", "$file")) {
   2785         my $linecount = 0;
   2786         my $truncate;
   2787         my @tail;
   2788         while(my $string = <$single>) {
   2789             $string =~ s/\r\n/\n/g;
   2790             $string =~ s/[\r\f\032]/\n/g;
   2791             $string .= "\n" unless ($string =~ /\n$/);
   2792             $string =~ tr/\n//;
   2793             for my $line (split(m/\n/, $string)) {
   2794                 $line =~ s/\s*\!$//;
   2795                 if($truncate) {
   2796                     push @tail, " $line\n";
   2797                 } else {
   2798                     logmsg " $line\n";
   2799                 }
   2800                 $linecount++;
   2801                 $truncate = $linecount > 1200;
   2802             }
   2803         }
   2804         close($single);
   2805         if(@tail) {
   2806             my $tailshow = 200;
   2807             my $tailskip = 0;
   2808             my $tailtotal = scalar @tail;
   2809             if($tailtotal > $tailshow) {
   2810                 $tailskip = $tailtotal - $tailshow;
   2811                 logmsg "=== File too long: $tailskip lines omitted here\n";
   2812             }
   2813             for($tailskip .. $tailtotal-1) {
   2814                 logmsg "$tail[$_]";
   2815             }
   2816         }
   2817     }
   2818 }
   2819 
   2820 sub displaylogs {
   2821     my ($runnerid, $testnum)=@_;
   2822     my $logdir = getrunnerlogdir($runnerid);
   2823     opendir(DIR, "$logdir") ||
   2824         die "can't open dir: $!";
   2825     my @logs = readdir(DIR);
   2826     closedir(DIR);
   2827 
   2828     logmsg "== Contents of files in the $logdir/ dir after test $testnum\n";
   2829     foreach my $log (sort @logs) {
   2830         if($log =~ /\.(\.|)$/) {
   2831             next; # skip "." and ".."
   2832         }
   2833         if($log =~ /^\.nfs/) {
   2834             next; # skip ".nfs"
   2835         }
   2836         if(($log eq "memdump") || ($log eq "core")) {
   2837             next; # skip "memdump" and  "core"
   2838         }
   2839         if((-d "$logdir/$log") || (! -s "$logdir/$log")) {
   2840             next; # skip directory and empty files
   2841         }
   2842         if(($log =~ /^stdout\d+/) && ($log !~ /^stdout$testnum/)) {
   2843             next; # skip stdoutNnn of other tests
   2844         }
   2845         if(($log =~ /^stderr\d+/) && ($log !~ /^stderr$testnum/)) {
   2846             next; # skip stderrNnn of other tests
   2847         }
   2848         if(($log =~ /^upload\d+/) && ($log !~ /^upload$testnum/)) {
   2849             next; # skip uploadNnn of other tests
   2850         }
   2851         if(($log =~ /^curl\d+\.out/) && ($log !~ /^curl$testnum\.out/)) {
   2852             next; # skip curlNnn.out of other tests
   2853         }
   2854         if(($log =~ /^test\d+\.txt/) && ($log !~ /^test$testnum\.txt/)) {
   2855             next; # skip testNnn.txt of other tests
   2856         }
   2857         if(($log =~ /^file\d+\.txt/) && ($log !~ /^file$testnum\.txt/)) {
   2858             next; # skip fileNnn.txt of other tests
   2859         }
   2860         if(($log =~ /^netrc\d+/) && ($log !~ /^netrc$testnum/)) {
   2861             next; # skip netrcNnn of other tests
   2862         }
   2863         if(($log =~ /^trace\d+/) && ($log !~ /^trace$testnum/)) {
   2864             next; # skip traceNnn of other tests
   2865         }
   2866         if(($log =~ /^valgrind\d+/) && ($log !~ /^valgrind$testnum(?:\..*)?$/)) {
   2867             next; # skip valgrindNnn of other tests
   2868         }
   2869         if(($log =~ /^test$testnum$/)) {
   2870             next; # skip test$testnum since it can be very big
   2871         }
   2872         logmsg "=== Start of file $log\n";
   2873         displaylogcontent("$logdir/$log");
   2874         logmsg "=== End of file $log\n";
   2875     }
   2876 }
   2877 
   2878 #######################################################################
   2879 # Scan tests to find suitable candidates
   2880 #
   2881 
   2882 my $failed;
   2883 my $failedign;
   2884 my $failedre;
   2885 my $ok=0;
   2886 my $ign=0;
   2887 my $total=0;
   2888 my $executed=0;
   2889 my $retry_done=0;
   2890 my $lasttest=0;
   2891 my @at = split(" ", $TESTCASES);
   2892 my $count=0;
   2893 my $endwaitcnt=0;
   2894 
   2895 $start = time();
   2896 
   2897 # scan all tests to find ones we should try to run
   2898 my @runtests;
   2899 foreach my $testnum (@at) {
   2900     $lasttest = $testnum if($testnum > $lasttest);
   2901     my ($why, $errorreturncode) = singletest_shouldrun($testnum);
   2902     if($why || $listonly) {
   2903         # Display test name now--test will be completely skipped later
   2904         my $error = singletest_count($testnum, $why);
   2905         next;
   2906     }
   2907     $ignoretestcodes{$testnum} = $errorreturncode;
   2908     push(@runtests, $testnum);
   2909 }
   2910 my $totaltests = scalar(@runtests);
   2911 
   2912 if($listonly) {
   2913     exit(0);
   2914 }
   2915 
   2916 #######################################################################
   2917 # Setup CI Test Run
   2918 citest_starttestrun();
   2919 
   2920 #######################################################################
   2921 # Start test runners
   2922 #
   2923 my $numrunners = $jobs < scalar(@runtests) ? $jobs : scalar(@runtests);
   2924 createrunners($numrunners);
   2925 
   2926 #######################################################################
   2927 # The main test-loop
   2928 #
   2929 # Every iteration through the loop consists of these steps:
   2930 #   - if the global abort flag is set, exit the loop; we are done
   2931 #   - if a runner is idle, start a new test on it
   2932 #   - if all runners are idle, exit the loop; we are done
   2933 #   - if a runner has a response for us, process the response
   2934 
   2935 # run through each candidate test and execute it
   2936 my $runner_wait_cnt = 0;
   2937 
   2938 # number of retry attempts for the whole test run
   2939 my $retry_left;
   2940 if($torture) {
   2941     $retry_left = 0;  # No use of retrying torture tests
   2942 }
   2943 else {
   2944     $retry_left = $retry;
   2945 }
   2946 
   2947 while() {
   2948     # check the abort flag
   2949     if($globalabort) {
   2950         logmsg singletest_dumplogs();
   2951         logmsg "Aborting tests\n";
   2952         logmsg "Waiting for " . scalar((keys %runnersrunning)) . " outstanding test(s) to finish...\n";
   2953         # Wait for the last requests to complete and throw them away so
   2954         # that IPC calls & responses stay in sync
   2955         # TODO: send a signal to the runners to interrupt a long test
   2956         foreach my $rid (keys %runnersrunning) {
   2957             runnerar($rid);
   2958             delete $runnersrunning{$rid};
   2959             logmsg ".";
   2960             $| = 1;
   2961         }
   2962         logmsg "\n";
   2963         last;
   2964     }
   2965 
   2966     # Start a new test if possible
   2967     if(scalar(@runnersidle) && scalar(@runtests)) {
   2968         # A runner is ready to run a test, and tests are still available to run
   2969         # so start a new test.
   2970         $count++;
   2971         my $testnum = shift(@runtests);
   2972 
   2973         # pick a runner for this new test
   2974         my $runnerid = pickrunner($testnum);
   2975         $countforrunner{$runnerid} = $count;
   2976 
   2977         # Start the test
   2978         my ($error, $again) = singletest($runnerid, $testnum, $countforrunner{$runnerid}, $totaltests);
   2979         if($again) {
   2980             # this runner is busy running a test
   2981             $runnersrunning{$runnerid} = $testnum;
   2982         } else {
   2983             runnerready($runnerid);
   2984             if($error >= 0) {
   2985                 # We make this simplifying assumption to avoid having to handle
   2986                 # $error properly here, but we must handle the case of runner
   2987                 # death without abending here.
   2988                 die "Internal error: test must not complete on first call";
   2989             }
   2990         }
   2991     }
   2992 
   2993     # See if we've completed all the tests
   2994     if(!scalar(%runnersrunning)) {
   2995         # No runners are running; we must be done
   2996         scalar(@runtests) && die 'Internal error: still have tests to run';
   2997         last;
   2998     }
   2999 
   3000     # See if a test runner needs attention
   3001     # If we could be running more tests, don't wait so we can schedule a new
   3002     # one immediately. If all runners are busy, wait a fraction of a second
   3003     # for one to finish so we can still loop around to check the abort flag.
   3004     my $runnerwait = scalar(@runnersidle) && scalar(@runtests) ? 0 : 1.0;
   3005     my (@ridsready, $riderror) = runnerar_ready($runnerwait);
   3006     if(@ridsready) {
   3007         for my $ridready (@ridsready) {
   3008             if($ridready && ! defined $runnersrunning{$ridready}) {
   3009                 # On Linux, a closed pipe still shows up as ready instead of error.
   3010                 # Detect this here by seeing if we are expecting it to be ready and
   3011                 # treat it as an error if not.
   3012                 logmsg "ERROR: Runner $ridready is unexpectedly ready; is probably actually dead\n";
   3013                 $riderror = $ridready;
   3014                 undef $ridready;
   3015             }
   3016             if($ridready) {
   3017                 # This runner is ready to be serviced
   3018                 my $testnum = $runnersrunning{$ridready};
   3019                 defined $testnum ||  die "Internal error: test for runner $ridready unknown";
   3020                 delete $runnersrunning{$ridready};
   3021                 my ($error, $again) = singletest($ridready, $testnum, $countforrunner{$ridready}, $totaltests);
   3022                 if($again) {
   3023                     # this runner is busy running a test
   3024                     $runnersrunning{$ridready} = $testnum;
   3025                 } else {
   3026                     # Test is complete
   3027                     $runner_wait_cnt = 0;
   3028                     runnerready($ridready);
   3029 
   3030                     if($error < 0) {
   3031                         # not a test we can run
   3032                         next;
   3033                     }
   3034 
   3035                     $total++; # number of tests we've run
   3036                     $executed++;
   3037 
   3038                     if($error>0) {
   3039                         if($error==2) {
   3040                             # ignored test failures
   3041                             $failedign .= "$testnum ";
   3042                         }
   3043                         else {
   3044                             # make another attempt to counteract flaky failures
   3045                             if($retry_left > 0) {
   3046                                 $retry_left--;
   3047                                 $retry_done++;
   3048                                 $total--;
   3049                                 push(@runtests, $testnum);
   3050                                 $failedre .= "$testnum ";
   3051                             }
   3052                             else {
   3053                                 $failed.= "$testnum ";
   3054                             }
   3055                         }
   3056                         if($postmortem) {
   3057                             # display all files in $LOGDIR/ in a nice way
   3058                             displaylogs($ridready, $testnum);
   3059                         }
   3060                         if($error==2) {
   3061                             $ign++; # ignored test result counter
   3062                         }
   3063                         elsif(!$anyway) {
   3064                             # a test failed, abort
   3065                             logmsg "\n - abort tests\n";
   3066                             undef @runtests;  # empty out the remaining tests
   3067                         }
   3068                     }
   3069                     elsif(!$error) {
   3070                         $ok++; # successful test counter
   3071                     }
   3072                 }
   3073             }
   3074         }
   3075     }
   3076     if(!@ridsready && $runnerwait && !$torture && scalar(%runnersrunning)) {
   3077         $runner_wait_cnt++;
   3078         if($runner_wait_cnt >= 5) {
   3079             my $msg = "waiting for " . scalar(%runnersrunning) . " results:";
   3080             my $sep = " ";
   3081             foreach my $rid (keys %runnersrunning) {
   3082                 $msg .= $sep . $runnersrunning{$rid} . "[$rid]";
   3083                 $sep = ", "
   3084             }
   3085             logmsg "$msg\n";
   3086         }
   3087         if($runner_wait_cnt >= 10) {
   3088             $runner_wait_cnt = 0;
   3089             foreach my $rid (keys %runnersrunning) {
   3090                 my $testnum = $runnersrunning{$rid};
   3091                 logmsg "current state of test $testnum in [$rid]:\n";
   3092                 displaylogs($rid, $testnum);
   3093             }
   3094         }
   3095     }
   3096     if($riderror) {
   3097         logmsg "ERROR: runner $riderror is dead! aborting test run\n";
   3098         delete $runnersrunning{$riderror} if(defined $runnersrunning{$riderror});
   3099         $globalabort = 1;
   3100     }
   3101     if(!scalar(@runtests) && ++$endwaitcnt == (240 + $jobs)) {
   3102         # Once all tests have been scheduled on a runner at the end of a test
   3103         # run, we just wait for their results to come in. If we're still
   3104         # waiting after a couple of minutes ($endwaitcnt multiplied by
   3105         # $runnerwait, plus $jobs because that number won't time out), display
   3106         # the same test runner status as we give with a SIGUSR1. This will
   3107         # likely point to a single test that has hung.
   3108         logmsg "Hmmm, the tests are taking a while to finish. Here is the status:\n";
   3109         catch_usr1();
   3110     }
   3111 }
   3112 
   3113 my $sofar = time() - $start;
   3114 
   3115 #######################################################################
   3116 # Finish CI Test Run
   3117 citest_finishtestrun();
   3118 
   3119 # Tests done, stop the servers
   3120 foreach my $runnerid (values %runnerids) {
   3121     runnerac_stopservers($runnerid);
   3122 }
   3123 
   3124 # Wait for servers to stop
   3125 my $unexpected;
   3126 foreach my $runnerid (values %runnerids) {
   3127     my ($rid, $unexpect, $logs) = runnerar($runnerid);
   3128     $unexpected ||= $unexpect;
   3129     logmsg $logs;
   3130 }
   3131 
   3132 # Kill the runners
   3133 # There is a race condition here since we don't know exactly when the runners
   3134 # have each finished shutting themselves down, but we're about to exit so it
   3135 # doesn't make much difference.
   3136 foreach my $runnerid (values %runnerids) {
   3137     runnerac_shutdown($runnerid);
   3138     sleep 0;  # give runner a context switch so it can shut itself down
   3139 }
   3140 
   3141 my $numskipped = %skipped ? sum values %skipped : 0;
   3142 my $all = $total + $numskipped;
   3143 
   3144 runtimestats($lasttest);
   3145 
   3146 if($all) {
   3147     logmsg "TESTDONE: $all tests were considered during ".
   3148         sprintf("%.0f", $sofar) ." seconds.\n";
   3149 }
   3150 
   3151 if(%skipped && !$short) {
   3152     my $s=0;
   3153     # Temporary hash to print the restraints sorted by the number
   3154     # of their occurrences
   3155     my %restraints;
   3156     logmsg "TESTINFO: $numskipped tests were skipped due to these restraints:\n";
   3157 
   3158     for(keys %skipped) {
   3159         my $r = $_;
   3160         my $skip_count = $skipped{$r};
   3161         my $log_line = sprintf("TESTINFO: \"%s\" %d time%s (", $r, $skip_count,
   3162                            ($skip_count == 1) ? "" : "s");
   3163 
   3164         # now gather all test case numbers that had this reason for being
   3165         # skipped
   3166         my $c=0;
   3167         my $max = 9;
   3168         for(0 .. scalar @teststat) {
   3169             my $t = $_;
   3170             if($teststat[$t] && ($teststat[$t] eq $r)) {
   3171                 if($c < $max) {
   3172                     $log_line .= ", " if($c);
   3173                     $log_line .= $t;
   3174                 }
   3175                 $c++;
   3176             }
   3177         }
   3178         if($c > $max) {
   3179             $log_line .= " and ".($c-$max)." more";
   3180         }
   3181         $log_line .= ")\n";
   3182         $restraints{$log_line} = $skip_count;
   3183     }
   3184     foreach my $log_line (sort {$restraints{$b} <=> $restraints{$a} || uc($a) cmp uc($b)} keys %restraints) {
   3185         logmsg $log_line;
   3186     }
   3187 }
   3188 
   3189 sub testnumdetails {
   3190     my ($desc, $numlist) = @_;
   3191     foreach my $testnum (split(' ', $numlist)) {
   3192         if(!loadtest("${TESTDIR}/test${testnum}")) {
   3193             my @info_keywords = getpart("info", "keywords");
   3194             my $testname = (getpart("client", "name"))[0];
   3195             chomp $testname;
   3196             logmsg "$desc $testnum: '$testname'";
   3197             my $first = 1;
   3198             for my $k (@info_keywords) {
   3199                 chomp $k;
   3200                 my $sep = ($first == 1) ? " " : ", ";
   3201                 logmsg "$sep$k";
   3202                 $first = 0;
   3203             }
   3204             logmsg "\n";
   3205         }
   3206     }
   3207 }
   3208 
   3209 if($executed) {
   3210     if($failedre) {
   3211         my $sorted = numsortwords($failedre);
   3212         logmsg "::group::Failed Retried Test details\n";
   3213         testnumdetails("FAIL-RETRIED", $sorted);
   3214         logmsg "RETRIED: failed tests: $sorted\n";
   3215         logmsg "::endgroup::\n";
   3216     }
   3217 
   3218     if($passedign) {
   3219         my $sorted = numsortwords($passedign);
   3220         logmsg "::group::Passed Ignored Test details\n";
   3221         testnumdetails("PASSED-IGNORED", $sorted);
   3222         logmsg "IGNORED: passed tests: $sorted\n";
   3223         logmsg "::endgroup::\n";
   3224     }
   3225 
   3226     if($failedign) {
   3227         my $sorted = numsortwords($failedign);
   3228         testnumdetails("FAIL-IGNORED", $sorted);
   3229         logmsg "IGNORED: failed tests: $sorted\n";
   3230     }
   3231     logmsg sprintf("TESTDONE: $ok tests out of $total reported OK: %d%%\n",
   3232                    $ok/$total*100);
   3233 
   3234     if($failed && ($ok != $total)) {
   3235         my $failedsorted = numsortwords($failed);
   3236         logmsg "\n";
   3237         testnumdetails("FAIL", $failedsorted);
   3238         logmsg "\nTESTFAIL: These test cases failed: $failedsorted\n\n";
   3239     }
   3240 }
   3241 else {
   3242     logmsg "\nTESTFAIL: No tests were performed\n\n";
   3243     if(scalar(keys %enabled_keywords)) {
   3244         logmsg "TESTFAIL: Nothing matched these keywords: ";
   3245         for(keys %enabled_keywords) {
   3246             logmsg "$_ ";
   3247         }
   3248         logmsg "\n";
   3249     }
   3250 }
   3251 
   3252 if(($total && (($ok+$ign) != $total)) || !$total || $unexpected) {
   3253     exit 1;
   3254 }