Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .evergreen/scripts/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,26 @@ def handle_fuzz() -> None:
targets = ["fuzz_decode", "fuzz_decode_all", "fuzz_is_valid"]

if SUB_TEST_NAME == "regression":
# Run the committed reproducers plus the seed corpus.
# Skip dotfiles (pathlib globbing matches them).
inputs = sorted(
str(p) for p in (ROOT / "fuzz" / "regression").glob("*") if not p.name.startswith(".")
)
if not inputs:
raise RuntimeError("No fuzz regression inputs found in fuzz/regression/")
# Each reproducer is passed as its own file argument, so libFuzzer
# runs each exactly once; the -atheris_runs budget equals the file
# count so atheris exits cleanly after the last one.
corpus_dir = os.environ.get("FUZZ_CORPUS_DIR", str(ROOT / "fuzz-scratch" / "corpus"))
# Fail fast on a missing or empty corpus; libFuzzer would silently
# skip it.
corpus_files = sorted(
str(p) for p in Path(corpus_dir).glob("*") if not p.name.startswith(".")
)
if not corpus_files:
raise RuntimeError(
f"No fuzz seed corpus found in {corpus_dir}; was setup_tests.py run?"
)
inputs.extend(corpus_files)
# Each input runs exactly once without random fuzzing; the -atheris_runs
# budget equals the file count so atheris exits cleanly after the last.
runs = len(inputs)
for target in targets:
run_command(
Expand Down
5 changes: 3 additions & 2 deletions .evergreen/scripts/setup_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,9 @@ def handle_test_env() -> None:
else:
TEST_ARGS = f"test/performance/async_perf_test.py {TEST_ARGS}"

if test_name == "fuzz" and sub_test_name != "regression":
# Build the seed corpus from the vendored spec data.
if test_name == "fuzz":
# Build the seed corpus from the vendored spec data. Regression runs
# run it too.
scratch_dir = ROOT / "fuzz-scratch"
shutil.rmtree(scratch_dir, ignore_errors=True)
corpus_dir = scratch_dir / "corpus"
Expand Down
5 changes: 4 additions & 1 deletion fuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ just setup-tests fuzz regression
just run-tests
```

On every PR, the `fuzz-regression` task runs the committed reproducers and the full generated seed
corpus.

Or without the justfile:

```bash
Expand Down Expand Up @@ -114,5 +117,5 @@ fuzz_decode.py Fuzz target: bson.decode()
fuzz_decode_all.py Fuzz target: bson.decode_all()
fuzz_is_valid.py Fuzz target: bson.is_valid()
fuzz_shared.py Code shared by the fuzz targets
regression/ Known bug reproducers (no corpus setup required)
regression/ Known bug reproducers; run with the seed corpus on PRs
```
4 changes: 4 additions & 0 deletions fuzz/fuzz_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def normalize(obj: object) -> object:
return [normalize(v) for v in obj]
if isinstance(obj, float) and math.isnan(obj):
return "nan"
if type(obj).__name__ == "Regex":
# int() canonicalizes the C extension's plain int flags and pure
# Python's re.RegexFlag.
return f"Regex:{obj.pattern!r}:{int(obj.flags)}"
return f"{type(obj).__name__}:{obj!r}"


Expand Down
Binary file added fuzz/regression/decode__regex_flags_repr
Binary file not shown.
Loading