Skip to content

Commit 15307de

Browse files
committed
Property-based fuzz test
1 parent 37a0020 commit 15307de

File tree

4 files changed

+83
-0
lines changed

4 files changed

+83
-0
lines changed

.github/workflows/fuzz.yml

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

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
dst_contents = black.format_str(src_contents, mode=mode)
41+
42+
# And check that we got equivalent and stable output.
43+
black.assert_equivalent(src_contents, dst_contents)
44+
black.assert_stable(src_contents, dst_contents, mode=mode)
45+
46+
# Future test: check that pure-python and mypyc versions of black
47+
# give identical output for identical input?
48+
49+
50+
if __name__ == "__main__":
51+
test_idempotent_any_syntatically_valid_python()

0 commit comments

Comments
 (0)