curl-curl/.github/scripts/randcurl.pl
Daniel Stenberg 0042f11d6e
GHA: run random curl command lines for N seconds in CI
In the memory and address sanitizer builds.

Verify that nothing unexpected happens.

Starting out with 60 second runs.

The script does not set any seed so it runs with a new random every
time, meaning that if it fails in a single CI run it might not fail in a
subsequent one: but it should still show the full command that failed to
enable us to reproduce it locally. We can work on improving the seed
situation later if this script turns useful.

Closes #16884
2025-04-01 11:31:48 +02:00

241 lines
5.1 KiB
Perl
Executable File

#!/usr/bin/env perl
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# SPDX-License-Identifier: curl
#
# Input: number of seconds to run.
#
# 1. Figure out all existing command line options
# 2. Generate random command line using supported options
# 3. Run the command line
# 4. Verify that it does not return an unexpected return code
# 5. Iterate until the time runs out
#
# Do the same with regular command lines as well as reading the options from a
# -K config file
#
# BEWARE: this may create a large amount of files using random names in the
# directory where it runs.
#
my $curl = "../src/curl";
my $url = "localhost:7777"; # not listening to this
my $seconds = $ARGV[0];
if($ARGV[1]) {
$curl = $ARGV[1];
}
if(!$seconds) {
$seconds = 10;
}
print "Run $curl for $seconds seconds\n";
my $counter = 0xabcdef + time();
sub getnum {
my ($max) = @_;
return int(rand($max));
}
sub storedata {
my ($short, $long, $arg) = @_;
push @opt, "-$short" if($short);
push @opt, "--$long";
if($arg =~ /^</) {
# these take an argument
$arg{"-$short"} = $arg if($short);
$arg{"--$long"} = $arg;
}
}
sub getoptions {
my @all = `$curl --help all`;
for my $o (@all) {
chomp $o;
if($o =~ /^ -(.), --([^ ]*) (.*)/) {
storedata($1, $2, $3);
}
elsif($o =~ /^ --([^ ]*) (.*)/) {
storedata("", $1, $2);
}
}
}
# this adds a fake randomly generated command line option
sub addarg {
my $nice = "abcdefhijklmnopqrstuvwqxyz".
"ABCDEFHIJKLMNOPQRSTUVWQXYZ".
"0123456789-";
my $len = getnum(20) + 2;
my $o;
for (1 .. $len) {
$o .= substr($nice, getnum(length($nice)), 1);
}
return "--$o";
}
sub randarg {
my $nice = "abcdefhijklmnopqrstuvwqxyz".
"ABCDEFHIJKLMNOPQRSTUVWQXYZ".
"0123456789".
",-?#$%!@ ";
my $len = getnum(20);
my $o;
for (1 .. $len) {
$o .= substr($nice, getnum(length($nice)), 1);
}
return "\'$o\'";
}
getoptions();
my $nopts = scalar(@opt);
my %useropt = (
'-U' => 1,
'-u' => 1,
'--user' => 1,
'--proxy-user' => 1);
my %commonrc = (
'0' => 1,
'1' => 1,
'2' => 1,
'26' => 1,
);
sub runone {
my $a;
my $nargs = getnum(60) + 1;
$totalargs += $nargs;
$totalcmds++;
for (1 .. $nargs) {
my $o = getnum($nopts);
my $option = $opt[$o];
my $ar = "";
$uniq{$option}++;
if($arg{$option}) {
$ar = " ".randarg();
if($useropt{$option}) {
# append password to avoid prompting
$ar .= ":".randarg();
}
}
$a .= sprintf(" %s%s", $option, $ar);
}
if(getnum(100) < 15) {
# add a fake arg
$a .= " ".addarg();
}
my $cmd="$curl$a $url";
my $rc = system("$cmd >curl-output 2>&1 </dev/null -M 0.1") >> 8;
#my $rc = system("valgrind -q $cmd >/dev/null 2>&1 </dev/null -M 0.1") >> 8;
$allrc{$rc}++;
#print "CMD: $cmd\n";
if(!$commonrc{$rc}) {
print "CMD: $cmd\n";
print "RC: $rc\n";
print "== curl-output == \n";
open(D, "<curl-output");
my @out = <D>;
print @out;
close(D);
exit;
}
}
sub runconfig {
my $a;
my $nargs = getnum(80) + 1;
open(C, ">config");
$totalargs += $nargs;
$totalcmds++;
for (1 .. $nargs) {
my $o = getnum($nopts);
my $option = $opt[$o];
my $ar = "";
$uniq{$option}++;
if($arg{$option}) {
$ar = " ".randarg();
if($useropt{$option}) {
# append password
$ar .= ":".randarg();
}
}
$a .= sprintf("\n%s%s", $option, $ar);
}
if(getnum(100) < 15) {
# add a fake arg
$a .= "\n".addarg();
}
print C "$a\n";
close(C);
my $cmd="$curl -K config $url";
my $rc = system("$cmd >curl-output 2>&1 </dev/null -M 0.1") >> 8;
$allrc{$rc}++;
if(!$commonrc{$rc}) {
print "CMD: $cmd\n";
print "RC: $rc\n";
print "== config == \n";
open(D, "<config");
my @all = <D>;
print @all;
close(D);
print "\n== curl-output == \n";
open(D, "<curl-output");
my @out = <D>;
print @out;
close(D);
exit 2;
}
}
# run curl command lines using -K
my $end = time() + $seconds/2;
my $c = 0;
print "Running command lines\n";
do {
runconfig();
$c++;
} while(time() <= $end);
print "$c command lines\n";
# run curl command lines
$end = time() + $seconds/2;
$c = 0;
print "Running config lines\n";
do {
runone();
$c++;
} while(time() <= $end);
print "$c config line uses\n";
print "Recorded exit codes:\n";
for my $rc (keys %allrc) {
printf " %2d: %d times\n", $rc, $allrc{$rc};
}
printf "Number or command lines tested:\n".
" $totalcmds (%.1f/second)\n", $totalcmds/$seconds;
printf "Number or command line options tested:\n".
" $totalargs (average %.1f per command line)\n",
$totalargs/$totalcmds;
printf "Number or different options tested:\n".
" %u out of %u\n", scalar(keys %uniq), $nopts;