Skip to content

Commit 0dc7694

Browse files
committed
Property-based fuzz test
1 parent 37a0020 commit 0dc7694

File tree

4 files changed

+90
-0
lines changed

4 files changed

+90
-0
lines changed

.github/workflows/fuzz.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Fuzz
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
fail-fast: false
10+
matrix:
11+
python-version: [3.6, 3.7, 3.8]
12+
13+
steps:
14+
- uses: actions/checkout@v2
15+
16+
- name: Set up Python ${{ matrix.python-version }}
17+
uses: actions/setup-python@v2
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
python -m pip install --upgrade coverage
25+
python -m pip install --upgrade hypothesmith
26+
python -m pip install -e ".[d]"
27+
28+
- name: Run fuzz tests
29+
run: |
30+
coverage run fuzz.py
31+
coverage report

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ src/_black_version.py
1414
.eggs
1515
.dmypy.json
1616
*.swp
17+
.hypothesis/

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -684,3 +684,4 @@ Multiple contributions by:
684684
- Yazdan
685685
- [Yngve Høiseth](mailto:[email protected])
686686
- [Yurii Karabas](mailto:[email protected])
687+
- [Zac Hatfield-Dodds](mailto:[email protected])

fuzz.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Property-based tests for Black.
2+
3+
By Zac Hatfield-Dodds, based on my Hypothesmith tool for source code
4+
generation. You can run this file with `python`, `pytest`, or (soon)
5+
a coverage-guided fuzzer I'm working on.
6+
"""
7+
8+
import hypothesmith
9+
from hypothesis import HealthCheck, given, settings, strategies as st
10+
11+
import black
12+
13+
14+
# This test uses the Hypothesis and Hypothesmith libraries to generate random
15+
# syntatically-valid Python source code and run Black in odd modes.
16+
@settings(
17+
max_examples=1000, # roughly 1k tests/minute, or half that under coverage
18+
derandomize=True, # deterministic mode to avoid CI flakiness
19+
deadline=None, # ignore Hypothesis' health checks; we already know that
20+
suppress_health_check=HealthCheck.all(), # this is slow and filter-heavy.
21+
)
22+
@given(
23+
# Note that while Hypothesmith might generate code unlike that written by
24+
# humans, it's a general test that should pass for any *valid* source code.
25+
# (so e.g. running it against code scraped of the internet might also help)
26+
src_contents=hypothesmith.from_grammar() | hypothesmith.from_node(),
27+
# Using randomly-varied modes helps us to exercise less common code paths.
28+
mode=st.builds(
29+
black.FileMode,
30+
line_length=st.just(88) | st.integers(0, 200),
31+
string_normalization=st.booleans(),
32+
is_pyi=st.booleans(),
33+
),
34+
)
35+
def test_idempotent_any_syntatically_valid_python(src_contents, mode):
36+
# Before starting, let's confirm that the input string is valid Python:
37+
compile(src_contents, "<string>", "exec") # else the bug is in hypothesmith
38+
39+
# Then format the code...
40+
try:
41+
dst_contents = black.format_str(src_contents, mode=mode)
42+
except black.InvalidInput:
43+
# This is a bug - if it's valid Python code, as above, black should be
44+
# able to code with it. See issues #970, #1012, #1358, and #1557.
45+
# TODO: remove this try-except block when issues are resolved.
46+
return
47+
48+
# And check that we got equivalent and stable output.
49+
black.assert_equivalent(src_contents, dst_contents)
50+
black.assert_stable(src_contents, dst_contents, mode=mode)
51+
52+
# Future test: check that pure-python and mypyc versions of black
53+
# give identical output for identical input?
54+
55+
56+
if __name__ == "__main__":
57+
test_idempotent_any_syntatically_valid_python()

0 commit comments

Comments
 (0)