Coverage guided mutation fuzzing and mutation agnostic tests #44
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: VDOM Fuzz | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - ".github/workflows/vdom-fuzz.yml" | |
| - "Cargo.lock" | |
| - "Cargo.toml" | |
| - "codecov.yml" | |
| - "packages/fuzz/**" | |
| - "packages/oracle/**" | |
| - "packages/core/**" | |
| - "packages/core-types/**" | |
| - "packages/dioxus/**" | |
| - "packages/ssr/**" | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| branches: | |
| - main | |
| paths: | |
| - ".github/workflows/vdom-fuzz.yml" | |
| - "Cargo.lock" | |
| - "Cargo.toml" | |
| - "codecov.yml" | |
| - "packages/fuzz/**" | |
| - "packages/oracle/**" | |
| - "packages/core/**" | |
| - "packages/core-types/**" | |
| - "packages/dioxus/**" | |
| - "packages/ssr/**" | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_TERM_COLOR: always | |
| FUZZ_DIR: packages/fuzz/fuzz | |
| FUZZ_TARGET: vdom_ops | |
| RUST_BACKTRACE: 1 | |
| rust_nightly: nightly-2025-10-05 | |
| # `cargo fuzz run` (smoke test) and `cargo fuzz coverage` both | |
| # produce binaries that trip LSan on `generational_box`'s and the | |
| # fuzz harness's intentional `Box::leak` sites. Apply the same | |
| # suppression file to every step that runs the fuzzer. | |
| LSAN_OPTIONS: suppressions=${{ github.workspace }}/packages/fuzz/fuzz/lsan.supp,print_suppressions=1 | |
| jobs: | |
| test-and-coverage: | |
| if: github.event.pull_request.draft == false | |
| name: "Fuzz | Test and coverage" | |
| runs-on: warp-ubuntu-latest-x64-4x | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install Rust ${{ env.rust_nightly }} | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| toolchain: ${{ env.rust_nightly }} | |
| components: llvm-tools-preview | |
| - uses: dtolnay/install@cargo-fuzz | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| cache-all-crates: "true" | |
| cache-workspace-crates: "true" | |
| cache-provider: "warpbuild" | |
| - name: Test fuzz support crate | |
| run: cargo test -p dioxus-vdom-fuzz --lib --examples | |
| - name: Resolve LSan symbolizer | |
| # Both the smoke test and `cargo fuzz coverage` need this on PATH | |
| # for LSan to demangle symbols (and therefore for the `leak:` | |
| # suppressions to match). Write it to $GITHUB_ENV so every later | |
| # step inherits it. | |
| run: | | |
| target_triple="$(rustc +${{ env.rust_nightly }} -vV | sed -n 's/^host: //p')" | |
| rustup_symbolizer="$(rustc +${{ env.rust_nightly }} --print sysroot)/lib/rustlib/$target_triple/bin/llvm-symbolizer" | |
| if [ -x "$rustup_symbolizer" ]; then | |
| symbolizer="$rustup_symbolizer" | |
| elif command -v llvm-symbolizer >/dev/null; then | |
| symbolizer="$(command -v llvm-symbolizer)" | |
| else | |
| symbolizer="$(ls /usr/bin/llvm-symbolizer-* 2>/dev/null | sort -V | tail -n1)" | |
| fi | |
| if [ -z "$symbolizer" ] || [ ! -x "$symbolizer" ]; then | |
| echo "no usable llvm-symbolizer found" >&2 | |
| exit 1 | |
| fi | |
| echo "ASAN_SYMBOLIZER_PATH=$symbolizer" | tee -a "$GITHUB_ENV" | |
| - name: Smoke test fuzz target | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/fuzz-corpus" "$RUNNER_TEMP/fuzz-artifacts" | |
| cargo +${{ env.rust_nightly }} fuzz run --fuzz-dir "$FUZZ_DIR" "$FUZZ_TARGET" "$RUNNER_TEMP/fuzz-corpus" -- \ | |
| -runs=256 \ | |
| -artifact_prefix="$RUNNER_TEMP/fuzz-artifacts/" | |
| - name: Generate fuzz coverage | |
| id: coverage | |
| env: | |
| # Smoke test already runs LSan; the coverage build is purely for | |
| # llvm-cov instrumentation. With leak detection on, cargo-fuzz's | |
| # MERGE-mode child exits non-zero even when every leak is | |
| # suppressed, which fails the step despite the profdata being | |
| # written successfully. | |
| ASAN_OPTIONS: detect_leaks=0 | |
| run: | | |
| cargo +${{ env.rust_nightly }} fuzz coverage --fuzz-dir "$FUZZ_DIR" "$FUZZ_TARGET" "$RUNNER_TEMP/fuzz-corpus" -- -runs=0 | |
| target_triple="$(rustc +${{ env.rust_nightly }} -vV | sed -n 's/^host: //p')" | |
| llvm_cov="$(rustc +${{ env.rust_nightly }} --print sysroot)/lib/rustlib/$target_triple/bin/llvm-cov" | |
| # cargo-fuzz's coverage mode overrides --target-dir to | |
| # `<cwd>/target/<triple>/coverage` (see project.rs::target_dir), | |
| # so the binary lands in the workspace-root `target/`, not in | |
| # `$FUZZ_DIR/target/`. | |
| coverage_binary="target/$target_triple/coverage/$target_triple/release/$FUZZ_TARGET" | |
| coverage_profile="$FUZZ_DIR/coverage/$FUZZ_TARGET/coverage.profdata" | |
| coverage_lcov="$RUNNER_TEMP/fuzz.lcov" | |
| coverage_report="$RUNNER_TEMP/fuzz-coverage.txt" | |
| coverage_comment="$RUNNER_TEMP/fuzz-coverage.md" | |
| test -x "$coverage_binary" | |
| test -s "$coverage_profile" | |
| "$llvm_cov" report \ | |
| --instr-profile="$coverage_profile" \ | |
| "$coverage_binary" \ | |
| --sources packages/fuzz/src \ | |
| | tee "$coverage_report" | |
| "$llvm_cov" export \ | |
| --format=lcov \ | |
| --instr-profile="$coverage_profile" \ | |
| "$coverage_binary" \ | |
| --sources packages/fuzz/src \ | |
| > "$coverage_lcov" | |
| test -s "$coverage_lcov" | |
| test -s "$coverage_report" | |
| COVERAGE_REPORT="$coverage_report" \ | |
| COVERAGE_COMMENT="$coverage_comment" \ | |
| python3 - <<'PY' | |
| import os | |
| import sys | |
| from pathlib import Path | |
| report_path = Path(os.environ["COVERAGE_REPORT"]) | |
| comment_path = Path(os.environ["COVERAGE_COMMENT"]) | |
| output_path = Path(os.environ["GITHUB_OUTPUT"]) | |
| total = next( | |
| (line for line in report_path.read_text(encoding="utf-8").splitlines() if line.startswith("TOTAL")), | |
| None, | |
| ) | |
| if total is None: | |
| print("llvm-cov report did not include a TOTAL row", file=sys.stderr) | |
| sys.exit(1) | |
| fields = total.split() | |
| if len(fields) < 10: | |
| print(f"Unexpected llvm-cov TOTAL row: {total}", file=sys.stderr) | |
| sys.exit(1) | |
| comment = f"""## Dioxus VDOM fuzz coverage | |
| Coverage generated from `cargo fuzz coverage` for `packages/fuzz/src` after the `{os.environ["FUZZ_TARGET"]}` smoke corpus run. | |
| | Metric | Coverage | | |
| | --- | ---: | | |
| | Regions | {fields[3]} | | |
| | Functions | {fields[6]} | | |
| | Lines | {fields[9]} | | |
| """ | |
| comment_path.write_text(comment, encoding="utf-8") | |
| with output_path.open("a", encoding="utf-8") as output: | |
| output.write("comment<<EOF\n") | |
| output.write(comment) | |
| output.write("\nEOF\n") | |
| PY | |
| - name: Upload fuzz coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| fail_ci_if_error: true | |
| files: ${{ runner.temp }}/fuzz.lcov | |
| flags: fuzz | |
| name: fuzz | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Upload fuzz coverage artifact | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: fuzz-coverage | |
| path: | | |
| ${{ runner.temp }}/fuzz.lcov | |
| ${{ runner.temp }}/fuzz-coverage.txt | |
| ${{ runner.temp }}/fuzz-coverage.md | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| - name: Upload fuzz failure artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: fuzz-artifacts | |
| path: ${{ runner.temp }}/fuzz-artifacts | |
| if-no-files-found: ignore | |
| retention-days: 7 |