Skip to content

Commit a677b25

Browse files
committed
test: added script which finds new formatting bugs with pysource-codegen
1 parent 5342d2e commit a677b25

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ repos:
5858
- types-commonmark
5959
- urllib3
6060
- hypothesmith
61+
- pysource-codegen
62+
- pysource-minimize
6163
- id: mypy
6264
name: mypy (Python 3.10)
6365
files: scripts/generate_schema.py

scripts/find_issue.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import random
2+
import re
3+
import subprocess
4+
import sys
5+
import tempfile
6+
import time
7+
from dataclasses import dataclass
8+
from pathlib import Path
9+
from typing import Optional
10+
11+
from pysource_codegen import generate
12+
from pysource_minimize import minimize
13+
14+
import black
15+
16+
base_path = Path(__file__).parent
17+
18+
19+
@dataclass()
20+
class Issue:
21+
src: str
22+
mode: black.FileMode
23+
24+
25+
def bug_in_code(issue: Issue) -> bool:
26+
try:
27+
dst_contents = black.format_str(issue.src, mode=issue.mode)
28+
29+
black.assert_equivalent(issue.src, dst_contents)
30+
black.assert_stable(issue.src, dst_contents, mode=issue.mode)
31+
except Exception as e:
32+
print("error:", e)
33+
return True
34+
return False
35+
36+
37+
def find_issue() -> Optional[Issue]:
38+
t = time.time()
39+
40+
while time.time() - t < 60 * 10:
41+
for line_length in [100, 1]:
42+
mode = black.FileMode(
43+
line_length=line_length,
44+
string_normalization=True,
45+
is_pyi=False,
46+
magic_trailing_comma=False,
47+
)
48+
seed = random.randint(0, 100000000)
49+
src_code = generate(seed)
50+
print("seed:", seed)
51+
52+
issue = Issue(src_code, mode)
53+
54+
if bug_in_code(issue):
55+
return issue
56+
print("no new issue found in 10 minutes")
57+
return None
58+
59+
60+
def minimize_code(issue: Issue) -> Issue:
61+
minimized = Issue(
62+
minimize(issue.src, lambda code: bug_in_code(Issue(code, issue.mode))),
63+
issue.mode,
64+
)
65+
assert bug_in_code(minimized)
66+
67+
print("minimized code:")
68+
print(minimized.src)
69+
70+
return minimized
71+
72+
73+
def mode_to_options(mode: black.FileMode) -> list[str]:
74+
result = ["-l", str(mode.line_length)]
75+
if not mode.magic_trailing_comma:
76+
result.append("-C")
77+
return result
78+
79+
80+
def create_issue(issue: Issue) -> str:
81+
82+
dir = tempfile.TemporaryDirectory()
83+
84+
cwd = Path(dir.name)
85+
(cwd / "bug.py").write_text(issue.src)
86+
87+
multiline_code = "\n".join([repr(s + "\n") for s in issue.src.split("\n")])
88+
89+
parse_code = f"""\
90+
from ast import parse
91+
parse({multiline_code})
92+
"""
93+
cwd = Path(dir.name)
94+
(cwd / "parse_code.py").write_text(parse_code)
95+
command = ["black", *mode_to_options(issue.mode), "bug.py"]
96+
97+
format_result = subprocess.run(
98+
[sys.executable, "-m", *command], capture_output=True, cwd=cwd
99+
)
100+
error_output = format_result.stderr.decode()
101+
102+
m = re.search("This diff might be helpful: (/.*)", error_output)
103+
print(error_output)
104+
print(m)
105+
reported_diff = ""
106+
if m:
107+
path = Path(m[1])
108+
reported_diff = f"""
109+
the reported diff in {path} is:
110+
``` diff
111+
{path.read_text()}
112+
```
113+
"""
114+
print(reported_diff)
115+
116+
run_result = subprocess.run(
117+
[sys.executable, "parse_code.py"], capture_output=True, cwd=cwd
118+
)
119+
120+
git_ref = subprocess.run(
121+
["git", "rev-parse", "origin/main"], capture_output=True
122+
).stdout
123+
124+
return f"""
125+
**Describe the bug**
126+
127+
The following code can not be parsed/formatted by black:
128+
129+
``` python
130+
{issue.src}
131+
```
132+
133+
black reported the following error:
134+
```
135+
> {" ".join(command)}
136+
{format_result.stderr.decode()}
137+
```
138+
{reported_diff}
139+
140+
but it can be parsed by cpython:
141+
``` python
142+
{parse_code}
143+
```
144+
result:
145+
```
146+
{run_result.stderr.decode()}
147+
returncode: {run_result.returncode}
148+
```
149+
150+
151+
**Environment**
152+
153+
<!-- Please complete the following information: -->
154+
155+
- Black's version: current main ({git_ref.decode().strip()})
156+
- OS and Python version: Linux/Python {sys.version}
157+
158+
**Additional context**
159+
160+
The bug was found by pysource-codegen (see #3908)
161+
162+
"""
163+
164+
165+
def main() -> None:
166+
issue = find_issue()
167+
if issue is None:
168+
return
169+
170+
issue = minimize_code(issue)
171+
172+
while issue.mode.line_length > 1 and bug_in_code(issue):
173+
print(issue.mode)
174+
issue.mode.line_length -= 1
175+
176+
issue = minimize_code(issue)
177+
178+
print(create_issue(issue))
179+
180+
181+
if __name__ == "__main__":
182+
main()

test_requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ pytest >= 6.1.1
44
pytest-xdist >= 3.0.2
55
pytest-cov >= 4.1.0
66
tox
7+
pysource_codegen >= 0.4.1
8+
pysource_minimize >= 0.4.0

0 commit comments

Comments
 (0)