mirror of
https://github.com/uutils/grep.git
synced 2026-06-10 16:15:11 -07:00
9598faf708
Add a cargo-fuzz harness (fuzz_grep) that runs uu_grep and GNU grep on the same generated args/input and panics on any divergence, using the vendored uufuzz crate (adapted from uutils/coreutils to depend on crates.io uucore rather than a path). A CI workflow (.github/workflows/fuzzing.yml) builds the uufuzz examples, builds the fuzzer, and runs it for 60s. fuzz_grep is marked should_pass: false / continue-on-error since it currently surfaces real GNU-compatibility gaps (e.g. uu_grep rejects a repeated -m, GNU accepts).
163 lines
6.2 KiB
YAML
163 lines
6.2 KiB
YAML
name: Fuzzing
|
|
|
|
# spell-checker:ignore (people) taiki-e
|
|
# spell-checker:ignore (misc) fuzzer uufuzz
|
|
|
|
env:
|
|
CARGO_INCREMENTAL: "0"
|
|
|
|
on:
|
|
push:
|
|
branches: [ main, master ]
|
|
pull_request:
|
|
branches: [ main, master ]
|
|
|
|
permissions:
|
|
contents: read # to fetch code (actions/checkout)
|
|
|
|
# End the current execution if there is a new changeset in the PR.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
|
|
jobs:
|
|
uufuzz-examples:
|
|
name: Build and test uufuzz examples
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
- name: Build uufuzz library
|
|
run: |
|
|
cd fuzz/uufuzz
|
|
cargo build --release
|
|
- name: Run uufuzz tests
|
|
run: |
|
|
cd fuzz/uufuzz
|
|
cargo test --lib
|
|
- name: Build and run uufuzz examples
|
|
run: |
|
|
cd fuzz/uufuzz
|
|
echo "Building all examples..."
|
|
cargo build --examples --release
|
|
|
|
# Run all examples except integration_testing (which has FD issues in CI)
|
|
for example in examples/*.rs; do
|
|
example_name=$(basename "$example" .rs)
|
|
if [ "$example_name" != "integration_testing" ]; then
|
|
cargo run --example "$example_name" --release
|
|
fi
|
|
done
|
|
|
|
fuzz-build:
|
|
name: Build the fuzzers
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
- name: Install `cargo-fuzz`
|
|
uses: taiki-e/install-action@v2
|
|
with:
|
|
tool: cargo-fuzz
|
|
- name: Emulate a nightly toolchain
|
|
run: |
|
|
echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}"
|
|
- name: Run `cargo-fuzz build`
|
|
# Force the correct target
|
|
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
|
|
run: cargo fuzz build --target $(rustc --print host-tuple)
|
|
|
|
fuzz-run:
|
|
needs: fuzz-build
|
|
name: Fuzz
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
env:
|
|
RUN_FOR: 60
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
test-target:
|
|
# fuzz_grep is a differential fuzzer against GNU grep; it currently
|
|
# surfaces compatibility differences, so it is not expected to pass yet.
|
|
- { name: fuzz_grep, should_pass: false }
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
- name: Install `cargo-fuzz`
|
|
uses: taiki-e/install-action@v2
|
|
with:
|
|
tool: cargo-fuzz
|
|
- name: Emulate a nightly toolchain
|
|
run: |
|
|
echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}"
|
|
- name: Run ${{ matrix.test-target.name }} for ${{ env.RUN_FOR }} seconds
|
|
id: run_fuzzer
|
|
shell: bash
|
|
continue-on-error: ${{ !matrix.test-target.should_pass }}
|
|
run: |
|
|
mkdir -p fuzz/stats
|
|
STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt"
|
|
# Force the correct target
|
|
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
|
|
cargo fuzz run --target $(rustc --print host-tuple) ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE"
|
|
|
|
# Save should_pass value for later inspection
|
|
echo "${{ matrix.test-target.should_pass }}" > "fuzz/stats/${{ matrix.test-target.name }}.should_pass"
|
|
|
|
# Print stats to job output for immediate visibility
|
|
echo "----------------------------------------"
|
|
echo "FUZZING STATISTICS FOR ${{ matrix.test-target.name }}"
|
|
echo "----------------------------------------"
|
|
echo "Runs: $(grep -q "stat::number_of_executed_units" "$STATS_FILE" && grep "stat::number_of_executed_units" "$STATS_FILE" | awk '{print $2}' || echo "unknown")"
|
|
echo "Execution Rate: $(grep -q "stat::average_exec_per_sec" "$STATS_FILE" && grep "stat::average_exec_per_sec" "$STATS_FILE" | awk '{print $2}' || echo "unknown") execs/sec"
|
|
echo "New Units: $(grep -q "stat::new_units_added" "$STATS_FILE" && grep "stat::new_units_added" "$STATS_FILE" | awk '{print $2}' || echo "unknown")"
|
|
echo "Expected: ${{ matrix.test-target.should_pass }}"
|
|
if grep -q "SUMMARY: " "$STATS_FILE"; then
|
|
echo "Status: $(grep "SUMMARY: " "$STATS_FILE" | head -1)"
|
|
else
|
|
echo "Status: Completed"
|
|
fi
|
|
echo "----------------------------------------"
|
|
|
|
# Add summary to GitHub step summary
|
|
echo "### Fuzzing Results for ${{ matrix.test-target.name }}" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
|
|
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
|
|
echo "| Runs | $(grep "stat::number_of_executed_units" "$STATS_FILE" | awk '{print $2}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
if grep -q "stat::average_exec_per_sec" "$STATS_FILE"; then
|
|
echo "| Execution Rate | $(grep "stat::average_exec_per_sec" "$STATS_FILE" | awk '{print $2}') execs/sec |" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
if grep -q "stat::new_units_added" "$STATS_FILE"; then
|
|
echo "| New Units | $(grep "stat::new_units_added" "$STATS_FILE" | awk '{print $2}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
echo "| Should pass | ${{ matrix.test-target.should_pass }} |" >> "$GITHUB_STEP_SUMMARY"
|
|
if grep -q "SUMMARY: " "$STATS_FILE"; then
|
|
echo "| Status | $(grep "SUMMARY: " "$STATS_FILE" | head -1) |" >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
echo "| Status | Completed |" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
- name: Upload Stats
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: fuzz-stats-${{ matrix.test-target.name }}
|
|
path: |
|
|
fuzz/stats/${{ matrix.test-target.name }}.txt
|
|
fuzz/stats/${{ matrix.test-target.name }}.should_pass
|
|
retention-days: 5
|