mirror of
https://github.com/curl/curl.git
synced 2024-12-15 06:40:09 +08:00
707f74c04a
testutil.pm now contains a few miscellaneous functions that are used in several places but have no better place to live. subvariables moves to servers.pm since most variables that it substitutes relate to servers, so this is the most appropriate place. Rename a few functions for better naming consistency. Ref: #10818 Closes #10995
2512 lines
78 KiB
Perl
Executable File
2512 lines
78 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#***************************************************************************
|
|
# _ _ ____ _
|
|
# Project ___| | | | _ \| |
|
|
# / __| | | | |_) | |
|
|
# | (__| |_| | _ <| |___
|
|
# \___|\___/|_| \_\_____|
|
|
#
|
|
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
#
|
|
# This software is licensed as described in the file COPYING, which
|
|
# you should have received as part of this distribution. The terms
|
|
# are also available at https://curl.se/docs/copyright.html.
|
|
#
|
|
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
# copies of the Software, and permit persons to whom the Software is
|
|
# furnished to do so, under the terms of the COPYING file.
|
|
#
|
|
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
# KIND, either express or implied.
|
|
#
|
|
# SPDX-License-Identifier: curl
|
|
#
|
|
###########################################################################
|
|
|
|
# Experimental hooks are available to run tests remotely on machines that
|
|
# are able to run curl but are unable to run the test harness.
|
|
# The following sections need to be modified:
|
|
#
|
|
# $HOSTIP, $HOST6IP - Set to the address of the host running the test suite
|
|
# $CLIENTIP, $CLIENT6IP - Set to the address of the host running curl
|
|
# runclient, runclientoutput - Modify to copy all the files in the log/
|
|
# directory to the system running curl, run the given command remotely
|
|
# and save the return code or returned stdout (respectively), then
|
|
# copy all the files from the remote system's log/ directory back to
|
|
# the host running the test suite. This can be done a few ways, such
|
|
# as using scp & ssh, rsync & telnet, or using a NFS shared directory
|
|
# and ssh.
|
|
#
|
|
# 'make && make test' needs to be done on both machines before making the
|
|
# above changes and running runtests.pl manually. In the shared NFS case,
|
|
# the contents of the tests/server/ directory must be from the host
|
|
# running the test suite, while the rest must be from the host running curl.
|
|
#
|
|
# Note that even with these changes a number of tests will still fail (mainly
|
|
# to do with cookies, those that set environment variables, or those that
|
|
# do more than touch the file system in a <precheck> or <postcheck>
|
|
# section). These can be added to the $TESTCASES line below,
|
|
# e.g. $TESTCASES="!8 !31 !63 !cookies..."
|
|
#
|
|
# Finally, to properly support -g and -n, checktestcmd needs to change
|
|
# to check the remote system's PATH, and the places in the code where
|
|
# the curl binary is read directly to determine its type also need to be
|
|
# fixed. As long as the -g option is never given, and the -n is always
|
|
# given, this won't be a problem.
|
|
|
|
use strict;
|
|
# Promote all warnings to fatal
|
|
use warnings FATAL => 'all';
|
|
use 5.006;
|
|
|
|
# These should be the only variables that might be needed to get edited:
|
|
|
|
BEGIN {
|
|
# Define srcdir to the location of the tests source directory. This is
|
|
# usually set by the Makefile, but for out-of-tree builds with direct
|
|
# invocation of runtests.pl, it may not be set.
|
|
if(!defined $ENV{'srcdir'}) {
|
|
use File::Basename;
|
|
$ENV{'srcdir'} = dirname(__FILE__);
|
|
}
|
|
push(@INC, $ENV{'srcdir'});
|
|
# run time statistics needs Time::HiRes
|
|
eval {
|
|
no warnings "all";
|
|
require Time::HiRes;
|
|
import Time::HiRes qw( time );
|
|
}
|
|
}
|
|
|
|
use Digest::MD5 qw(md5);
|
|
use List::Util 'sum';
|
|
|
|
use pathhelp qw(
|
|
exe_ext
|
|
sys_native_current_path
|
|
);
|
|
use processhelp qw(
|
|
portable_sleep
|
|
);
|
|
|
|
use appveyor;
|
|
use azure;
|
|
use getpart; # array functions
|
|
use servers;
|
|
use valgrind; # valgrind report parser
|
|
use globalconfig;
|
|
use runner;
|
|
use testutil;
|
|
|
|
my %custom_skip_reasons;
|
|
|
|
my $ACURL=$VCURL; # what curl binary to use to talk to APIs (relevant for CI)
|
|
# ACURL is handy to set to the system one for reliability
|
|
my $CURLCONFIG="../curl-config"; # curl-config from current build
|
|
|
|
# Normally, all test cases should be run, but at times it is handy to
|
|
# simply run a particular one:
|
|
my $TESTCASES="all";
|
|
|
|
# To run specific test cases, set them like:
|
|
# $TESTCASES="1 2 3 7 8";
|
|
|
|
#######################################################################
|
|
# No variables below this point should need to be modified
|
|
#
|
|
|
|
my $libtool;
|
|
my $repeat = 0;
|
|
|
|
my $start; # time at which testing started
|
|
|
|
my $uname_release = `uname -r`;
|
|
my $is_wsl = $uname_release =~ /Microsoft$/;
|
|
|
|
my $http_ipv6; # set if HTTP server has IPv6 support
|
|
my $http_unix; # set if HTTP server has Unix sockets support
|
|
my $ftp_ipv6; # set if FTP server has IPv6 support
|
|
|
|
my $resolver; # name of the resolver backend (for human presentation)
|
|
|
|
my $has_textaware; # set if running on a system that has a text mode concept
|
|
# on files. Windows for example
|
|
|
|
my %skipped; # skipped{reason}=counter, reasons for skip
|
|
my @teststat; # teststat[testnum]=reason, reasons for skip
|
|
my %disabled_keywords; # key words of tests to skip
|
|
my %ignored_keywords; # key words of tests to ignore results
|
|
my %enabled_keywords; # key words of tests to run
|
|
my %disabled; # disabled test cases
|
|
my %ignored; # ignored results of test cases
|
|
|
|
my $timestats; # time stamping and stats generation
|
|
my $fullstats; # show time stats for every single test
|
|
my %timeprepini; # timestamp for each test preparation start
|
|
|
|
#######################################################################
|
|
# variables that command line options may set
|
|
#
|
|
|
|
my $short;
|
|
my $no_debuginfod;
|
|
my $keepoutfiles; # keep stdout and stderr files after tests
|
|
my $clearlocks; # force removal of files by killing locking processes
|
|
my $postmortem; # display detailed info about failed tests
|
|
my $run_disabled; # run the specific tests even if listed in DISABLED
|
|
my $scrambleorder;
|
|
my $randseed = 0;
|
|
|
|
# Azure Pipelines specific variables
|
|
my $AZURE_RUN_ID = 0;
|
|
my $AZURE_RESULT_ID = 0;
|
|
|
|
#######################################################################
|
|
# logmsg is our general message logging subroutine.
|
|
#
|
|
sub logmsg {
|
|
for(@_) {
|
|
my $line = $_;
|
|
if ($is_wsl) {
|
|
# use \r\n for WSL shell
|
|
$line =~ s/\r?\n$/\r\n/g;
|
|
}
|
|
print "$line";
|
|
}
|
|
}
|
|
|
|
# enable memory debugging if curl is compiled with it
|
|
$ENV{'CURL_MEMDEBUG'} = $memdump;
|
|
$ENV{'CURL_ENTROPY'}="12345678";
|
|
$ENV{'CURL_FORCETIME'}=1; # for debug NTLM magic
|
|
$ENV{'CURL_GLOBAL_INIT'}=1; # debug curl_global_init/cleanup use
|
|
$ENV{'HOME'}=$pwd;
|
|
$ENV{'CURL_HOME'}=$ENV{'HOME'};
|
|
$ENV{'XDG_CONFIG_HOME'}=$ENV{'HOME'};
|
|
$ENV{'COLUMNS'}=79; # screen width!
|
|
|
|
sub catch_zap {
|
|
my $signame = shift;
|
|
logmsg "runtests.pl received SIG$signame, exiting\n";
|
|
stopservers($verbose);
|
|
die "Somebody sent me a SIG$signame";
|
|
}
|
|
$SIG{INT} = \&catch_zap;
|
|
$SIG{TERM} = \&catch_zap;
|
|
|
|
##########################################################################
|
|
# Clear all possible '*_proxy' environment variables for various protocols
|
|
# to prevent them to interfere with our testing!
|
|
|
|
foreach my $protocol (('ftp', 'http', 'ftps', 'https', 'no', 'all')) {
|
|
my $proxy = "${protocol}_proxy";
|
|
# clear lowercase version
|
|
delete $ENV{$proxy} if($ENV{$proxy});
|
|
# clear uppercase version
|
|
delete $ENV{uc($proxy)} if($ENV{uc($proxy)});
|
|
}
|
|
|
|
# make sure we don't get affected by other variables that control our
|
|
# behavior
|
|
|
|
delete $ENV{'SSL_CERT_DIR'} if($ENV{'SSL_CERT_DIR'});
|
|
delete $ENV{'SSL_CERT_PATH'} if($ENV{'SSL_CERT_PATH'});
|
|
delete $ENV{'CURL_CA_BUNDLE'} if($ENV{'CURL_CA_BUNDLE'});
|
|
|
|
# provide defaults from our config file for ENV vars not explicitly
|
|
# set by the caller
|
|
if (open(my $fd, "<", "config")) {
|
|
while(my $line = <$fd>) {
|
|
next if ($line =~ /^#/);
|
|
chomp $line;
|
|
my ($name, $val) = split(/\s*:\s*/, $line, 2);
|
|
$ENV{$name} = $val if(!$ENV{$name});
|
|
}
|
|
close($fd);
|
|
}
|
|
|
|
# Check if we have nghttpx available and if it talks http/3
|
|
my $nghttpx_h3 = 0;
|
|
if (!$ENV{"NGHTTPX"}) {
|
|
$ENV{"NGHTTPX"} = checktestcmd("nghttpx");
|
|
}
|
|
if ($ENV{"NGHTTPX"}) {
|
|
my $nghttpx_version=join(' ', `"$ENV{'NGHTTPX'} -v 2>/dev/null"`);
|
|
$nghttpx_h3 = $nghttpx_version =~ /nghttp3\//;
|
|
chomp $nghttpx_h3;
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Get the list of tests that the tests/data/Makefile.am knows about!
|
|
#
|
|
my $disttests = "";
|
|
sub get_disttests {
|
|
# If a non-default $TESTDIR is being used there may not be any
|
|
# Makefile.inc in which case there's nothing to do.
|
|
open(my $dh, "<", "$TESTDIR/Makefile.inc") or return;
|
|
while(<$dh>) {
|
|
chomp $_;
|
|
if(($_ =~ /^#/) ||($_ !~ /test/)) {
|
|
next;
|
|
}
|
|
$disttests .= $_;
|
|
}
|
|
close($dh);
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Remove all files in the specified directory
|
|
#
|
|
sub cleardir {
|
|
my $dir = $_[0];
|
|
my $done = 1; # success
|
|
my $file;
|
|
|
|
# Get all files
|
|
opendir(my $dh, $dir) ||
|
|
return 0; # can't open dir
|
|
while($file = readdir($dh)) {
|
|
# Don't clear the $PIDDIR since those need to live beyond one test
|
|
if(($file !~ /^(\.|\.\.)\z/) && "$dir/$file" ne $PIDDIR) {
|
|
if(-d "$dir/$file") {
|
|
if(!cleardir("$dir/$file")) {
|
|
$done = 0;
|
|
}
|
|
if(!rmdir("$dir/$file")) {
|
|
$done = 0;
|
|
}
|
|
}
|
|
else {
|
|
# Ignore stunnel since we cannot do anything about its locks
|
|
if(!unlink("$dir/$file") && "$file" !~ /_stunnel\.log$/) {
|
|
$done = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
closedir $dh;
|
|
return $done;
|
|
}
|
|
|
|
#######################################################################
|
|
# compare test results with the expected output, we might filter off
|
|
# some pattern that is allowed to differ, output test results
|
|
#
|
|
sub compare {
|
|
my ($testnum, $testname, $subject, $firstref, $secondref)=@_;
|
|
|
|
my $result = compareparts($firstref, $secondref);
|
|
|
|
if($result) {
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
|
|
if(!$short) {
|
|
logmsg "\n $testnum: $subject FAILED:\n";
|
|
logmsg showdiff($LOGDIR, $firstref, $secondref);
|
|
}
|
|
elsif(!$automakestyle) {
|
|
logmsg "FAILED\n";
|
|
}
|
|
else {
|
|
# automakestyle
|
|
logmsg "FAIL: $testnum - $testname - $subject\n";
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
#######################################################################
|
|
# Check & display information about curl and the host the test suite runs on.
|
|
# Information to do with servers is displayed in displayserverfeatures, after
|
|
# the server initialization is performed.
|
|
sub checksystemfeatures {
|
|
my $feat;
|
|
my $curl;
|
|
my $libcurl;
|
|
my $versretval;
|
|
my $versnoexec;
|
|
my @version=();
|
|
my @disabled;
|
|
my $dis = "";
|
|
|
|
my $curlverout="$LOGDIR/curlverout.log";
|
|
my $curlvererr="$LOGDIR/curlvererr.log";
|
|
my $versioncmd="$CURL --version 1>$curlverout 2>$curlvererr";
|
|
|
|
unlink($curlverout);
|
|
unlink($curlvererr);
|
|
|
|
$versretval = runclient($versioncmd);
|
|
$versnoexec = $!;
|
|
|
|
open(my $versout, "<", "$curlverout");
|
|
@version = <$versout>;
|
|
close($versout);
|
|
|
|
open(my $disabledh, "-|", "server/disabled".exe_ext('TOOL'));
|
|
@disabled = <$disabledh>;
|
|
close($disabledh);
|
|
|
|
if($disabled[0]) {
|
|
s/[\r\n]//g for @disabled;
|
|
$dis = join(", ", @disabled);
|
|
}
|
|
|
|
$resolver="stock";
|
|
for(@version) {
|
|
chomp;
|
|
|
|
if($_ =~ /^curl ([^ ]*)/) {
|
|
$curl = $_;
|
|
$CURLVERSION = $1;
|
|
$curl =~ s/^(.*)(libcurl.*)/$1/g || die "Failure determining curl binary version";
|
|
|
|
$libcurl = $2;
|
|
if($curl =~ /linux|bsd|solaris/) {
|
|
# system support LD_PRELOAD; may be disabled later
|
|
$feature{"ld_preload"} = 1;
|
|
}
|
|
if($curl =~ /win32|Windows|mingw(32|64)/) {
|
|
# This is a Windows MinGW build or native build, we need to use
|
|
# Win32-style path.
|
|
$pwd = sys_native_current_path();
|
|
$has_textaware = 1;
|
|
$feature{"win32"} = 1;
|
|
# set if built with MinGW (as opposed to MinGW-w64)
|
|
$feature{"MinGW"} = 1 if ($curl =~ /-pc-mingw32/);
|
|
}
|
|
if ($libcurl =~ /\s(winssl|schannel)\b/i) {
|
|
$feature{"Schannel"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\sopenssl\b/i) {
|
|
$feature{"OpenSSL"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\sgnutls\b/i) {
|
|
$feature{"GnuTLS"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\srustls-ffi\b/i) {
|
|
$feature{"rustls"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\snss\b/i) {
|
|
$feature{"NSS"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\swolfssl\b/i) {
|
|
$feature{"wolfssl"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\sbearssl\b/i) {
|
|
$feature{"bearssl"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\ssecuretransport\b/i) {
|
|
$feature{"sectransp"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\sBoringSSL\b/i) {
|
|
# OpenSSL compatible API
|
|
$feature{"OpenSSL"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\slibressl\b/i) {
|
|
# OpenSSL compatible API
|
|
$feature{"OpenSSL"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
elsif ($libcurl =~ /\smbedTLS\b/i) {
|
|
$feature{"mbedtls"} = 1;
|
|
$feature{"SSLpinning"} = 1;
|
|
}
|
|
if ($libcurl =~ /ares/i) {
|
|
$feature{"c-ares"} = 1;
|
|
$resolver="c-ares";
|
|
}
|
|
if ($libcurl =~ /Hyper/i) {
|
|
$feature{"hyper"} = 1;
|
|
}
|
|
if ($libcurl =~ /nghttp2/i) {
|
|
# nghttp2 supports h2c, hyper does not
|
|
$feature{"h2c"} = 1;
|
|
}
|
|
if ($libcurl =~ /libssh2/i) {
|
|
$feature{"libssh2"} = 1;
|
|
}
|
|
if ($libcurl =~ /libssh\/([0-9.]*)\//i) {
|
|
$feature{"libssh"} = 1;
|
|
if($1 =~ /(\d+)\.(\d+).(\d+)/) {
|
|
my $v = $1 * 100 + $2 * 10 + $3;
|
|
if($v < 94) {
|
|
# before 0.9.4
|
|
$feature{"oldlibssh"} = 1;
|
|
}
|
|
}
|
|
}
|
|
if ($libcurl =~ /wolfssh/i) {
|
|
$feature{"wolfssh"} = 1;
|
|
}
|
|
}
|
|
elsif($_ =~ /^Protocols: (.*)/i) {
|
|
# these are the protocols compiled in to this libcurl
|
|
@protocols = split(' ', lc($1));
|
|
|
|
# Generate a "proto-ipv6" version of each protocol to match the
|
|
# IPv6 <server> name and a "proto-unix" to match the variant which
|
|
# uses Unix domain sockets. This works even if support isn't
|
|
# compiled in because the <features> test will fail.
|
|
push @protocols, map(("$_-ipv6", "$_-unix"), @protocols);
|
|
|
|
# 'http-proxy' is used in test cases to do CONNECT through
|
|
push @protocols, 'http-proxy';
|
|
|
|
# 'none' is used in test cases to mean no server
|
|
push @protocols, 'none';
|
|
}
|
|
elsif($_ =~ /^Features: (.*)/i) {
|
|
$feat = $1;
|
|
|
|
# built with memory tracking support (--enable-curldebug); may be disabled later
|
|
$feature{"TrackMemory"} = $feat =~ /TrackMemory/i;
|
|
# curl was built with --enable-debug
|
|
$feature{"debug"} = $feat =~ /debug/i;
|
|
# ssl enabled
|
|
$feature{"SSL"} = $feat =~ /SSL/i;
|
|
# multiple ssl backends available.
|
|
$feature{"MultiSSL"} = $feat =~ /MultiSSL/i;
|
|
# large file support
|
|
$feature{"large_file"} = $feat =~ /Largefile/i;
|
|
# IDN support
|
|
$feature{"idn"} = $feat =~ /IDN/i;
|
|
# IPv6 support
|
|
$feature{"ipv6"} = $feat =~ /IPv6/i;
|
|
# Unix sockets support
|
|
$feature{"unix-sockets"} = $feat =~ /UnixSockets/i;
|
|
# libz compression
|
|
$feature{"libz"} = $feat =~ /libz/i;
|
|
# Brotli compression
|
|
$feature{"brotli"} = $feat =~ /brotli/i;
|
|
# Zstd compression
|
|
$feature{"zstd"} = $feat =~ /zstd/i;
|
|
# NTLM enabled
|
|
$feature{"NTLM"} = $feat =~ /NTLM/i;
|
|
# NTLM delegation to winbind daemon ntlm_auth helper enabled
|
|
$feature{"NTLM_WB"} = $feat =~ /NTLM_WB/i;
|
|
# SSPI enabled
|
|
$feature{"SSPI"} = $feat =~ /SSPI/i;
|
|
# GSS-API enabled
|
|
$feature{"GSS-API"} = $feat =~ /GSS-API/i;
|
|
# Kerberos enabled
|
|
$feature{"Kerberos"} = $feat =~ /Kerberos/i;
|
|
# SPNEGO enabled
|
|
$feature{"SPNEGO"} = $feat =~ /SPNEGO/i;
|
|
# CharConv enabled
|
|
$feature{"CharConv"} = $feat =~ /CharConv/i;
|
|
# TLS-SRP enabled
|
|
$feature{"TLS-SRP"} = $feat =~ /TLS-SRP/i;
|
|
# PSL enabled
|
|
$feature{"PSL"} = $feat =~ /PSL/i;
|
|
# alt-svc enabled
|
|
$feature{"alt-svc"} = $feat =~ /alt-svc/i;
|
|
# HSTS support
|
|
$feature{"HSTS"} = $feat =~ /HSTS/i;
|
|
if($feat =~ /AsynchDNS/i) {
|
|
if(!$feature{"c-ares"}) {
|
|
# this means threaded resolver
|
|
$feature{"threaded-resolver"} = 1;
|
|
$resolver="threaded";
|
|
}
|
|
}
|
|
# http2 enabled
|
|
$feature{"http/2"} = $feat =~ /HTTP2/;
|
|
if($feature{"http/2"}) {
|
|
push @protocols, 'http/2';
|
|
}
|
|
# http3 enabled
|
|
$feature{"http/3"} = $feat =~ /HTTP3/;
|
|
if($feature{"http/3"}) {
|
|
push @protocols, 'http/3';
|
|
}
|
|
# https proxy support
|
|
$feature{"https-proxy"} = $feat =~ /HTTPS-proxy/;
|
|
if($feature{"https-proxy"}) {
|
|
# 'https-proxy' is used as "server" so consider it a protocol
|
|
push @protocols, 'https-proxy';
|
|
}
|
|
# UNICODE support
|
|
$feature{"Unicode"} = $feat =~ /Unicode/i;
|
|
# Thread-safe init
|
|
$feature{"threadsafe"} = $feat =~ /threadsafe/i;
|
|
}
|
|
#
|
|
# Test harness currently uses a non-stunnel server in order to
|
|
# run HTTP TLS-SRP tests required when curl is built with https
|
|
# protocol support and TLS-SRP feature enabled. For convenience
|
|
# 'httptls' may be included in the test harness protocols array
|
|
# to differentiate this from classic stunnel based 'https' test
|
|
# harness server.
|
|
#
|
|
if($feature{"TLS-SRP"}) {
|
|
my $add_httptls;
|
|
for(@protocols) {
|
|
if($_ =~ /^https(-ipv6|)$/) {
|
|
$add_httptls=1;
|
|
last;
|
|
}
|
|
}
|
|
if($add_httptls && (! grep /^httptls$/, @protocols)) {
|
|
push @protocols, 'httptls';
|
|
push @protocols, 'httptls-ipv6';
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!$curl) {
|
|
logmsg "unable to get curl's version, further details are:\n";
|
|
logmsg "issued command: \n";
|
|
logmsg "$versioncmd \n";
|
|
if ($versretval == -1) {
|
|
logmsg "command failed with: \n";
|
|
logmsg "$versnoexec \n";
|
|
}
|
|
elsif ($versretval & 127) {
|
|
logmsg sprintf("command died with signal %d, and %s coredump.\n",
|
|
($versretval & 127), ($versretval & 128)?"a":"no");
|
|
}
|
|
else {
|
|
logmsg sprintf("command exited with value %d \n", $versretval >> 8);
|
|
}
|
|
logmsg "contents of $curlverout: \n";
|
|
displaylogcontent("$curlverout");
|
|
logmsg "contents of $curlvererr: \n";
|
|
displaylogcontent("$curlvererr");
|
|
die "couldn't get curl's version";
|
|
}
|
|
|
|
if(-r "../lib/curl_config.h") {
|
|
open(my $conf, "<", "../lib/curl_config.h");
|
|
while(<$conf>) {
|
|
if($_ =~ /^\#define HAVE_GETRLIMIT/) {
|
|
# set if system has getrlimit()
|
|
$feature{"getrlimit"} = 1;
|
|
}
|
|
}
|
|
close($conf);
|
|
}
|
|
|
|
# allow this feature only if debug mode is disabled
|
|
$feature{"ld_preload"} = $feature{"ld_preload"} && !$feature{"debug"};
|
|
|
|
if($feature{"ipv6"}) {
|
|
# client has IPv6 support
|
|
|
|
# check if the HTTP server has it!
|
|
my $cmd = "server/sws".exe_ext('SRV')." --version";
|
|
my @sws = `$cmd`;
|
|
if($sws[0] =~ /IPv6/) {
|
|
# HTTP server has IPv6 support!
|
|
$http_ipv6 = 1;
|
|
}
|
|
|
|
# check if the FTP server has it!
|
|
$cmd = "server/sockfilt".exe_ext('SRV')." --version";
|
|
@sws = `$cmd`;
|
|
if($sws[0] =~ /IPv6/) {
|
|
# FTP server has IPv6 support!
|
|
$ftp_ipv6 = 1;
|
|
}
|
|
}
|
|
|
|
if($feature{"unix-sockets"}) {
|
|
# client has Unix sockets support, check whether the HTTP server has it
|
|
my $cmd = "server/sws".exe_ext('SRV')." --version";
|
|
my @sws = `$cmd`;
|
|
$http_unix = 1 if($sws[0] =~ /unix/);
|
|
}
|
|
|
|
open(my $manh, "-|", "$CURL -M 2>&1");
|
|
while(my $s = <$manh>) {
|
|
if($s =~ /built-in manual was disabled at build-time/) {
|
|
$feature{"manual"} = 0;
|
|
last;
|
|
}
|
|
$feature{"manual"} = 1;
|
|
last;
|
|
}
|
|
close($manh);
|
|
|
|
$feature{"unittest"} = $feature{"debug"};
|
|
$feature{"nghttpx"} = !!$ENV{'NGHTTPX'};
|
|
$feature{"nghttpx-h3"} = !!$nghttpx_h3;
|
|
|
|
#
|
|
# strings that must exactly match the names used in server/disabled.c
|
|
#
|
|
$feature{"cookies"} = 1;
|
|
# Use this as a proxy for any cryptographic authentication
|
|
$feature{"crypto"} = $feature{"NTLM"} || $feature{"Kerberos"} || $feature{"SPNEGO"};
|
|
$feature{"DoH"} = 1;
|
|
$feature{"HTTP-auth"} = 1;
|
|
$feature{"Mime"} = 1;
|
|
$feature{"netrc"} = 1;
|
|
$feature{"parsedate"} = 1;
|
|
$feature{"proxy"} = 1;
|
|
$feature{"shuffle-dns"} = 1;
|
|
$feature{"typecheck"} = 1;
|
|
$feature{"verbose-strings"} = 1;
|
|
$feature{"wakeup"} = 1;
|
|
$feature{"headers-api"} = 1;
|
|
$feature{"xattr"} = 1;
|
|
|
|
# make each protocol an enabled "feature"
|
|
for my $p (@protocols) {
|
|
$feature{$p} = 1;
|
|
}
|
|
# 'socks' was once here but is now removed
|
|
|
|
$has_shared = `sh $CURLCONFIG --built-shared`;
|
|
chomp $has_shared;
|
|
$has_shared = $has_shared eq "yes";
|
|
|
|
if(!$feature{"TrackMemory"} && $torture) {
|
|
die "can't run torture tests since curl was built without ".
|
|
"TrackMemory feature (--enable-curldebug)";
|
|
}
|
|
|
|
my $hostname=join(' ', runclientoutput("hostname"));
|
|
my $hosttype=join(' ', runclientoutput("uname -a"));
|
|
my $hostos=$^O;
|
|
|
|
# display summary information about curl and the test host
|
|
logmsg ("********* System characteristics ******** \n",
|
|
"* $curl\n",
|
|
"* $libcurl\n",
|
|
"* Features: $feat\n",
|
|
"* Disabled: $dis\n",
|
|
"* Host: $hostname",
|
|
"* System: $hosttype",
|
|
"* OS: $hostos\n");
|
|
|
|
if($feature{"TrackMemory"} && $feature{"threaded-resolver"}) {
|
|
logmsg("*\n",
|
|
"*** DISABLES memory tracking when using threaded resolver\n",
|
|
"*\n");
|
|
}
|
|
|
|
logmsg sprintf("* Env: %s%s%s", $valgrind?"Valgrind ":"",
|
|
$run_event_based?"event-based ":"",
|
|
$nghttpx_h3);
|
|
logmsg sprintf("%s\n", $libtool?"Libtool ":"");
|
|
logmsg ("* Seed: $randseed\n");
|
|
|
|
# Disable memory tracking when using threaded resolver
|
|
$feature{"TrackMemory"} = $feature{"TrackMemory"} && !$feature{"threaded-resolver"};
|
|
|
|
# toggle off the features that were disabled in the build
|
|
for my $d(@disabled) {
|
|
$feature{$d} = 0;
|
|
}
|
|
}
|
|
|
|
#######################################################################
|
|
# display information about server features
|
|
#
|
|
sub displayserverfeatures {
|
|
logmsg sprintf("* Servers: %s", $stunnel?"SSL ":"");
|
|
logmsg sprintf("%s", $http_ipv6?"HTTP-IPv6 ":"");
|
|
logmsg sprintf("%s", $http_unix?"HTTP-unix ":"");
|
|
logmsg sprintf("%s\n", $ftp_ipv6?"FTP-IPv6 ":"");
|
|
|
|
if($verbose) {
|
|
if($feature{"unix-sockets"}) {
|
|
logmsg "* Unix socket paths:\n";
|
|
if($http_unix) {
|
|
logmsg sprintf("* HTTP-Unix:%s\n", $HTTPUNIXPATH);
|
|
logmsg sprintf("* Socks-Unix:%s\n", $SOCKSUNIXPATH);
|
|
}
|
|
}
|
|
}
|
|
|
|
logmsg "***************************************** \n";
|
|
}
|
|
|
|
#######################################################################
|
|
# Provide time stamps for single test skipped events
|
|
#
|
|
sub timestampskippedevents {
|
|
my $testnum = $_[0];
|
|
|
|
return if((not defined($testnum)) || ($testnum < 1));
|
|
|
|
if($timestats) {
|
|
|
|
if($timevrfyend{$testnum}) {
|
|
return;
|
|
}
|
|
elsif($timesrvrlog{$testnum}) {
|
|
$timevrfyend{$testnum} = $timesrvrlog{$testnum};
|
|
return;
|
|
}
|
|
elsif($timetoolend{$testnum}) {
|
|
$timevrfyend{$testnum} = $timetoolend{$testnum};
|
|
$timesrvrlog{$testnum} = $timetoolend{$testnum};
|
|
}
|
|
elsif($timetoolini{$testnum}) {
|
|
$timevrfyend{$testnum} = $timetoolini{$testnum};
|
|
$timesrvrlog{$testnum} = $timetoolini{$testnum};
|
|
$timetoolend{$testnum} = $timetoolini{$testnum};
|
|
}
|
|
elsif($timesrvrend{$testnum}) {
|
|
$timevrfyend{$testnum} = $timesrvrend{$testnum};
|
|
$timesrvrlog{$testnum} = $timesrvrend{$testnum};
|
|
$timetoolend{$testnum} = $timesrvrend{$testnum};
|
|
$timetoolini{$testnum} = $timesrvrend{$testnum};
|
|
}
|
|
elsif($timesrvrini{$testnum}) {
|
|
$timevrfyend{$testnum} = $timesrvrini{$testnum};
|
|
$timesrvrlog{$testnum} = $timesrvrini{$testnum};
|
|
$timetoolend{$testnum} = $timesrvrini{$testnum};
|
|
$timetoolini{$testnum} = $timesrvrini{$testnum};
|
|
$timesrvrend{$testnum} = $timesrvrini{$testnum};
|
|
}
|
|
elsif($timeprepini{$testnum}) {
|
|
$timevrfyend{$testnum} = $timeprepini{$testnum};
|
|
$timesrvrlog{$testnum} = $timeprepini{$testnum};
|
|
$timetoolend{$testnum} = $timeprepini{$testnum};
|
|
$timetoolini{$testnum} = $timeprepini{$testnum};
|
|
$timesrvrend{$testnum} = $timeprepini{$testnum};
|
|
$timesrvrini{$testnum} = $timeprepini{$testnum};
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
# Setup CI Test Run
|
|
sub citest_starttestrun {
|
|
if(azure_check_environment()) {
|
|
$AZURE_RUN_ID = azure_create_test_run($ACURL);
|
|
logmsg "Azure Run ID: $AZURE_RUN_ID\n" if ($verbose);
|
|
}
|
|
# Appveyor doesn't require anything here
|
|
}
|
|
|
|
|
|
# Register the test case with the CI runner
|
|
sub citest_starttest {
|
|
my $testnum = $_[0];
|
|
|
|
# get the name of the test early
|
|
my $testname= (getpart("client", "name"))[0];
|
|
chomp $testname;
|
|
|
|
# create test result in CI services
|
|
if(azure_check_environment() && $AZURE_RUN_ID) {
|
|
$AZURE_RESULT_ID = azure_create_test_result($ACURL, $AZURE_RUN_ID, $testnum, $testname);
|
|
}
|
|
elsif(appveyor_check_environment()) {
|
|
appveyor_create_test_result($ACURL, $testnum, $testname);
|
|
}
|
|
}
|
|
|
|
|
|
# Submit the test case result with the CI runner
|
|
sub citest_finishtest {
|
|
my ($testnum, $error) = @_;
|
|
# update test result in CI services
|
|
if(azure_check_environment() && $AZURE_RUN_ID && $AZURE_RESULT_ID) {
|
|
$AZURE_RESULT_ID = azure_update_test_result($ACURL, $AZURE_RUN_ID, $AZURE_RESULT_ID, $testnum, $error,
|
|
$timeprepini{$testnum}, $timevrfyend{$testnum});
|
|
}
|
|
elsif(appveyor_check_environment()) {
|
|
appveyor_update_test_result($ACURL, $testnum, $error, $timeprepini{$testnum}, $timevrfyend{$testnum});
|
|
}
|
|
}
|
|
|
|
# Complete CI test run
|
|
sub citest_finishtestrun {
|
|
if(azure_check_environment() && $AZURE_RUN_ID) {
|
|
$AZURE_RUN_ID = azure_update_test_run($ACURL, $AZURE_RUN_ID);
|
|
}
|
|
# Appveyor doesn't require anything here
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Verify that this test case should be run
|
|
sub singletest_shouldrun {
|
|
my $testnum = $_[0];
|
|
my $why; # why the test won't be run
|
|
my $errorreturncode = 1; # 1 means normal error, 2 means ignored error
|
|
my @what; # what features are needed
|
|
|
|
# first, remove all lingering log files
|
|
if(!cleardir($LOGDIR) && $clearlocks) {
|
|
clearlocks($LOGDIR);
|
|
cleardir($LOGDIR);
|
|
}
|
|
|
|
# timestamp test preparation start
|
|
$timeprepini{$testnum} = Time::HiRes::time();
|
|
|
|
if($disttests !~ /test$testnum(\W|\z)/ ) {
|
|
logmsg "Warning: test$testnum not present in tests/data/Makefile.inc\n";
|
|
}
|
|
if($disabled{$testnum}) {
|
|
if(!$run_disabled) {
|
|
$why = "listed in DISABLED";
|
|
}
|
|
else {
|
|
logmsg "Warning: test$testnum is explicitly disabled\n";
|
|
}
|
|
}
|
|
if($ignored{$testnum}) {
|
|
logmsg "Warning: test$testnum result is ignored\n";
|
|
$errorreturncode = 2;
|
|
}
|
|
|
|
# load the test case file definition
|
|
if(loadtest("${TESTDIR}/test${testnum}")) {
|
|
if($verbose) {
|
|
# this is not a test
|
|
logmsg "RUN: $testnum doesn't look like a test case\n";
|
|
}
|
|
$why = "no test";
|
|
}
|
|
else {
|
|
@what = getpart("client", "features");
|
|
}
|
|
|
|
# We require a feature to be present
|
|
for(@what) {
|
|
my $f = $_;
|
|
$f =~ s/\s//g;
|
|
|
|
if($f =~ /^([^!].*)$/) {
|
|
if($feature{$1}) {
|
|
next;
|
|
}
|
|
|
|
$why = "curl lacks $1 support";
|
|
last;
|
|
}
|
|
}
|
|
|
|
# We require a feature to not be present
|
|
if(!$why) {
|
|
for(@what) {
|
|
my $f = $_;
|
|
$f =~ s/\s//g;
|
|
|
|
if($f =~ /^!(.*)$/) {
|
|
if(!$feature{$1}) {
|
|
next;
|
|
}
|
|
}
|
|
else {
|
|
next;
|
|
}
|
|
|
|
$why = "curl has $1 support";
|
|
last;
|
|
}
|
|
}
|
|
|
|
my @info_keywords;
|
|
if(!$why) {
|
|
@info_keywords = getpart("info", "keywords");
|
|
|
|
# Clear the list of keywords from the last test
|
|
%keywords = ();
|
|
|
|
if(!$info_keywords[0]) {
|
|
$why = "missing the <keywords> section!";
|
|
}
|
|
|
|
my $match;
|
|
for my $k (@info_keywords) {
|
|
chomp $k;
|
|
if ($disabled_keywords{lc($k)}) {
|
|
$why = "disabled by keyword";
|
|
}
|
|
elsif ($enabled_keywords{lc($k)}) {
|
|
$match = 1;
|
|
}
|
|
if ($ignored_keywords{lc($k)}) {
|
|
logmsg "Warning: test$testnum result is ignored due to $k\n";
|
|
$errorreturncode = 2;
|
|
}
|
|
|
|
$keywords{$k} = 1;
|
|
}
|
|
|
|
if(!$why && !$match && %enabled_keywords) {
|
|
$why = "disabled by missing keyword";
|
|
}
|
|
}
|
|
|
|
if (!$why && defined $custom_skip_reasons{test}{$testnum}) {
|
|
$why = $custom_skip_reasons{test}{$testnum};
|
|
}
|
|
|
|
if (!$why && defined $custom_skip_reasons{tool}) {
|
|
foreach my $tool (getpart("client", "tool")) {
|
|
foreach my $tool_skip_pattern (keys %{$custom_skip_reasons{tool}}) {
|
|
if ($tool =~ /$tool_skip_pattern/i) {
|
|
$why = $custom_skip_reasons{tool}{$tool_skip_pattern};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$why && defined $custom_skip_reasons{keyword}) {
|
|
foreach my $keyword (@info_keywords) {
|
|
foreach my $keyword_skip_pattern (keys %{$custom_skip_reasons{keyword}}) {
|
|
if ($keyword =~ /$keyword_skip_pattern/i) {
|
|
$why = $custom_skip_reasons{keyword}{$keyword_skip_pattern};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ($why, $errorreturncode);
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Print the test name and count tests
|
|
sub singletest_count {
|
|
my ($testnum, $why) = @_;
|
|
|
|
if($why && !$listonly) {
|
|
# there's a problem, count it as "skipped"
|
|
$skipped{$why}++;
|
|
$teststat[$testnum]=$why; # store reason for this test case
|
|
|
|
if(!$short) {
|
|
if($skipped{$why} <= 3) {
|
|
# show only the first three skips for each reason
|
|
logmsg sprintf("test %04d SKIPPED: $why\n", $testnum);
|
|
}
|
|
}
|
|
|
|
timestampskippedevents($testnum);
|
|
return -1;
|
|
}
|
|
|
|
# At this point we've committed to run this test
|
|
logmsg sprintf("test %04d...", $testnum) if(!$automakestyle);
|
|
|
|
# name of the test
|
|
my $testname= (getpart("client", "name"))[0];
|
|
chomp $testname;
|
|
logmsg "[$testname]\n" if(!$short);
|
|
|
|
if($listonly) {
|
|
timestampskippedevents($testnum);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Verify test succeeded
|
|
sub singletest_check {
|
|
my ($testnum, $cmdres, $CURLOUT, $tool, $disablevalgrind)=@_;
|
|
|
|
# Skip all the verification on torture tests
|
|
if ($torture) {
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -2;
|
|
}
|
|
|
|
my @err = getpart("verify", "errorcode");
|
|
my $errorcode = $err[0] || "0";
|
|
my $ok="";
|
|
my $res;
|
|
chomp $errorcode;
|
|
my $testname= (getpart("client", "name"))[0];
|
|
chomp $testname;
|
|
# what parts to cut off from stdout/stderr
|
|
my @stripfile = getpart("verify", "stripfile");
|
|
|
|
my @validstdout = getpart("verify", "stdout");
|
|
if (@validstdout) {
|
|
# verify redirected stdout
|
|
my @actual = loadarray($STDOUT);
|
|
|
|
foreach my $strip (@stripfile) {
|
|
chomp $strip;
|
|
my @newgen;
|
|
for(@actual) {
|
|
eval $strip;
|
|
if($_) {
|
|
push @newgen, $_;
|
|
}
|
|
}
|
|
# this is to get rid of array entries that vanished (zero
|
|
# length) because of replacements
|
|
@actual = @newgen;
|
|
}
|
|
|
|
# get all attributes
|
|
my %hash = getpartattr("verify", "stdout");
|
|
|
|
# get the mode attribute
|
|
my $filemode=$hash{'mode'};
|
|
if($filemode && ($filemode eq "text") && $has_textaware) {
|
|
# text mode when running on windows: fix line endings
|
|
s/\r\n/\n/g for @validstdout;
|
|
s/\n/\r\n/g for @validstdout;
|
|
}
|
|
|
|
if($hash{'nonewline'}) {
|
|
# Yes, we must cut off the final newline from the final line
|
|
# of the protocol data
|
|
chomp($validstdout[-1]);
|
|
}
|
|
|
|
if($hash{'crlf'} ||
|
|
($feature{"hyper"} && ($keywords{"HTTP"}
|
|
|| $keywords{"HTTPS"}))) {
|
|
subnewlines(0, \$_) for @validstdout;
|
|
}
|
|
|
|
$res = compare($testnum, $testname, "stdout", \@actual, \@validstdout);
|
|
if($res) {
|
|
return -1;
|
|
}
|
|
$ok .= "s";
|
|
}
|
|
else {
|
|
$ok .= "-"; # stdout not checked
|
|
}
|
|
|
|
my @validstderr = getpart("verify", "stderr");
|
|
if (@validstderr) {
|
|
# verify redirected stderr
|
|
my @actual = loadarray($STDERR);
|
|
|
|
foreach my $strip (@stripfile) {
|
|
chomp $strip;
|
|
my @newgen;
|
|
for(@actual) {
|
|
eval $strip;
|
|
if($_) {
|
|
push @newgen, $_;
|
|
}
|
|
}
|
|
# this is to get rid of array entries that vanished (zero
|
|
# length) because of replacements
|
|
@actual = @newgen;
|
|
}
|
|
|
|
# get all attributes
|
|
my %hash = getpartattr("verify", "stderr");
|
|
|
|
# get the mode attribute
|
|
my $filemode=$hash{'mode'};
|
|
if($filemode && ($filemode eq "text") && $feature{"hyper"}) {
|
|
# text mode check in hyper-mode. Sometimes necessary if the stderr
|
|
# data *looks* like HTTP and thus has gotten CRLF newlines
|
|
# mistakenly
|
|
s/\r\n/\n/g for @validstderr;
|
|
}
|
|
if($filemode && ($filemode eq "text") && $has_textaware) {
|
|
# text mode when running on windows: fix line endings
|
|
s/\r\n/\n/g for @validstderr;
|
|
s/\n/\r\n/g for @validstderr;
|
|
}
|
|
|
|
if($hash{'nonewline'}) {
|
|
# Yes, we must cut off the final newline from the final line
|
|
# of the protocol data
|
|
chomp($validstderr[-1]);
|
|
}
|
|
|
|
$res = compare($testnum, $testname, "stderr", \@actual, \@validstderr);
|
|
if($res) {
|
|
return -1;
|
|
}
|
|
$ok .= "r";
|
|
}
|
|
else {
|
|
$ok .= "-"; # stderr not checked
|
|
}
|
|
|
|
# what to cut off from the live protocol sent by curl
|
|
my @strip = getpart("verify", "strip");
|
|
|
|
# what parts to cut off from the protocol & upload
|
|
my @strippart = getpart("verify", "strippart");
|
|
|
|
# this is the valid protocol blurb curl should generate
|
|
my @protocol= getpart("verify", "protocol");
|
|
if(@protocol) {
|
|
# Verify the sent request
|
|
my @out = loadarray($SERVERIN);
|
|
|
|
# check if there's any attributes on the verify/protocol section
|
|
my %hash = getpartattr("verify", "protocol");
|
|
|
|
if($hash{'nonewline'}) {
|
|
# Yes, we must cut off the final newline from the final line
|
|
# of the protocol data
|
|
chomp($protocol[-1]);
|
|
}
|
|
|
|
for(@strip) {
|
|
# strip off all lines that match the patterns from both arrays
|
|
chomp $_;
|
|
@out = striparray( $_, \@out);
|
|
@protocol= striparray( $_, \@protocol);
|
|
}
|
|
|
|
for my $strip (@strippart) {
|
|
chomp $strip;
|
|
for(@out) {
|
|
eval $strip;
|
|
}
|
|
}
|
|
|
|
if($hash{'crlf'}) {
|
|
subnewlines(1, \$_) for @protocol;
|
|
}
|
|
|
|
if((!$out[0] || ($out[0] eq "")) && $protocol[0]) {
|
|
logmsg "\n $testnum: protocol FAILED!\n".
|
|
" There was no content at all in the file $SERVERIN.\n".
|
|
" Server glitch? Total curl failure? Returned: $cmdres\n";
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
|
|
$res = compare($testnum, $testname, "protocol", \@out, \@protocol);
|
|
if($res) {
|
|
return -1;
|
|
}
|
|
|
|
$ok .= "p";
|
|
|
|
}
|
|
else {
|
|
$ok .= "-"; # protocol not checked
|
|
}
|
|
|
|
my %replyattr = getpartattr("reply", "data");
|
|
my @reply;
|
|
if (partexists("reply", "datacheck")) {
|
|
for my $partsuffix (('', '1', '2', '3', '4')) {
|
|
my @replycheckpart = getpart("reply", "datacheck".$partsuffix);
|
|
if(@replycheckpart) {
|
|
my %replycheckpartattr = getpartattr("reply", "datacheck".$partsuffix);
|
|
# get the mode attribute
|
|
my $filemode=$replycheckpartattr{'mode'};
|
|
if($filemode && ($filemode eq "text") && $has_textaware) {
|
|
# text mode when running on windows: fix line endings
|
|
s/\r\n/\n/g for @replycheckpart;
|
|
s/\n/\r\n/g for @replycheckpart;
|
|
}
|
|
if($replycheckpartattr{'nonewline'}) {
|
|
# Yes, we must cut off the final newline from the final line
|
|
# of the datacheck
|
|
chomp($replycheckpart[-1]);
|
|
}
|
|
if($replycheckpartattr{'crlf'} ||
|
|
($feature{"hyper"} && ($keywords{"HTTP"}
|
|
|| $keywords{"HTTPS"}))) {
|
|
subnewlines(0, \$_) for @replycheckpart;
|
|
}
|
|
push(@reply, @replycheckpart);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# check against the data section
|
|
@reply = getpart("reply", "data");
|
|
if(@reply) {
|
|
if($replyattr{'nonewline'}) {
|
|
# cut off the final newline from the final line of the data
|
|
chomp($reply[-1]);
|
|
}
|
|
}
|
|
# get the mode attribute
|
|
my $filemode=$replyattr{'mode'};
|
|
if($filemode && ($filemode eq "text") && $has_textaware) {
|
|
# text mode when running on windows: fix line endings
|
|
s/\r\n/\n/g for @reply;
|
|
s/\n/\r\n/g for @reply;
|
|
}
|
|
if($replyattr{'crlf'} ||
|
|
($feature{"hyper"} && ($keywords{"HTTP"}
|
|
|| $keywords{"HTTPS"}))) {
|
|
subnewlines(0, \$_) for @reply;
|
|
}
|
|
}
|
|
|
|
if(!$replyattr{'nocheck'} && (@reply || $replyattr{'sendzero'})) {
|
|
# verify the received data
|
|
my @out = loadarray($CURLOUT);
|
|
$res = compare($testnum, $testname, "data", \@out, \@reply);
|
|
if ($res) {
|
|
return -1;
|
|
}
|
|
$ok .= "d";
|
|
}
|
|
else {
|
|
$ok .= "-"; # data not checked
|
|
}
|
|
|
|
# if this section exists, we verify upload
|
|
my @upload = getpart("verify", "upload");
|
|
if(@upload) {
|
|
my %hash = getpartattr("verify", "upload");
|
|
if($hash{'nonewline'}) {
|
|
# cut off the final newline from the final line of the upload data
|
|
chomp($upload[-1]);
|
|
}
|
|
|
|
# verify uploaded data
|
|
my @out = loadarray("$LOGDIR/upload.$testnum");
|
|
for my $strip (@strippart) {
|
|
chomp $strip;
|
|
for(@out) {
|
|
eval $strip;
|
|
}
|
|
}
|
|
|
|
$res = compare($testnum, $testname, "upload", \@out, \@upload);
|
|
if ($res) {
|
|
return -1;
|
|
}
|
|
$ok .= "u";
|
|
}
|
|
else {
|
|
$ok .= "-"; # upload not checked
|
|
}
|
|
|
|
# this is the valid protocol blurb curl should generate to a proxy
|
|
my @proxyprot = getpart("verify", "proxy");
|
|
if(@proxyprot) {
|
|
# Verify the sent proxy request
|
|
# check if there's any attributes on the verify/protocol section
|
|
my %hash = getpartattr("verify", "proxy");
|
|
|
|
if($hash{'nonewline'}) {
|
|
# Yes, we must cut off the final newline from the final line
|
|
# of the protocol data
|
|
chomp($proxyprot[-1]);
|
|
}
|
|
|
|
my @out = loadarray($PROXYIN);
|
|
for(@strip) {
|
|
# strip off all lines that match the patterns from both arrays
|
|
chomp $_;
|
|
@out = striparray( $_, \@out);
|
|
@proxyprot= striparray( $_, \@proxyprot);
|
|
}
|
|
|
|
for my $strip (@strippart) {
|
|
chomp $strip;
|
|
for(@out) {
|
|
eval $strip;
|
|
}
|
|
}
|
|
|
|
if($hash{'crlf'} ||
|
|
($feature{"hyper"} && ($keywords{"HTTP"} || $keywords{"HTTPS"}))) {
|
|
subnewlines(0, \$_) for @proxyprot;
|
|
}
|
|
|
|
$res = compare($testnum, $testname, "proxy", \@out, \@proxyprot);
|
|
if($res) {
|
|
return -1;
|
|
}
|
|
|
|
$ok .= "P";
|
|
|
|
}
|
|
else {
|
|
$ok .= "-"; # protocol not checked
|
|
}
|
|
|
|
my $outputok;
|
|
for my $partsuffix (('', '1', '2', '3', '4')) {
|
|
my @outfile=getpart("verify", "file".$partsuffix);
|
|
if(@outfile || partexists("verify", "file".$partsuffix) ) {
|
|
# we're supposed to verify a dynamically generated file!
|
|
my %hash = getpartattr("verify", "file".$partsuffix);
|
|
|
|
my $filename=$hash{'name'};
|
|
if(!$filename) {
|
|
logmsg "ERROR: section verify=>file$partsuffix ".
|
|
"has no name attribute\n";
|
|
stopservers($verbose);
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
my @generated=loadarray($filename);
|
|
|
|
# what parts to cut off from the file
|
|
my @stripfilepar = getpart("verify", "stripfile".$partsuffix);
|
|
|
|
my $filemode=$hash{'mode'};
|
|
if($filemode && ($filemode eq "text") && $has_textaware) {
|
|
# text mode when running on windows: fix line endings
|
|
s/\r\n/\n/g for @outfile;
|
|
s/\n/\r\n/g for @outfile;
|
|
}
|
|
if($hash{'crlf'} ||
|
|
($feature{"hyper"} && ($keywords{"HTTP"}
|
|
|| $keywords{"HTTPS"}))) {
|
|
subnewlines(0, \$_) for @outfile;
|
|
}
|
|
|
|
for my $strip (@stripfilepar) {
|
|
chomp $strip;
|
|
my @newgen;
|
|
for(@generated) {
|
|
eval $strip;
|
|
if($_) {
|
|
push @newgen, $_;
|
|
}
|
|
}
|
|
# this is to get rid of array entries that vanished (zero
|
|
# length) because of replacements
|
|
@generated = @newgen;
|
|
}
|
|
|
|
$res = compare($testnum, $testname, "output ($filename)",
|
|
\@generated, \@outfile);
|
|
if($res) {
|
|
return -1;
|
|
}
|
|
|
|
$outputok = 1; # output checked
|
|
}
|
|
}
|
|
$ok .= ($outputok) ? "o" : "-"; # output checked or not
|
|
|
|
# verify SOCKS proxy details
|
|
my @socksprot = getpart("verify", "socks");
|
|
if(@socksprot) {
|
|
# Verify the sent SOCKS proxy details
|
|
my @out = loadarray($SOCKSIN);
|
|
$res = compare($testnum, $testname, "socks", \@out, \@socksprot);
|
|
if($res) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
# accept multiple comma-separated error codes
|
|
my @splerr = split(/ *, */, $errorcode);
|
|
my $errok;
|
|
foreach my $e (@splerr) {
|
|
if($e == $cmdres) {
|
|
# a fine error code
|
|
$errok = 1;
|
|
last;
|
|
}
|
|
}
|
|
|
|
if($errok) {
|
|
$ok .= "e";
|
|
}
|
|
else {
|
|
if(!$short) {
|
|
logmsg sprintf("\n%s returned $cmdres, when expecting %s\n",
|
|
(!$tool)?"curl":$tool, $errorcode);
|
|
}
|
|
logmsg " exit FAILED\n";
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
|
|
if($feature{"TrackMemory"}) {
|
|
if(! -f $memdump) {
|
|
my %cmdhash = getpartattr("client", "command");
|
|
my $cmdtype = $cmdhash{'type'} || "default";
|
|
logmsg "\n** ALERT! memory tracking with no output file?\n"
|
|
if(!$cmdtype eq "perl");
|
|
}
|
|
else {
|
|
my @memdata=`$memanalyze $memdump`;
|
|
my $leak=0;
|
|
for(@memdata) {
|
|
if($_ ne "") {
|
|
# well it could be other memory problems as well, but
|
|
# we call it leak for short here
|
|
$leak=1;
|
|
}
|
|
}
|
|
if($leak) {
|
|
logmsg "\n** MEMORY FAILURE\n";
|
|
logmsg @memdata;
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
else {
|
|
$ok .= "m";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$ok .= "-"; # memory not checked
|
|
}
|
|
|
|
if($valgrind) {
|
|
if(use_valgrind() && !$disablevalgrind) {
|
|
if(!opendir(DIR, "$LOGDIR")) {
|
|
logmsg "ERROR: unable to read $LOGDIR\n";
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
my @files = readdir(DIR);
|
|
closedir(DIR);
|
|
my $vgfile;
|
|
foreach my $file (@files) {
|
|
if($file =~ /^valgrind$testnum(\..*|)$/) {
|
|
$vgfile = $file;
|
|
last;
|
|
}
|
|
}
|
|
if(!$vgfile) {
|
|
logmsg "ERROR: valgrind log file missing for test $testnum\n";
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
my @e = valgrindparse("$LOGDIR/$vgfile");
|
|
if(@e && $e[0]) {
|
|
if($automakestyle) {
|
|
logmsg "FAIL: $testnum - $testname - valgrind\n";
|
|
}
|
|
else {
|
|
logmsg " valgrind ERROR ";
|
|
logmsg @e;
|
|
}
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
return -1;
|
|
}
|
|
$ok .= "v";
|
|
}
|
|
else {
|
|
if($verbose && !$disablevalgrind) {
|
|
logmsg " valgrind SKIPPED\n";
|
|
}
|
|
$ok .= "-"; # skipped
|
|
}
|
|
}
|
|
else {
|
|
$ok .= "-"; # valgrind not checked
|
|
}
|
|
# add 'E' for event-based
|
|
$ok .= $run_event_based ? "E" : "-";
|
|
|
|
logmsg "$ok " if(!$short);
|
|
|
|
# timestamp test result verification end
|
|
$timevrfyend{$testnum} = Time::HiRes::time();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Report a successful test
|
|
sub singletest_success {
|
|
my ($testnum, $count, $total, $errorreturncode)=@_;
|
|
|
|
my $sofar= time()-$start;
|
|
my $esttotal = $sofar/$count * $total;
|
|
my $estleft = $esttotal - $sofar;
|
|
my $timeleft=sprintf("remaining: %02d:%02d",
|
|
$estleft/60,
|
|
$estleft%60);
|
|
my $took = $timevrfyend{$testnum} - $timeprepini{$testnum};
|
|
my $duration = sprintf("duration: %02d:%02d",
|
|
$sofar/60, $sofar%60);
|
|
if(!$automakestyle) {
|
|
logmsg sprintf("OK (%-3d out of %-3d, %s, took %.3fs, %s)\n",
|
|
$count, $total, $timeleft, $took, $duration);
|
|
}
|
|
else {
|
|
my $testname= (getpart("client", "name"))[0];
|
|
chomp $testname;
|
|
logmsg "PASS: $testnum - $testname\n";
|
|
}
|
|
|
|
if($errorreturncode==2) {
|
|
logmsg "Warning: test$testnum result is ignored, but passed!\n";
|
|
}
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Run a single specified test case
|
|
#
|
|
sub singletest {
|
|
my ($testnum, $count, $total)=@_;
|
|
|
|
#######################################################################
|
|
# Verify that the test should be run
|
|
my ($why, $errorreturncode) = singletest_shouldrun($testnum);
|
|
|
|
|
|
#######################################################################
|
|
# Restore environment variables that were modified in a previous run.
|
|
# Test definition may instruct to (un)set environment vars.
|
|
# This is done this early so that leftover variables don't affect starting
|
|
# servers or CI registration.
|
|
restore_test_env(1);
|
|
|
|
#######################################################################
|
|
# Register the test case with the CI environment
|
|
citest_starttest($testnum);
|
|
|
|
if(!$why) {
|
|
$why = runner_test_preprocess($testnum);
|
|
} else {
|
|
|
|
# set zero servers verification time when they aren't started
|
|
$timesrvrini{$testnum} = $timesrvrend{$testnum} = Time::HiRes::time();
|
|
}
|
|
|
|
#######################################################################
|
|
# Print the test name and count tests
|
|
my $error;
|
|
$error = singletest_count($testnum, $why);
|
|
if($error || $listonly) {
|
|
return $error;
|
|
}
|
|
|
|
#######################################################################
|
|
# Execute this test number
|
|
my $cmdres;
|
|
my $CURLOUT;
|
|
my $tool;
|
|
my $disablevalgrind;
|
|
($error, $cmdres, $CURLOUT, $tool, $disablevalgrind) = runner_test_run($testnum);
|
|
if($error == -1) {
|
|
# return a test failure, either to be reported or to be ignored
|
|
return $errorreturncode;
|
|
}
|
|
elsif($error == -2) {
|
|
timestampskippedevents($testnum);
|
|
return $error;
|
|
}
|
|
elsif($error > 0) {
|
|
return $error;
|
|
}
|
|
|
|
#######################################################################
|
|
# Verify that the test succeeded
|
|
$error = singletest_check($testnum, $cmdres, $CURLOUT, $tool, $disablevalgrind);
|
|
if($error == -1) {
|
|
# return a test failure, either to be reported or to be ignored
|
|
return $errorreturncode;
|
|
}
|
|
elsif($error == -2) {
|
|
# torture test; there is no verification, so the run result holds the
|
|
# test success code
|
|
return $cmdres;
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
# Report a successful test
|
|
singletest_success($testnum, $count, $total, $errorreturncode);
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
#######################################################################
|
|
# runtimestats displays test-suite run time statistics
|
|
#
|
|
sub runtimestats {
|
|
my $lasttest = $_[0];
|
|
|
|
return if(not $timestats);
|
|
|
|
logmsg "\nTest suite total running time breakdown per task...\n\n";
|
|
|
|
my @timesrvr;
|
|
my @timeprep;
|
|
my @timetool;
|
|
my @timelock;
|
|
my @timevrfy;
|
|
my @timetest;
|
|
my $timesrvrtot = 0.0;
|
|
my $timepreptot = 0.0;
|
|
my $timetooltot = 0.0;
|
|
my $timelocktot = 0.0;
|
|
my $timevrfytot = 0.0;
|
|
my $timetesttot = 0.0;
|
|
my $counter;
|
|
|
|
for my $testnum (1 .. $lasttest) {
|
|
if($timesrvrini{$testnum}) {
|
|
$timesrvrtot += $timesrvrend{$testnum} - $timesrvrini{$testnum};
|
|
$timepreptot +=
|
|
(($timetoolini{$testnum} - $timeprepini{$testnum}) -
|
|
($timesrvrend{$testnum} - $timesrvrini{$testnum}));
|
|
$timetooltot += $timetoolend{$testnum} - $timetoolini{$testnum};
|
|
$timelocktot += $timesrvrlog{$testnum} - $timetoolend{$testnum};
|
|
$timevrfytot += $timevrfyend{$testnum} - $timesrvrlog{$testnum};
|
|
$timetesttot += $timevrfyend{$testnum} - $timeprepini{$testnum};
|
|
push @timesrvr, sprintf("%06.3f %04d",
|
|
$timesrvrend{$testnum} - $timesrvrini{$testnum}, $testnum);
|
|
push @timeprep, sprintf("%06.3f %04d",
|
|
($timetoolini{$testnum} - $timeprepini{$testnum}) -
|
|
($timesrvrend{$testnum} - $timesrvrini{$testnum}), $testnum);
|
|
push @timetool, sprintf("%06.3f %04d",
|
|
$timetoolend{$testnum} - $timetoolini{$testnum}, $testnum);
|
|
push @timelock, sprintf("%06.3f %04d",
|
|
$timesrvrlog{$testnum} - $timetoolend{$testnum}, $testnum);
|
|
push @timevrfy, sprintf("%06.3f %04d",
|
|
$timevrfyend{$testnum} - $timesrvrlog{$testnum}, $testnum);
|
|
push @timetest, sprintf("%06.3f %04d",
|
|
$timevrfyend{$testnum} - $timeprepini{$testnum}, $testnum);
|
|
}
|
|
}
|
|
|
|
{
|
|
no warnings 'numeric';
|
|
@timesrvr = sort { $b <=> $a } @timesrvr;
|
|
@timeprep = sort { $b <=> $a } @timeprep;
|
|
@timetool = sort { $b <=> $a } @timetool;
|
|
@timelock = sort { $b <=> $a } @timelock;
|
|
@timevrfy = sort { $b <=> $a } @timevrfy;
|
|
@timetest = sort { $b <=> $a } @timetest;
|
|
}
|
|
|
|
logmsg "Spent ". sprintf("%08.3f ", $timesrvrtot) .
|
|
"seconds starting and verifying test harness servers.\n";
|
|
logmsg "Spent ". sprintf("%08.3f ", $timepreptot) .
|
|
"seconds reading definitions and doing test preparations.\n";
|
|
logmsg "Spent ". sprintf("%08.3f ", $timetooltot) .
|
|
"seconds actually running test tools.\n";
|
|
logmsg "Spent ". sprintf("%08.3f ", $timelocktot) .
|
|
"seconds awaiting server logs lock removal.\n";
|
|
logmsg "Spent ". sprintf("%08.3f ", $timevrfytot) .
|
|
"seconds verifying test results.\n";
|
|
logmsg "Spent ". sprintf("%08.3f ", $timetesttot) .
|
|
"seconds doing all of the above.\n";
|
|
|
|
$counter = 25;
|
|
logmsg "\nTest server starting and verification time per test ".
|
|
sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
|
|
logmsg "-time- test\n";
|
|
logmsg "------ ----\n";
|
|
foreach my $txt (@timesrvr) {
|
|
last if((not $fullstats) && (not $counter--));
|
|
logmsg "$txt\n";
|
|
}
|
|
|
|
$counter = 10;
|
|
logmsg "\nTest definition reading and preparation time per test ".
|
|
sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
|
|
logmsg "-time- test\n";
|
|
logmsg "------ ----\n";
|
|
foreach my $txt (@timeprep) {
|
|
last if((not $fullstats) && (not $counter--));
|
|
logmsg "$txt\n";
|
|
}
|
|
|
|
$counter = 25;
|
|
logmsg "\nTest tool execution time per test ".
|
|
sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
|
|
logmsg "-time- test\n";
|
|
logmsg "------ ----\n";
|
|
foreach my $txt (@timetool) {
|
|
last if((not $fullstats) && (not $counter--));
|
|
logmsg "$txt\n";
|
|
}
|
|
|
|
$counter = 15;
|
|
logmsg "\nTest server logs lock removal time per test ".
|
|
sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
|
|
logmsg "-time- test\n";
|
|
logmsg "------ ----\n";
|
|
foreach my $txt (@timelock) {
|
|
last if((not $fullstats) && (not $counter--));
|
|
logmsg "$txt\n";
|
|
}
|
|
|
|
$counter = 10;
|
|
logmsg "\nTest results verification time per test ".
|
|
sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
|
|
logmsg "-time- test\n";
|
|
logmsg "------ ----\n";
|
|
foreach my $txt (@timevrfy) {
|
|
last if((not $fullstats) && (not $counter--));
|
|
logmsg "$txt\n";
|
|
}
|
|
|
|
$counter = 50;
|
|
logmsg "\nTotal time per test ".
|
|
sprintf("(%s)...\n\n", (not $fullstats)?"top $counter":"full");
|
|
logmsg "-time- test\n";
|
|
logmsg "------ ----\n";
|
|
foreach my $txt (@timetest) {
|
|
last if((not $fullstats) && (not $counter--));
|
|
logmsg "$txt\n";
|
|
}
|
|
|
|
logmsg "\n";
|
|
}
|
|
|
|
#######################################################################
|
|
# Check options to this test program
|
|
#
|
|
|
|
# Special case for CMake: replace '$TFLAGS' by the contents of the
|
|
# environment variable (if any).
|
|
if(@ARGV && $ARGV[-1] eq '$TFLAGS') {
|
|
pop @ARGV;
|
|
push(@ARGV, split(' ', $ENV{'TFLAGS'})) if defined($ENV{'TFLAGS'});
|
|
}
|
|
|
|
$valgrind = checktestcmd("valgrind");
|
|
my $number=0;
|
|
my $fromnum=-1;
|
|
my @testthis;
|
|
while(@ARGV) {
|
|
if ($ARGV[0] eq "-v") {
|
|
# verbose output
|
|
$verbose=1;
|
|
}
|
|
elsif ($ARGV[0] eq "-c") {
|
|
# use this path to curl instead of default
|
|
$DBGCURL=$CURL="\"$ARGV[1]\"";
|
|
shift @ARGV;
|
|
}
|
|
elsif ($ARGV[0] eq "-vc") {
|
|
# use this path to a curl used to verify servers
|
|
|
|
# Particularly useful when you introduce a crashing bug somewhere in
|
|
# the development version as then it won't be able to run any tests
|
|
# since it can't verify the servers!
|
|
|
|
$VCURL="\"$ARGV[1]\"";
|
|
shift @ARGV;
|
|
}
|
|
elsif ($ARGV[0] eq "-ac") {
|
|
# use this curl only to talk to APIs (currently only CI test APIs)
|
|
$ACURL="\"$ARGV[1]\"";
|
|
shift @ARGV;
|
|
}
|
|
elsif ($ARGV[0] eq "-d") {
|
|
# have the servers display protocol output
|
|
$debugprotocol=1;
|
|
}
|
|
elsif($ARGV[0] eq "-e") {
|
|
# run the tests cases event based if possible
|
|
$run_event_based=1;
|
|
}
|
|
elsif($ARGV[0] eq "-f") {
|
|
# force - run the test case even if listed in DISABLED
|
|
$run_disabled=1;
|
|
}
|
|
elsif($ARGV[0] eq "-E") {
|
|
# load additional reasons to skip tests
|
|
shift @ARGV;
|
|
my $exclude_file = $ARGV[0];
|
|
open(my $fd, "<", $exclude_file) or die "Couldn't open '$exclude_file': $!";
|
|
while(my $line = <$fd>) {
|
|
next if ($line =~ /^#/);
|
|
chomp $line;
|
|
my ($type, $patterns, $skip_reason) = split(/\s*:\s*/, $line, 3);
|
|
|
|
die "Unsupported type: $type\n" if($type !~ /^keyword|test|tool$/);
|
|
|
|
foreach my $pattern (split(/,/, $patterns)) {
|
|
if($type eq "test") {
|
|
# Strip leading zeros in the test number
|
|
$pattern = int($pattern);
|
|
}
|
|
$custom_skip_reasons{$type}{$pattern} = $skip_reason;
|
|
}
|
|
}
|
|
close($fd);
|
|
}
|
|
elsif ($ARGV[0] eq "-g") {
|
|
# run this test with gdb
|
|
$gdbthis=1;
|
|
}
|
|
elsif ($ARGV[0] eq "-gw") {
|
|
# run this test with windowed gdb
|
|
$gdbthis=1;
|
|
$gdbxwin=1;
|
|
}
|
|
elsif($ARGV[0] eq "-s") {
|
|
# short output
|
|
$short=1;
|
|
}
|
|
elsif($ARGV[0] eq "-am") {
|
|
# automake-style output
|
|
$short=1;
|
|
$automakestyle=1;
|
|
}
|
|
elsif($ARGV[0] eq "-n") {
|
|
# no valgrind
|
|
undef $valgrind;
|
|
}
|
|
elsif($ARGV[0] eq "--no-debuginfod") {
|
|
# disable the valgrind debuginfod functionality
|
|
$no_debuginfod = 1;
|
|
}
|
|
elsif ($ARGV[0] eq "-R") {
|
|
# execute in scrambled order
|
|
$scrambleorder=1;
|
|
}
|
|
elsif($ARGV[0] =~ /^-t(.*)/) {
|
|
# torture
|
|
$torture=1;
|
|
my $xtra = $1;
|
|
|
|
if($xtra =~ s/(\d+)$//) {
|
|
$tortalloc = $1;
|
|
}
|
|
}
|
|
elsif($ARGV[0] =~ /--shallow=(\d+)/) {
|
|
# Fail no more than this amount per tests when running
|
|
# torture.
|
|
my ($num)=($1);
|
|
$shallow=$num;
|
|
}
|
|
elsif($ARGV[0] =~ /--repeat=(\d+)/) {
|
|
# Repeat-run the given tests this many times
|
|
$repeat = $1;
|
|
}
|
|
elsif($ARGV[0] =~ /--seed=(\d+)/) {
|
|
# Set a fixed random seed (used for -R and --shallow)
|
|
$randseed = $1;
|
|
}
|
|
elsif($ARGV[0] eq "-a") {
|
|
# continue anyway, even if a test fail
|
|
$anyway=1;
|
|
}
|
|
elsif($ARGV[0] eq "-o") {
|
|
shift @ARGV;
|
|
if ($ARGV[0] =~ /^(\w+)=([\w.:\/\[\]-]+)$/) {
|
|
my ($variable, $value) = ($1, $2);
|
|
eval "\$$variable='$value'" or die "Failed to set \$$variable to $value: $@";
|
|
} else {
|
|
die "Failed to parse '-o $ARGV[0]'. May contain unexpected characters.\n";
|
|
}
|
|
}
|
|
elsif($ARGV[0] eq "-p") {
|
|
$postmortem=1;
|
|
}
|
|
elsif($ARGV[0] eq "-P") {
|
|
shift @ARGV;
|
|
$proxy_address=$ARGV[0];
|
|
}
|
|
elsif($ARGV[0] eq "-L") {
|
|
# require additional library file
|
|
shift @ARGV;
|
|
require $ARGV[0];
|
|
}
|
|
elsif($ARGV[0] eq "-l") {
|
|
# lists the test case names only
|
|
$listonly=1;
|
|
}
|
|
elsif($ARGV[0] eq "-k") {
|
|
# keep stdout and stderr files after tests
|
|
$keepoutfiles=1;
|
|
}
|
|
elsif($ARGV[0] eq "-r") {
|
|
# run time statistics needs Time::HiRes
|
|
if($Time::HiRes::VERSION) {
|
|
keys(%timeprepini) = 1000;
|
|
keys(%timesrvrini) = 1000;
|
|
keys(%timesrvrend) = 1000;
|
|
keys(%timetoolini) = 1000;
|
|
keys(%timetoolend) = 1000;
|
|
keys(%timesrvrlog) = 1000;
|
|
keys(%timevrfyend) = 1000;
|
|
$timestats=1;
|
|
$fullstats=0;
|
|
}
|
|
}
|
|
elsif($ARGV[0] eq "-rf") {
|
|
# run time statistics needs Time::HiRes
|
|
if($Time::HiRes::VERSION) {
|
|
keys(%timeprepini) = 1000;
|
|
keys(%timesrvrini) = 1000;
|
|
keys(%timesrvrend) = 1000;
|
|
keys(%timetoolini) = 1000;
|
|
keys(%timetoolend) = 1000;
|
|
keys(%timesrvrlog) = 1000;
|
|
keys(%timevrfyend) = 1000;
|
|
$timestats=1;
|
|
$fullstats=1;
|
|
}
|
|
}
|
|
elsif($ARGV[0] eq "-rm") {
|
|
# force removal of files by killing locking processes
|
|
$clearlocks=1;
|
|
}
|
|
elsif($ARGV[0] eq "-u") {
|
|
# error instead of warning on server unexpectedly alive
|
|
$err_unexpected=1;
|
|
}
|
|
elsif(($ARGV[0] eq "-h") || ($ARGV[0] eq "--help")) {
|
|
# show help text
|
|
print <<"EOHELP"
|
|
Usage: runtests.pl [options] [test selection(s)]
|
|
-a continue even if a test fails
|
|
-ac path use this curl only to talk to APIs (currently only CI test APIs)
|
|
-am automake style output PASS/FAIL: [number] [name]
|
|
-c path use this curl executable
|
|
-d display server debug info
|
|
-e event-based execution
|
|
-E file load the specified file to exclude certain tests
|
|
-f forcibly run even if disabled
|
|
-g run the test case with gdb
|
|
-gw run the test case with gdb as a windowed application
|
|
-h this help text
|
|
-k keep stdout and stderr files present after tests
|
|
-L path require an additional perl library file to replace certain functions
|
|
-l list all test case names/descriptions
|
|
-n no valgrind
|
|
--no-debuginfod disable the valgrind debuginfod functionality
|
|
-o variable=value set internal variable to the specified value
|
|
-P proxy use the specified proxy
|
|
-p print log file contents when a test fails
|
|
-R scrambled order (uses the random seed, see --seed)
|
|
-r run time statistics
|
|
-rf full run time statistics
|
|
-rm force removal of files by killing locking processes (Windows only)
|
|
--repeat=[num] run the given tests this many times
|
|
-s short output
|
|
--seed=[num] set the random seed to a fixed number
|
|
--shallow=[num] randomly makes the torture tests "thinner"
|
|
-t[N] torture (simulate function failures); N means fail Nth function
|
|
-u error instead of warning on server unexpectedly alive
|
|
-v verbose output
|
|
-vc path use this curl only to verify the existing servers
|
|
[num] like "5 6 9" or " 5 to 22 " to run those tests only
|
|
[!num] like "!5 !6 !9" to disable those tests
|
|
[~num] like "~5 ~6 ~9" to ignore the result of those tests
|
|
[keyword] like "IPv6" to select only tests containing the key word
|
|
[!keyword] like "!cookies" to disable any tests containing the key word
|
|
[~keyword] like "~cookies" to ignore results of tests containing key word
|
|
EOHELP
|
|
;
|
|
exit;
|
|
}
|
|
elsif($ARGV[0] =~ /^(\d+)/) {
|
|
$number = $1;
|
|
if($fromnum >= 0) {
|
|
for my $n ($fromnum .. $number) {
|
|
push @testthis, $n;
|
|
}
|
|
$fromnum = -1;
|
|
}
|
|
else {
|
|
push @testthis, $1;
|
|
}
|
|
}
|
|
elsif($ARGV[0] =~ /^to$/i) {
|
|
$fromnum = $number+1;
|
|
}
|
|
elsif($ARGV[0] =~ /^!(\d+)/) {
|
|
$fromnum = -1;
|
|
$disabled{$1}=$1;
|
|
}
|
|
elsif($ARGV[0] =~ /^~(\d+)/) {
|
|
$fromnum = -1;
|
|
$ignored{$1}=$1;
|
|
}
|
|
elsif($ARGV[0] =~ /^!(.+)/) {
|
|
$disabled_keywords{lc($1)}=$1;
|
|
}
|
|
elsif($ARGV[0] =~ /^~(.+)/) {
|
|
$ignored_keywords{lc($1)}=$1;
|
|
}
|
|
elsif($ARGV[0] =~ /^([-[{a-zA-Z].*)/) {
|
|
$enabled_keywords{lc($1)}=$1;
|
|
}
|
|
else {
|
|
print "Unknown option: $ARGV[0]\n";
|
|
exit;
|
|
}
|
|
shift @ARGV;
|
|
}
|
|
|
|
delete $ENV{'DEBUGINFOD_URLS'} if($ENV{'DEBUGINFOD_URLS'} && $no_debuginfod);
|
|
|
|
if(!$randseed) {
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
|
|
localtime(time);
|
|
# seed of the month. December 2019 becomes 201912
|
|
$randseed = ($year+1900)*100 + $mon+1;
|
|
open(my $curlvh, "-|", "$CURL --version 2>/dev/null") ||
|
|
die "could not get curl version!";
|
|
my @c = <$curlvh>;
|
|
close($curlvh) || die "could not get curl version!";
|
|
# use the first line of output and get the md5 out of it
|
|
my $str = md5($c[0]);
|
|
$randseed += unpack('S', $str); # unsigned 16 bit value
|
|
}
|
|
srand $randseed;
|
|
|
|
if(@testthis && ($testthis[0] ne "")) {
|
|
$TESTCASES=join(" ", @testthis);
|
|
}
|
|
|
|
if($valgrind) {
|
|
# we have found valgrind on the host, use it
|
|
|
|
# verify that we can invoke it fine
|
|
my $code = runclient("valgrind >/dev/null 2>&1");
|
|
|
|
if(($code>>8) != 1) {
|
|
#logmsg "Valgrind failure, disable it\n";
|
|
undef $valgrind;
|
|
} else {
|
|
|
|
# since valgrind 2.1.x, '--tool' option is mandatory
|
|
# use it, if it is supported by the version installed on the system
|
|
# (this happened in 2003, so we could probably don't need to care about
|
|
# that old version any longer and just delete this check)
|
|
runclient("valgrind --help 2>&1 | grep -- --tool > /dev/null 2>&1");
|
|
if (($? >> 8)) {
|
|
$valgrind_tool="";
|
|
}
|
|
open(my $curlh, "<", "$CURL");
|
|
my $l = <$curlh>;
|
|
if($l =~ /^\#\!/) {
|
|
# A shell script. This is typically when built with libtool,
|
|
$valgrind="../libtool --mode=execute $valgrind";
|
|
}
|
|
close($curlh);
|
|
|
|
# valgrind 3 renamed the --logfile option to --log-file!!!
|
|
# (this happened in 2005, so we could probably don't need to care about
|
|
# that old version any longer and just delete this check)
|
|
my $ver=join(' ', runclientoutput("valgrind --version"));
|
|
# cut off all but digits and dots
|
|
$ver =~ s/[^0-9.]//g;
|
|
|
|
if($ver =~ /^(\d+)/) {
|
|
$ver = $1;
|
|
if($ver < 3) {
|
|
$valgrind_logfile="--logfile";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($gdbthis) {
|
|
# open the executable curl and read the first 4 bytes of it
|
|
open(my $check, "<", "$CURL");
|
|
my $c;
|
|
sysread $check, $c, 4;
|
|
close($check);
|
|
if($c eq "#! /") {
|
|
# A shell script. This is typically when built with libtool,
|
|
$libtool = 1;
|
|
$gdb = "../libtool --mode=execute gdb";
|
|
}
|
|
}
|
|
|
|
#######################################################################
|
|
# clear and create logging directory:
|
|
#
|
|
|
|
cleardir($LOGDIR);
|
|
mkdir($LOGDIR, 0777);
|
|
mkdir($PIDDIR, 0777);
|
|
|
|
#######################################################################
|
|
# initialize some variables
|
|
#
|
|
|
|
get_disttests();
|
|
|
|
#######################################################################
|
|
# Output curl version and host info being tested
|
|
#
|
|
|
|
if(!$listonly) {
|
|
unlink($memdump); # remove this if there was one left
|
|
checksystemfeatures();
|
|
}
|
|
|
|
#######################################################################
|
|
# initialize configuration needed to set up servers
|
|
#
|
|
initserverconfig();
|
|
|
|
if(!$listonly) {
|
|
# these can only be displayed after initserverconfig() has been called
|
|
displayserverfeatures();
|
|
|
|
# globally disabled tests
|
|
disabledtests("$TESTDIR/DISABLED");
|
|
}
|
|
|
|
#######################################################################
|
|
# Fetch all disabled tests, if there are any
|
|
#
|
|
|
|
sub disabledtests {
|
|
my ($file) = @_;
|
|
my @input;
|
|
|
|
if(open(my $disabledh, "<", "$file")) {
|
|
while(<$disabledh>) {
|
|
if(/^ *\#/) {
|
|
# allow comments
|
|
next;
|
|
}
|
|
push @input, $_;
|
|
}
|
|
close($disabledh);
|
|
|
|
# preprocess the input to make conditionally disabled tests depending
|
|
# on variables
|
|
my @pp = prepro(0, @input);
|
|
for my $t (@pp) {
|
|
if($t =~ /(\d+)/) {
|
|
my ($n) = $1;
|
|
$disabled{$n}=$n; # disable this test number
|
|
if(! -f "$srcdir/data/test$n") {
|
|
print STDERR "WARNING! Non-existing test $n in $file!\n";
|
|
# fail hard to make user notice
|
|
exit 1;
|
|
}
|
|
logmsg "DISABLED: test $n\n" if ($verbose);
|
|
}
|
|
else {
|
|
print STDERR "$file: rubbish content: $t\n";
|
|
exit 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#######################################################################
|
|
# If 'all' tests are requested, find out all test numbers
|
|
#
|
|
|
|
if ( $TESTCASES eq "all") {
|
|
# Get all commands and find out their test numbers
|
|
opendir(DIR, $TESTDIR) || die "can't opendir $TESTDIR: $!";
|
|
my @cmds = grep { /^test([0-9]+)$/ && -f "$TESTDIR/$_" } readdir(DIR);
|
|
closedir(DIR);
|
|
|
|
$TESTCASES=""; # start with no test cases
|
|
|
|
# cut off everything but the digits
|
|
for(@cmds) {
|
|
$_ =~ s/[a-z\/\.]*//g;
|
|
}
|
|
# sort the numbers from low to high
|
|
foreach my $n (sort { $a <=> $b } @cmds) {
|
|
if($disabled{$n}) {
|
|
# skip disabled test cases
|
|
my $why = "configured as DISABLED";
|
|
$skipped{$why}++;
|
|
$teststat[$n]=$why; # store reason for this test case
|
|
next;
|
|
}
|
|
$TESTCASES .= " $n";
|
|
}
|
|
}
|
|
else {
|
|
my $verified="";
|
|
for(split(" ", $TESTCASES)) {
|
|
if (-e "$TESTDIR/test$_") {
|
|
$verified.="$_ ";
|
|
}
|
|
}
|
|
if($verified eq "") {
|
|
print "No existing test cases were specified\n";
|
|
exit;
|
|
}
|
|
$TESTCASES = $verified;
|
|
}
|
|
if($repeat) {
|
|
my $s;
|
|
for(1 .. $repeat) {
|
|
$s .= $TESTCASES;
|
|
}
|
|
$TESTCASES = $s;
|
|
}
|
|
|
|
if($scrambleorder) {
|
|
# scramble the order of the test cases
|
|
my @rand;
|
|
while($TESTCASES) {
|
|
my @all = split(/ +/, $TESTCASES);
|
|
if(!$all[0]) {
|
|
# if the first is blank, shift away it
|
|
shift @all;
|
|
}
|
|
my $r = rand @all;
|
|
push @rand, $all[$r];
|
|
$all[$r]="";
|
|
$TESTCASES = join(" ", @all);
|
|
}
|
|
$TESTCASES = join(" ", @rand);
|
|
}
|
|
|
|
# Display the contents of the given file. Line endings are canonicalized
|
|
# and excessively long files are elided
|
|
sub displaylogcontent {
|
|
my ($file)=@_;
|
|
if(open(my $single, "<", "$file")) {
|
|
my $linecount = 0;
|
|
my $truncate;
|
|
my @tail;
|
|
while(my $string = <$single>) {
|
|
$string =~ s/\r\n/\n/g;
|
|
$string =~ s/[\r\f\032]/\n/g;
|
|
$string .= "\n" unless ($string =~ /\n$/);
|
|
$string =~ tr/\n//;
|
|
for my $line (split(m/\n/, $string)) {
|
|
$line =~ s/\s*\!$//;
|
|
if ($truncate) {
|
|
push @tail, " $line\n";
|
|
} else {
|
|
logmsg " $line\n";
|
|
}
|
|
$linecount++;
|
|
$truncate = $linecount > 1000;
|
|
}
|
|
}
|
|
close($single);
|
|
if(@tail) {
|
|
my $tailshow = 200;
|
|
my $tailskip = 0;
|
|
my $tailtotal = scalar @tail;
|
|
if($tailtotal > $tailshow) {
|
|
$tailskip = $tailtotal - $tailshow;
|
|
logmsg "=== File too long: $tailskip lines omitted here\n";
|
|
}
|
|
for($tailskip .. $tailtotal-1) {
|
|
logmsg "$tail[$_]";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub displaylogs {
|
|
my ($testnum)=@_;
|
|
opendir(DIR, "$LOGDIR") ||
|
|
die "can't open dir: $!";
|
|
my @logs = readdir(DIR);
|
|
closedir(DIR);
|
|
|
|
logmsg "== Contents of files in the $LOGDIR/ dir after test $testnum\n";
|
|
foreach my $log (sort @logs) {
|
|
if($log =~ /\.(\.|)$/) {
|
|
next; # skip "." and ".."
|
|
}
|
|
if($log =~ /^\.nfs/) {
|
|
next; # skip ".nfs"
|
|
}
|
|
if(($log eq "memdump") || ($log eq "core")) {
|
|
next; # skip "memdump" and "core"
|
|
}
|
|
if((-d "$LOGDIR/$log") || (! -s "$LOGDIR/$log")) {
|
|
next; # skip directory and empty files
|
|
}
|
|
if(($log =~ /^stdout\d+/) && ($log !~ /^stdout$testnum/)) {
|
|
next; # skip stdoutNnn of other tests
|
|
}
|
|
if(($log =~ /^stderr\d+/) && ($log !~ /^stderr$testnum/)) {
|
|
next; # skip stderrNnn of other tests
|
|
}
|
|
if(($log =~ /^upload\d+/) && ($log !~ /^upload$testnum/)) {
|
|
next; # skip uploadNnn of other tests
|
|
}
|
|
if(($log =~ /^curl\d+\.out/) && ($log !~ /^curl$testnum\.out/)) {
|
|
next; # skip curlNnn.out of other tests
|
|
}
|
|
if(($log =~ /^test\d+\.txt/) && ($log !~ /^test$testnum\.txt/)) {
|
|
next; # skip testNnn.txt of other tests
|
|
}
|
|
if(($log =~ /^file\d+\.txt/) && ($log !~ /^file$testnum\.txt/)) {
|
|
next; # skip fileNnn.txt of other tests
|
|
}
|
|
if(($log =~ /^netrc\d+/) && ($log !~ /^netrc$testnum/)) {
|
|
next; # skip netrcNnn of other tests
|
|
}
|
|
if(($log =~ /^trace\d+/) && ($log !~ /^trace$testnum/)) {
|
|
next; # skip traceNnn of other tests
|
|
}
|
|
if(($log =~ /^valgrind\d+/) && ($log !~ /^valgrind$testnum(?:\..*)?$/)) {
|
|
next; # skip valgrindNnn of other tests
|
|
}
|
|
if(($log =~ /^test$testnum$/)) {
|
|
next; # skip test$testnum since it can be very big
|
|
}
|
|
logmsg "=== Start of file $log\n";
|
|
displaylogcontent("$LOGDIR/$log");
|
|
logmsg "=== End of file $log\n";
|
|
}
|
|
}
|
|
|
|
#######################################################################
|
|
# Setup CI Test Run
|
|
citest_starttestrun();
|
|
|
|
#######################################################################
|
|
# The main test-loop
|
|
#
|
|
|
|
my $failed;
|
|
my $failedign;
|
|
my $ok=0;
|
|
my $ign=0;
|
|
my $total=0;
|
|
my $lasttest=0;
|
|
my @at = split(" ", $TESTCASES);
|
|
my $count=0;
|
|
|
|
$start = time();
|
|
|
|
foreach my $testnum (@at) {
|
|
|
|
$lasttest = $testnum if($testnum > $lasttest);
|
|
$count++;
|
|
|
|
# execute one test case
|
|
my $error = singletest($testnum, $count, scalar(@at));
|
|
|
|
# Submit the test case result with the CI environment
|
|
citest_finishtest($testnum, $error);
|
|
|
|
if($error < 0) {
|
|
# not a test we can run
|
|
next;
|
|
}
|
|
|
|
$total++; # number of tests we've run
|
|
|
|
if($error>0) {
|
|
if($error==2) {
|
|
# ignored test failures
|
|
$failedign .= "$testnum ";
|
|
}
|
|
else {
|
|
$failed.= "$testnum ";
|
|
}
|
|
if($postmortem) {
|
|
# display all files in $LOGDIR/ in a nice way
|
|
displaylogs($testnum);
|
|
}
|
|
if($error==2) {
|
|
$ign++; # ignored test result counter
|
|
}
|
|
elsif(!$anyway) {
|
|
# a test failed, abort
|
|
logmsg "\n - abort tests\n";
|
|
last;
|
|
}
|
|
}
|
|
elsif(!$error) {
|
|
$ok++; # successful test counter
|
|
}
|
|
|
|
# loop for next test
|
|
}
|
|
|
|
my $sofar = time() - $start;
|
|
|
|
#######################################################################
|
|
# Finish CI Test Run
|
|
citest_finishtestrun();
|
|
|
|
# Tests done, stop the servers
|
|
my $unexpected = stopservers($verbose);
|
|
|
|
my $numskipped = %skipped ? sum values %skipped : 0;
|
|
my $all = $total + $numskipped;
|
|
|
|
runtimestats($lasttest);
|
|
|
|
if($all) {
|
|
logmsg "TESTDONE: $all tests were considered during ".
|
|
sprintf("%.0f", $sofar) ." seconds.\n";
|
|
}
|
|
|
|
if(%skipped && !$short) {
|
|
my $s=0;
|
|
# Temporary hash to print the restraints sorted by the number
|
|
# of their occurrences
|
|
my %restraints;
|
|
logmsg "TESTINFO: $numskipped tests were skipped due to these restraints:\n";
|
|
|
|
for(keys %skipped) {
|
|
my $r = $_;
|
|
my $skip_count = $skipped{$r};
|
|
my $log_line = sprintf("TESTINFO: \"%s\" %d time%s (", $r, $skip_count,
|
|
($skip_count == 1) ? "" : "s");
|
|
|
|
# now gather all test case numbers that had this reason for being
|
|
# skipped
|
|
my $c=0;
|
|
my $max = 9;
|
|
for(0 .. scalar @teststat) {
|
|
my $t = $_;
|
|
if($teststat[$t] && ($teststat[$t] eq $r)) {
|
|
if($c < $max) {
|
|
$log_line .= ", " if($c);
|
|
$log_line .= $t;
|
|
}
|
|
$c++;
|
|
}
|
|
}
|
|
if($c > $max) {
|
|
$log_line .= " and ".($c-$max)." more";
|
|
}
|
|
$log_line .= ")\n";
|
|
$restraints{$log_line} = $skip_count;
|
|
}
|
|
foreach my $log_line (sort {$restraints{$b} <=> $restraints{$a}} keys %restraints) {
|
|
logmsg $log_line;
|
|
}
|
|
}
|
|
|
|
if($total) {
|
|
if($failedign) {
|
|
logmsg "IGNORED: failed tests: $failedign\n";
|
|
}
|
|
logmsg sprintf("TESTDONE: $ok tests out of $total reported OK: %d%%\n",
|
|
$ok/$total*100);
|
|
|
|
if($failed && ($ok != $total)) {
|
|
logmsg "\nTESTFAIL: These test cases failed: $failed\n\n";
|
|
}
|
|
}
|
|
else {
|
|
logmsg "\nTESTFAIL: No tests were performed\n\n";
|
|
if(scalar(keys %enabled_keywords)) {
|
|
logmsg "TESTFAIL: Nothing matched these keywords: ";
|
|
for(keys %enabled_keywords) {
|
|
logmsg "$_ ";
|
|
}
|
|
logmsg "\n";
|
|
}
|
|
}
|
|
|
|
if(($total && (($ok+$ign) != $total)) || !$total || $unexpected) {
|
|
exit 1;
|
|
}
|