lez-fuzzing/scripts/check_target_inventory.py
Roman 56162a66a3
fix: repo pinning
- minor invariant changes
- missing target in CI added
- docs
2026-06-22 10:17:30 +08:00

93 lines
2.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Fail if any fuzz target registered in fuzz/Cargo.toml is missing from a
workflow / script / doc that enumerates the target list.
`fuzz/Cargo.toml` is the single source of truth: every `[[bin]] name = "fuzz_*"`
must be mentioned by name in each of the consumer files below. This guards
against the drift that `scripts/add_fuzz_target.py` cannot prevent on its own
(it only edits `.github/workflows/fuzz.yml`).
Usage:
python3 scripts/check_target_inventory.py
Exit code 0 = all consumers list every target; 1 = drift detected (prints the
missing target/file pairs). Run from anywhere; paths are resolved relative to
the repository root.
"""
import re
import sys
from pathlib import Path
# Files that enumerate the full target list and must stay in sync with Cargo.toml.
# Paths are relative to the repository root.
CONSUMERS = [
".github/workflows/fuzz.yml",
".github/workflows/fuzz-afl.yml",
".github/workflows/mutants.yml",
"scripts/mutants-corpus-test.sh",
"README.md",
"docs/fuzzing.md",
]
_BIN_NAME_RE = re.compile(r'name\s*=\s*"(fuzz_[a-z0-9_]+)"')
def registered_targets(cargo_toml: Path) -> list[str]:
"""Every `[[bin]] name = "fuzz_*"` in fuzz/Cargo.toml, in file order."""
names = _BIN_NAME_RE.findall(cargo_toml.read_text())
# Preserve order, drop duplicates defensively.
seen: set[str] = set()
ordered: list[str] = []
for n in names:
if n not in seen:
seen.add(n)
ordered.append(n)
return ordered
def main() -> None:
root = Path(__file__).parent.parent # repository root
cargo_toml = root / "fuzz" / "Cargo.toml"
if not cargo_toml.exists():
print(f"ERROR: {cargo_toml} not found", file=sys.stderr)
sys.exit(1)
targets = registered_targets(cargo_toml)
if not targets:
print(f"ERROR: no [[bin]] targets found in {cargo_toml}", file=sys.stderr)
sys.exit(1)
missing: list[tuple[str, str]] = []
for rel in CONSUMERS:
path = root / rel
if not path.exists():
print(f"ERROR: consumer file not found: {rel}", file=sys.stderr)
sys.exit(1)
text = path.read_text()
for target in targets:
if target not in text:
missing.append((rel, target))
if missing:
print(
f"Target-inventory drift: {len(targets)} targets registered in "
f"fuzz/Cargo.toml, but some consumers are missing entries:\n",
file=sys.stderr,
)
for rel, target in missing:
print(f" MISSING {rel} -> {target}", file=sys.stderr)
print(
"\nAdd the target(s) above to each listed file "
"(see scripts/add_fuzz_target.py for the canonical insertion points).",
file=sys.stderr,
)
sys.exit(1)
print(f"OK: all {len(CONSUMERS)} consumers list every one of the "
f"{len(targets)} registered targets.")
if __name__ == "__main__":
main()