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