From 49e15305bf2a4df0a5b99e6c440ff3338472a4c0 Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Thu, 12 Mar 2026 11:18:49 -0400 Subject: [PATCH 1/3] Add GitHub Actions workflow for continuous fuzz testing in CI - Add .github/workflows/fuzz.yml: runs each Fuzz* target for 30s per push/PR across head, cat, wc, tail, and grep packages; caches corpus between runs; skips gracefully when no fuzz targets exist yet in a package. - Update .github/workflows/test.yml: add fuzz seed corpus regression step so any checked-in corpus entries that crash are caught on every PR. - Add testdata/fuzz/.gitkeep placeholders so corpus cache paths are consistent. - Document corpus retention policy in .gitignore. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/fuzz.yml | 76 +++++++++++++++++++ .github/workflows/test.yml | 2 + .gitignore | 4 + .../builtins/tests/cat/testdata/fuzz/.gitkeep | 0 .../tests/grep/testdata/fuzz/.gitkeep | 0 .../tests/head/testdata/fuzz/.gitkeep | 0 .../tests/tail/testdata/fuzz/.gitkeep | 0 .../builtins/tests/wc/testdata/fuzz/.gitkeep | 0 8 files changed, 82 insertions(+) create mode 100644 .github/workflows/fuzz.yml create mode 100644 interp/builtins/tests/cat/testdata/fuzz/.gitkeep create mode 100644 interp/builtins/tests/grep/testdata/fuzz/.gitkeep create mode 100644 interp/builtins/tests/head/testdata/fuzz/.gitkeep create mode 100644 interp/builtins/tests/tail/testdata/fuzz/.gitkeep create mode 100644 interp/builtins/tests/wc/testdata/fuzz/.gitkeep diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 00000000..ee7dfecf --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,76 @@ +name: Fuzz Tests + +on: + push: + branches: ['**'] + pull_request: + +permissions: + contents: read + +jobs: + fuzz: + name: Fuzz (${{ matrix.name }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - pkg: ./interp/builtins/tests/head/ + name: head + - pkg: ./interp/builtins/tests/cat/ + name: cat + - pkg: ./interp/builtins/tests/wc/ + name: wc + - pkg: ./interp/builtins/tests/tail/ + name: tail + - pkg: ./interp/builtins/tests/grep/ + name: grep + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + with: + go-version-file: .go-version + + # Restore corpus from previous runs + - name: Restore fuzz corpus + uses: actions/cache@v4 + with: + path: | + interp/builtins/tests/${{ matrix.name }}/testdata/fuzz/ + key: fuzz-corpus-${{ matrix.name }}-${{ github.sha }} + restore-keys: | + fuzz-corpus-${{ matrix.name }}- + + # Run seed corpus as normal tests (fast, deterministic) + - name: Run fuzz seed corpus + run: | + # Find all Fuzz* functions in the package (excluding differential ones that need RSHELL_BASH_TEST) + FUZZ_FUNCS=$(grep -r '^func Fuzz' ${{ matrix.pkg }} 2>/dev/null | grep -v 'Differential' | sed 's/.*func \(Fuzz[^(]*\).*/\1/' | sort -u | tr '\n' '|' | sed 's/|$//') + if [ -n "$FUZZ_FUNCS" ]; then + go test -run "^(${FUZZ_FUNCS})$" -fuzztime=0s ${{ matrix.pkg }} -timeout 120s + else + echo "No non-differential fuzz functions found in ${{ matrix.pkg }}, skipping" + fi + + # Run actual fuzzing for a short duration + - name: Fuzz (${{ matrix.name }}) + run: | + FUZZ_FUNCS=$(grep -r '^func Fuzz' ${{ matrix.pkg }} 2>/dev/null | grep -v 'Differential' | sed 's/.*func \(Fuzz[^(]*\).*/\1/' | sort -u) + if [ -z "$FUZZ_FUNCS" ]; then + echo "No fuzz targets found in ${{ matrix.pkg }}, skipping" + exit 0 + fi + for FUNC in $FUZZ_FUNCS; do + echo "Fuzzing $FUNC..." + go test -fuzz="^${FUNC}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 120s || true + done + + # Save corpus + - name: Save fuzz corpus + uses: actions/cache/save@v4 + if: always() + with: + path: | + interp/builtins/tests/${{ matrix.name }}/testdata/fuzz/ + key: fuzz-corpus-${{ matrix.name }}-${{ github.sha }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ec5b00e..68fa3c50 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,8 @@ jobs: go-version-file: .go-version - name: Run tests with race detector run: go test -race -v ./... + - name: Run fuzz seed corpus (regression test) + run: go test -run '^Fuzz' -fuzztime=0s ./interp/builtins/... -timeout 120s test-against-bash: name: Test against Bash (Docker) diff --git a/.gitignore b/.gitignore index 3a8c62d8..b102bc82 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ /rshell .DS_Store + +# Fuzz corpus: keep checked in for regression testing. +# Uncomment the line below if corpus grows too large: +# interp/builtins/tests/*/testdata/fuzz/*/corpus-* diff --git a/interp/builtins/tests/cat/testdata/fuzz/.gitkeep b/interp/builtins/tests/cat/testdata/fuzz/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/interp/builtins/tests/grep/testdata/fuzz/.gitkeep b/interp/builtins/tests/grep/testdata/fuzz/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/interp/builtins/tests/head/testdata/fuzz/.gitkeep b/interp/builtins/tests/head/testdata/fuzz/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/interp/builtins/tests/tail/testdata/fuzz/.gitkeep b/interp/builtins/tests/tail/testdata/fuzz/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/interp/builtins/tests/wc/testdata/fuzz/.gitkeep b/interp/builtins/tests/wc/testdata/fuzz/.gitkeep new file mode 100644 index 00000000..e69de29b From 1500d7b33ba77407554eb96fcc76b414eacdf003 Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Thu, 12 Mar 2026 11:53:13 -0400 Subject: [PATCH 2/3] Address review comments on fuzz.yml - Fix || true silencing fuzzer crashes: accumulate failures with FAILED=0/FAILED=1 and exit with the accumulated code so any crash fails the step while still running all fuzz targets in the loop (P1) - Pin actions/cache and actions/cache/save to SHA 0057852bfaa89a56745cba8c7296529d2fc39830 (v4.3.0) matching the supply-chain pinning discipline for checkout and setup-go (P2) - Restrict push trigger from branches: ['**'] to branches: [main] to match the scope convention in test.yml and avoid burning CI minutes on WIP branches (P3) - Use -timeout 60s per fuzz invocation instead of shared -timeout 120s so the timeout does not accumulate across multiple fuzz targets in one package (P3) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/fuzz.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index ee7dfecf..ab9905d7 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -2,7 +2,7 @@ name: Fuzz Tests on: push: - branches: ['**'] + branches: [main] pull_request: permissions: @@ -34,7 +34,7 @@ jobs: # Restore corpus from previous runs - name: Restore fuzz corpus - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | interp/builtins/tests/${{ matrix.name }}/testdata/fuzz/ @@ -61,14 +61,16 @@ jobs: echo "No fuzz targets found in ${{ matrix.pkg }}, skipping" exit 0 fi + FAILED=0 for FUNC in $FUZZ_FUNCS; do echo "Fuzzing $FUNC..." - go test -fuzz="^${FUNC}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 120s || true + go test -fuzz="^${FUNC}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 60s || FAILED=1 done + exit $FAILED # Save corpus - name: Save fuzz corpus - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: always() with: path: | From 6061958477448c2cf8b501dbcfce6fdf6c13b7f8 Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Thu, 12 Mar 2026 13:15:32 -0400 Subject: [PATCH 3/3] Fix: remove invalid -fuzztime=0s from seed corpus step -fuzztime=0s is rejected by the Go test binary as an invalid duration when used without -fuzz=. Drop the flag; -run '^Fuzz' without -fuzz= is all that's needed to run seed corpus entries as regular tests. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 68fa3c50..916ec44f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - name: Run tests with race detector run: go test -race -v ./... - name: Run fuzz seed corpus (regression test) - run: go test -run '^Fuzz' -fuzztime=0s ./interp/builtins/... -timeout 120s + run: go test -run '^Fuzz' ./interp/builtins/... -timeout 120s test-against-bash: name: Test against Bash (Docker)