From b06f6c552910574e14e4fcad2dee417f3cf15445 Mon Sep 17 00:00:00 2001 From: Anyitechs Date: Fri, 10 Oct 2025 13:05:22 +0100 Subject: [PATCH 1/2] Upload CI generated fuzz corpus coverage to codecov Because each CI job runs on a fresh runner and can't share data between jobs. We rely on Github Actions upload-artifact and download-artifact to share the CI generated fuzz corpus, then replay them in the `contrib/generate_fuzz_coverage.sh` script to generate the coverage report. --- .github/workflows/build.yml | 11 +++++++++++ contrib/generate_fuzz_coverage.sh | 23 ++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b643058a746..2ff428c4b75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,6 +110,7 @@ jobs: run: CI_ENV=1 CI_MINIMIZE_DISK_USAGE=1 ./ci/ci-tx-sync-tests.sh coverage: + needs: fuzz strategy: fail-fast: false runs-on: self-hosted @@ -133,6 +134,11 @@ jobs: # Maybe if codecov wasn't broken we wouldn't need to do this... ./codecov --verbose upload-process --disable-search --fail-on-error -f target/codecov.json -t "f421b687-4dc2-4387-ac3d-dc3b2528af57" -F 'tests' cargo clean + - name: Download honggfuzz corpus + uses: actions/download-artifact@v4 + with: + name: hfuzz-corpus + path: fuzz/hfuzz_workspace - name: Run fuzz coverage generation run: | ./contrib/generate_fuzz_coverage.sh --output-dir `pwd` --output-codecov-json @@ -268,6 +274,11 @@ jobs: cargo clean - name: Run fuzzers run: cd fuzz && ./ci-fuzz.sh && cd .. + - name: Upload honggfuzz corpus + uses: actions/upload-artifact@v4 + with: + name: hfuzz-corpus + path: fuzz/hfuzz_workspace linting: runs-on: ubuntu-latest diff --git a/contrib/generate_fuzz_coverage.sh b/contrib/generate_fuzz_coverage.sh index 694ff65aa2f..09d37656f47 100755 --- a/contrib/generate_fuzz_coverage.sh +++ b/contrib/generate_fuzz_coverage.sh @@ -62,9 +62,26 @@ if [ "$OUTPUT_CODECOV_JSON" = "0" ]; then cargo llvm-cov --html --ignore-filename-regex "fuzz/" --output-dir "$OUTPUT_DIR" echo "Coverage report generated in $OUTPUT_DIR/html/index.html" else - cargo llvm-cov -j8 --codecov --ignore-filename-regex "fuzz/" --output-path "$OUTPUT_DIR/fuzz-codecov.json" - echo "Fuzz codecov report available at $OUTPUT_DIR/fuzz-codecov.json" -fi + # Clean previous coverage artifacts to ensure a fresh run. + cargo llvm-cov clean --workspace + # Import honggfuzz corpus if the artifact was downloaded. + if [ -d "hfuzz_workspace" ]; then + echo "Importing corpus from hfuzz_workspace..." + for target_dir in hfuzz_workspace/*; do + [ -d "$target_dir" ] || continue + src_name="$(basename "$target_dir")" + dest="${src_name%_target}" + mkdir -p "test_cases/$dest" + # Copy corpus files into the test_cases directory + find "$target_dir" -maxdepth 2 -type f -path "$target_dir/input/*" \ + -print0 | xargs -0 -I{} cp -n {} "test_cases/$dest/" + done + fi + echo "Replaying imported corpus (if found) via tests to generate coverage..." + cargo llvm-cov -j8 --codecov --ignore-filename-regex "fuzz/" \ + --output-path "$OUTPUT_DIR/fuzz-codecov.json" --tests + echo "Fuzz codecov report available at $OUTPUT_DIR/fuzz-codecov.json" +fi From 58078522acb7c27ef9f02fc9bc657f198177dd00 Mon Sep 17 00:00:00 2001 From: Anyitechs Date: Mon, 27 Oct 2025 17:04:32 +0100 Subject: [PATCH 2/2] Persist fuzz corpus between CI runs Implements a persistent, global fuzz corpus cache. PRs perform a "read-only" restore from the `main` cache to seed fuzzer runs. The `main` branch performs a "read-write" to save new findings and grow the corpus. --- .github/workflows/build.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ff428c4b75..3c50b2a0041 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -267,6 +267,29 @@ jobs: - name: Install Rust ${{ env.TOOLCHAIN }} toolchain run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain ${{ env.TOOLCHAIN }} + # This is read-only for PRs. It seeds the fuzzer for a more effective run. + # NOTE: The `key` is unique and will always miss, forcing a fallback to + # the `restore-keys` to find the latest global cache from the `main` branch. + - name: Restore persistent fuzz corpus (PR) + if: ${{ github.ref != 'refs/heads/main' }} + uses: actions/cache/restore@v4 + with: + path: fuzz/hfuzz_workspace + key: fuzz-corpus-${{ github.ref }}-${{ github.sha }} + restore-keys: | + fuzz-corpus-refs/heads/main- + # The `restore-keys` performs a prefix search to find the most recent + # cache from a previous `main` run. We then save with a new, unique + # `key` (using the SHA) to ensure the cache is always updated, + # as caches are immutable. + - name: Restore/Save persistent honggfuzz corpus (Main) + if: ${{ github.ref == 'refs/heads/main' }} + uses: actions/cache@v4 + with: + path: fuzz/hfuzz_workspace + key: fuzz-corpus-refs/heads/main-${{ github.sha }} + restore-keys: | + fuzz-corpus-refs/heads/main- - name: Sanity check fuzz targets on Rust ${{ env.TOOLCHAIN }} run: | cd fuzz