Skip to content

Turn the fuzz-parser script into a properly packaged Python project #14606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 27, 2024
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
5 changes: 3 additions & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
# flake8-pyi
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood

# Script for fuzzing the parser
/scripts/fuzz-parser/ @AlexWaygood
# Script for fuzzing the parser/red-knot etc.
/python/py-fuzzer/ @AlexWaygood

# red-knot
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp
21 changes: 12 additions & 9 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
- crates/ruff_text_size/**
- crates/ruff_python_ast/**
- crates/ruff_python_parser/**
- scripts/fuzz-parser/**
- python/py-fuzzer/**
- .github/workflows/ci.yaml

linter:
Expand Down Expand Up @@ -291,13 +291,7 @@ jobs:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install Python requirements
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
- uses: astral-sh/setup-uv@v4
- uses: actions/download-artifact@v4
name: Download Ruff binary to test
id: download-cached-binary
Expand All @@ -309,7 +303,16 @@ jobs:
# Make executable, since artifact download doesn't preserve this
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff

python scripts/fuzz-parser/fuzz.py --bin ruff 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
(
uv run \
--no-project \
--python=${{ env.PYTHON_VERSION }} \
--with=./python/py-fuzzer \
fuzz \
--test-executable=${{ steps.download-cached-binary.outputs.download-path }}/ruff \
--bin=ruff \
0-500
)

scripts:
name: "test scripts"
Expand Down
20 changes: 12 additions & 8 deletions .github/workflows/daily_fuzz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@ jobs:
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install Python requirements
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
- uses: astral-sh/setup-uv@v4
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
Expand All @@ -49,7 +43,17 @@ jobs:
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
run: cargo build --locked
- name: Fuzz
run: python scripts/fuzz-parser/fuzz.py --bin ruff $(shuf -i 0-9999999999999999999 -n 1000) --test-executable target/debug/ruff
run: |
(
uv run \
--no-project \
--python=3.12 \
--with=./python/py-fuzzer \
fuzz \
--test-executable=target/debug/ruff \
--bin=ruff \
$(shuf -i 0-9999999999999999999 -n 1000)
)

create-issue-on-failure:
name: Create an issue if the daily fuzz surfaced any bugs
Expand Down
13 changes: 4 additions & 9 deletions crates/ruff_python_parser/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,14 @@ cargo test --package ruff_python_parser
The Ruff project includes a Python-based fuzzer that can be used to run the parser on
randomly generated (but syntactically valid) Python source code files.

To run the fuzzer, first install the required dependencies:
To run the fuzzer, execute the following command
(requires [`uv`](https://github.com/astral-sh/uv) to be installed):

```sh
uv pip install -r scripts/fuzz-parser/requirements.txt
uv run --no-project --with ./python/py-fuzzer fuzz
```

Then, run the fuzzer with the following command:

```sh
python scripts/fuzz-parser/fuzz.py
```

Refer to the [fuzz.py](https://github.com/astral-sh/ruff/blob/main/scripts/fuzz-parser/fuzz.py)
Refer to the [py-fuzzer](https://github.com/astral-sh/ruff/blob/main/python/py-fuzzer/fuzz.py)
script for more information or use the `--help` flag to see the available options.

#### CI
Expand Down
8 changes: 8 additions & 0 deletions python/py-fuzzer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# py-fuzzer

A fuzzer script to run Ruff executables on randomly generated
(but syntactically valid) Python source-code files.

Run `uv run --no-project --with ./python/py-fuzzer fuzz -h` from the repository root
for more information and example invocations
(requires [`uv`](https://github.com/astral-sh/uv) to be installed).
36 changes: 18 additions & 18 deletions scripts/fuzz-parser/fuzz.py → python/py-fuzzer/fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
Run a Ruff executable on randomly generated (but syntactically valid)
Python source-code files.

To install all dependencies for this script into an environment using `uv`, run:
uv pip install -r scripts/fuzz-parser/requirements.txt
This script can be installed into a virtual environment using
`uv pip install -e ./python/py-fuzzer` from the Ruff repository root,
or can be run using `uv run --no-project --with ./python/py-fuzzer`
(in which case the virtual environment does not need to be activated).

Example invocations of the script:
Example invocations of the script using `uv`:
- Run the fuzzer on Ruff's parser using seeds 0, 1, 2, 78 and 93 to generate the code:
`python scripts/fuzz-parser/fuzz.py --bin ruff 0-2 78 93`
`uv run --no-project --with ./python/py-fuzzer fuzz --bin ruff 0-2 78 93`
- Run the fuzzer concurrently using seeds in range 0-10 inclusive,
but only reporting bugs that are new on your branch:
`python scripts/fuzz-parser/fuzz.py --bin ruff 0-10 --new-bugs-only`
`uv run --no-project --with ./python/py-fuzzer fuzz --bin ruff 0-10 --new-bugs-only`
- Run the fuzzer concurrently on 10,000 different Python source-code files,
using a random selection of seeds, and only print a summary at the end
(the `shuf` command is Unix-specific):
`python scripts/fuzz-parser/fuzz.py --bin ruff $(shuf -i 0-1000000 -n 10000) --quiet
`uv run --no-project --with ./python/py-fuzzer fuzz --bin ruff $(shuf -i 0-1000000 -n 10000) --quiet
"""

from __future__ import annotations
Expand All @@ -28,11 +30,10 @@
from dataclasses import KW_ONLY, dataclass
from functools import partial
from pathlib import Path
from typing import NewType, assert_never
from typing import NewType, NoReturn, assert_never

from pysource_codegen import generate as generate_random_code
from pysource_minimize import CouldNotMinimize
from pysource_minimize import minimize as minimize_repro
from pysource_minimize import CouldNotMinimize, minimize as minimize_repro
from rich_argparse import RawDescriptionRichHelpFormatter
from termcolor import colored

Expand Down Expand Up @@ -214,7 +215,7 @@ def run_fuzzer_sequentially(args: ResolvedCliArgs) -> list[FuzzResult]:
return bugs


def main(args: ResolvedCliArgs) -> ExitCode:
def run_fuzzer(args: ResolvedCliArgs) -> ExitCode:
if len(args.seeds) <= 5:
bugs = run_fuzzer_sequentially(args)
else:
Expand Down Expand Up @@ -372,12 +373,7 @@ def parse_args() -> ResolvedCliArgs:
executable,
]
try:
subprocess.run(
cmd,
check=True,
capture_output=True,
text=True,
)
subprocess.run(cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(e.stderr)
raise
Expand All @@ -401,6 +397,10 @@ def parse_args() -> ResolvedCliArgs:
)


if __name__ == "__main__":
def main() -> NoReturn:
args = parse_args()
raise SystemExit(main(args))
raise SystemExit(run_fuzzer(args))


if __name__ == "__main__":
main()
79 changes: 79 additions & 0 deletions python/py-fuzzer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
[project]
name = "py-fuzzer"
version = "0.0.0"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pysource-codegen>=0.6.0",
"pysource-minimize>=0.7.0",
"rich-argparse>=1.6.0",
"ruff>=0.8.0",
"termcolor>=2.5.0",
]

[project.scripts]
fuzz = "fuzz:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[dependency-groups]
dev = ["mypy", "ruff"]

[tool.hatch.build.targets.wheel]
include = ["fuzz.py"]

[tool.mypy]
files = "fuzz.py"
pretty = true
strict = true
warn_unreachable = true
local_partial_types = true
enable_error_code = "ignore-without-code,redundant-expr,truthy-bool"

[tool.ruff]
fix = true
preview = true

[tool.ruff.format]
docstring-code-format = true
skip-magic-trailing-comma = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it :)


[tool.ruff.lint]
select = [
"ARG",
"E",
"F",
"B",
"B9",
"C4",
"SIM",
"I",
"UP",
"PIE",
"PGH",
"PYI",
"RUF",
]
ignore = [
# only relevant if you run a script with `python -0`,
"B011",
# These are enforced by, or incompatible with, the ruff formatter:
"E203",
"E501",
# Makes code slower and more verbose
# https://github.com/astral-sh/ruff/issues/7871
"UP038",
]
unfixable = [
"F841", # unused variable. ruff keeps the call, but mostly it's best to get rid of it all
"F601", # automatic fix might obscure issue
"F602", # automatic fix might obscure issue
"B018", # automatic fix might obscure issue
"RUF017", # Ruff's fix is faster, but I prefer using itertools.chain_from_iterable
]

[tool.ruff.lint.isort]
combine-as-imports = true
split-on-trailing-comma = false
Loading
Loading