From: Daniel Stenberg Date: Mon, 31 Mar 2025 11:49:18 +0000 (+0200) Subject: GHA: run random curl command lines for N seconds in CI X-Git-Tag: curl-8_13_0~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0042f11d6e45261a05492a89de9338767b861ed6;p=thirdparty%2Fcurl.git 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 --- diff --git a/.github/scripts/randcurl.pl b/.github/scripts/randcurl.pl new file mode 100755 index 0000000000..84804d6960 --- /dev/null +++ b/.github/scripts/randcurl.pl @@ -0,0 +1,240 @@ +#!/usr/bin/env perl +# Copyright (C) Daniel Stenberg, , 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 =~ /^ 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 > 8; + #my $rc = system("valgrind -q $cmd >/dev/null 2>&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, "; + 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 > 8; + + $allrc{$rc}++; + + if(!$commonrc{$rc}) { + print "CMD: $cmd\n"; + print "RC: $rc\n"; + print "== config == \n"; + open(D, "; + print @all; + close(D); + print "\n== curl-output == \n"; + open(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; diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b123904420..a4828e30b2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -228,7 +228,7 @@ jobs: - name: address-sanitizer install_packages: zlib1g-dev libssh2-1-dev clang libssl-dev libubsan1 libasan8 libtsan2 - install_steps: pytest + install_steps: pytest randcurl configure: >- CC=clang CFLAGS="-fsanitize=address,undefined,signed-integer-overflow -fno-sanitize-recover=undefined,integer -Wformat -Werror=format-security -Werror=array-bounds -g" @@ -247,6 +247,7 @@ jobs: - name: memory-sanitizer install_packages: clang + install_steps: randcurl configure: >- CC=clang CFLAGS="-fsanitize=memory -Wformat -Werror=format-security -Werror=array-bounds -g" @@ -697,6 +698,13 @@ jobs: make -C bld V=1 pytest-ci fi + - name: 'randcurl' + if: ${{ contains(matrix.build.install_steps, 'randcurl') }} + run: | + mkdir run + cd run + ../.github/scripts/randcurl.pl 60 ../bld/src/curl + - name: 'build examples' if: ${{ matrix.build.make-custom-target != 'tidy' }} run: |