Various small improvements to the fuzz-parser script (#11186)

This commit is contained in:
Alex Waygood 2024-04-28 19:17:27 +01:00 committed by GitHub
parent 3474e37836
commit 113e259e6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 21 deletions

View file

@ -11,8 +11,9 @@ Example invocations of the script:
but only reporting bugs that are new on your branch: but only reporting bugs that are new on your branch:
`python scripts/fuzz-parser/fuzz.py 0-10 --new-bugs-only` `python scripts/fuzz-parser/fuzz.py 0-10 --new-bugs-only`
- Run the fuzzer concurrently on 10,000 different Python source-code files, - Run the fuzzer concurrently on 10,000 different Python source-code files,
and only print a summary at the end: using a random selection of seeds, and only print a summary at the end
`python scripts/fuzz-parser/fuzz.py 1-10000 --quiet (the `shuf` command is Unix-specific):
`python scripts/fuzz-parser/fuzz.py $(shuf -i 0-1000000 -n 10000) --quiet
""" """
from __future__ import annotations from __future__ import annotations
@ -27,6 +28,7 @@ from typing import NewType
from pysource_codegen import generate as generate_random_code from pysource_codegen import generate as generate_random_code
from pysource_minimize import minimize as minimize_repro from pysource_minimize import minimize as minimize_repro
from rich_argparse import RawDescriptionRichHelpFormatter
from termcolor import colored from termcolor import colored
MinimizedSourceCode = NewType("MinimizedSourceCode", str) MinimizedSourceCode = NewType("MinimizedSourceCode", str)
@ -67,19 +69,20 @@ class FuzzResult:
# required to trigger the bug. If not, it will be `None`. # required to trigger the bug. If not, it will be `None`.
maybe_bug: MinimizedSourceCode | None maybe_bug: MinimizedSourceCode | None
def print_description(self) -> None: def print_description(self, index: int, num_seeds: int) -> None:
"""Describe the results of fuzzing the parser with this seed.""" """Describe the results of fuzzing the parser with this seed."""
progress = f"[{index}/{num_seeds}]"
msg = (
colored(f"Ran fuzzer on seed {self.seed}", "red")
if self.maybe_bug
else colored(f"Ran fuzzer successfully on seed {self.seed}", "green")
)
print(f"{msg:<55} {progress:>15}", flush=True)
if self.maybe_bug: if self.maybe_bug:
print(colored(f"Ran fuzzer on seed {self.seed}", "red"))
print(colored("The following code triggers a bug:", "red")) print(colored("The following code triggers a bug:", "red"))
print() print()
print(self.maybe_bug) print(self.maybe_bug)
print(flush=True) print(flush=True)
else:
print(
colored(f"Ran fuzzer successfully on seed {self.seed}", "green"),
flush=True,
)
def fuzz_code( def fuzz_code(
@ -110,9 +113,10 @@ def fuzz_code(
def run_fuzzer_concurrently(args: ResolvedCliArgs) -> list[FuzzResult]: def run_fuzzer_concurrently(args: ResolvedCliArgs) -> list[FuzzResult]:
num_seeds = len(args.seeds)
print( print(
f"Concurrently running the fuzzer on " f"Concurrently running the fuzzer on "
f"{len(args.seeds)} randomly generated source-code files..." f"{num_seeds} randomly generated source-code files..."
) )
bugs: list[FuzzResult] = [] bugs: list[FuzzResult] = []
with concurrent.futures.ProcessPoolExecutor() as executor: with concurrent.futures.ProcessPoolExecutor() as executor:
@ -127,10 +131,12 @@ def run_fuzzer_concurrently(args: ResolvedCliArgs) -> list[FuzzResult]:
for seed in args.seeds for seed in args.seeds
] ]
try: try:
for future in concurrent.futures.as_completed(fuzz_result_futures): for i, future in enumerate(
concurrent.futures.as_completed(fuzz_result_futures), start=1
):
fuzz_result = future.result() fuzz_result = future.result()
if not args.quiet: if not args.quiet:
fuzz_result.print_description() fuzz_result.print_description(i, num_seeds)
if fuzz_result.maybe_bug: if fuzz_result.maybe_bug:
bugs.append(fuzz_result) bugs.append(fuzz_result)
except KeyboardInterrupt: except KeyboardInterrupt:
@ -142,12 +148,13 @@ def run_fuzzer_concurrently(args: ResolvedCliArgs) -> list[FuzzResult]:
def run_fuzzer_sequentially(args: ResolvedCliArgs) -> list[FuzzResult]: def run_fuzzer_sequentially(args: ResolvedCliArgs) -> list[FuzzResult]:
num_seeds = len(args.seeds)
print( print(
f"Sequentially running the fuzzer on " f"Sequentially running the fuzzer on "
f"{len(args.seeds)} randomly generated source-code files..." f"{num_seeds} randomly generated source-code files..."
) )
bugs: list[FuzzResult] = [] bugs: list[FuzzResult] = []
for seed in args.seeds: for i, seed in enumerate(args.seeds, start=1):
fuzz_result = fuzz_code( fuzz_result = fuzz_code(
seed, seed,
test_executable=args.test_executable, test_executable=args.test_executable,
@ -155,7 +162,7 @@ def run_fuzzer_sequentially(args: ResolvedCliArgs) -> list[FuzzResult]:
only_new_bugs=args.only_new_bugs, only_new_bugs=args.only_new_bugs,
) )
if not args.quiet: if not args.quiet:
fuzz_result.print_description() fuzz_result.print_description(i, num_seeds)
if fuzz_result.maybe_bug: if fuzz_result.maybe_bug:
bugs.append(fuzz_result) bugs.append(fuzz_result)
return bugs return bugs
@ -212,7 +219,7 @@ class ResolvedCliArgs:
def parse_args() -> ResolvedCliArgs: def parse_args() -> ResolvedCliArgs:
"""Parse command-line arguments""" """Parse command-line arguments"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter description=__doc__, formatter_class=RawDescriptionRichHelpFormatter
) )
parser.add_argument( parser.add_argument(
"seeds", "seeds",
@ -293,7 +300,16 @@ def parse_args() -> ResolvedCliArgs:
print( print(
"Running `cargo build --release` since no test executable was specified..." "Running `cargo build --release` since no test executable was specified..."
) )
subprocess.run(["cargo", "build", "--release"], check=True, capture_output=True) try:
subprocess.run(
["cargo", "build", "--release", "--color", "always"],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
print(e.stderr)
raise
args.test_executable = os.path.join("target", "release", "ruff") args.test_executable = os.path.join("target", "release", "ruff")
assert os.path.exists(args.test_executable) assert os.path.exists(args.test_executable)

View file

@ -1,4 +1,5 @@
pysource-codegen pysource-codegen
pysource-minimize pysource-minimize
rich-argparse
ruff ruff
termcolor termcolor

View file

@ -12,11 +12,14 @@ mdurl==0.1.2
# via markdown-it-py # via markdown-it-py
pygments==2.17.2 pygments==2.17.2
# via rich # via rich
pysource-codegen==0.5.1 pysource-codegen==0.5.2
pysource-minimize==0.6.2 pysource-minimize==0.6.3
rich==13.7.1 rich==13.7.1
# via pysource-minimize # via
ruff==0.4.0 # pysource-minimize
# rich-argparse
rich-argparse==1.4.0
ruff==0.4.2
six==1.16.0 six==1.16.0
# via # via
# asttokens # asttokens