Skip to content

Commit 5a81533

Browse files
authored
stdlib compatability checking scripts (#5697)
* stdlib compat checking scripts Signed-off-by: Ashwin Naren <arihant2math@gmail.com> * update output --------- Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
1 parent a18029e commit 5a81533

File tree

4 files changed

+373
-0
lines changed

4 files changed

+373
-0
lines changed

scripts/checklist_template.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% macro display_line(i) %}- {% if i.completed == True %}[x] {% elif i.completed == False %}[ ] {% endif %}{{ i.name }}{% if i.pr != None %} {{ i.pr }}{% endif %}{% endmacro %}
2+
# List of libraries
3+
4+
{% for lib in update_libs %}{{ display_line(lib) }}
5+
{% endfor %}
6+
7+
# List of un-added libraries
8+
These libraries are not added yet. Pure python one will be possible while others are not.
9+
10+
{% for lib in add_libs %}{{ display_line(lib) }}
11+
{% endfor %}
12+
13+
# List of tests without python libraries
14+
15+
{% for lib in update_tests %}{{ display_line(lib) }}
16+
{% endfor %}
17+
18+
# List of un-added tests without python libraries
19+
20+
{% for lib in add_tests %}{{ display_line(lib) }}
21+
{% endfor %}

scripts/find_eq.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Run differential queries to find equivalent files in cpython and rustpython
2+
# Arguments
3+
# --cpython: Path to cpython source code
4+
# --print-diff: Print the diff between the files
5+
# --color: Output color
6+
# --files: Optional globbing pattern to match files in cpython source code
7+
# --checklist: output as checklist
8+
9+
import argparse
10+
import difflib
11+
import pathlib
12+
13+
parser = argparse.ArgumentParser(description="Find equivalent files in cpython and rustpython")
14+
parser.add_argument("--cpython", type=pathlib.Path, required=True, help="Path to cpython source code")
15+
parser.add_argument("--print-diff", action="store_true", help="Print the diff between the files")
16+
parser.add_argument("--color", action="store_true", help="Output color")
17+
parser.add_argument("--files", type=str, default="*.py", help="Optional globbing pattern to match files in cpython source code")
18+
19+
args = parser.parse_args()
20+
21+
if not args.cpython.exists():
22+
raise FileNotFoundError(f"Path {args.cpython} does not exist")
23+
if not args.cpython.is_dir():
24+
raise NotADirectoryError(f"Path {args.cpython} is not a directory")
25+
if not args.cpython.is_absolute():
26+
args.cpython = args.cpython.resolve()
27+
28+
cpython_lib = args.cpython / "Lib"
29+
rustpython_lib = pathlib.Path(__file__).parent.parent / "Lib"
30+
assert rustpython_lib.exists(), "RustPython lib directory does not exist, ensure the find_eq.py script is located in the right place"
31+
32+
# walk through the cpython lib directory
33+
cpython_files = []
34+
for path in cpython_lib.rglob(args.files):
35+
if path.is_file():
36+
# remove the cpython lib path from the file path
37+
path = path.relative_to(cpython_lib)
38+
cpython_files.append(path)
39+
40+
for path in cpython_files:
41+
# check if the file exists in the rustpython lib directory
42+
rustpython_path = rustpython_lib / path
43+
if rustpython_path.exists():
44+
# open both files and compare them
45+
try:
46+
with open(cpython_lib / path, "r") as cpython_file:
47+
cpython_code = cpython_file.read()
48+
with open(rustpython_lib / path, "r") as rustpython_file:
49+
rustpython_code = rustpython_file.read()
50+
# compare the files
51+
diff = difflib.unified_diff(cpython_code.splitlines(), rustpython_code.splitlines(), lineterm="", fromfile=str(path), tofile=str(path))
52+
# print the diff if there are differences
53+
diff = list(diff)
54+
if len(diff) > 0:
55+
if args.print_diff:
56+
print("Differences:")
57+
for line in diff:
58+
print(line)
59+
else:
60+
print(f"File is not identical: {path}")
61+
else:
62+
print(f"File is identical: {path}")
63+
except Exception as e:
64+
print(f"Unable to check file {path}: {e}")
65+
else:
66+
print(f"File not found in RustPython: {path}")
67+
68+
# check for files in rustpython lib directory that are not in cpython lib directory
69+
rustpython_files = []
70+
for path in rustpython_lib.rglob(args.files):
71+
if path.is_file():
72+
# remove the rustpython lib path from the file path
73+
path = path.relative_to(rustpython_lib)
74+
rustpython_files.append(path)
75+
if path not in cpython_files:
76+
print(f"File not found in CPython: {path}")

scripts/generate_checklist.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# Arguments
2+
# --cpython: Path to cpython source code
3+
# --updated-libs: Libraries that have been updated in RustPython
4+
5+
6+
import argparse
7+
import dataclasses
8+
import difflib
9+
import pathlib
10+
from typing import Optional
11+
import warnings
12+
13+
import requests
14+
from jinja2 import Environment, FileSystemLoader
15+
16+
parser = argparse.ArgumentParser(description="Find equivalent files in cpython and rustpython")
17+
parser.add_argument("--cpython", type=pathlib.Path, required=True, help="Path to cpython source code")
18+
parser.add_argument("--notes", type=pathlib.Path, required=False, help="Path to notes file")
19+
20+
args = parser.parse_args()
21+
22+
def check_pr(pr_id: str) -> bool:
23+
if pr_id.startswith("#"):
24+
pr_id = pr_id[1:]
25+
int_pr_id = int(pr_id)
26+
req = f"https://api.github.com/repos/RustPython/RustPython/pulls/{int_pr_id}"
27+
response = requests.get(req).json()
28+
return response["merged_at"] is not None
29+
30+
@dataclasses.dataclass
31+
class LibUpdate:
32+
pr: Optional[str] = None
33+
done: bool = True
34+
35+
def parse_updated_lib_issue(issue_body: str) -> dict[str, LibUpdate]:
36+
lines = issue_body.splitlines()
37+
updated_libs = {}
38+
for line in lines:
39+
if line.strip().startswith("- "):
40+
line = line.strip()[2:]
41+
out = line.split(" ")
42+
out = [x for x in out if x]
43+
assert len(out) < 3
44+
if len(out) == 1:
45+
updated_libs[out[0]] = LibUpdate()
46+
elif len(out) == 2:
47+
updated_libs[out[0]] = LibUpdate(out[1], check_pr(out[1]))
48+
return updated_libs
49+
50+
def get_updated_libs() -> dict[str, LibUpdate]:
51+
issue_id = "5736"
52+
req = f"https://api.github.com/repos/RustPython/RustPython/issues/{issue_id}"
53+
response = requests.get(req).json()
54+
return parse_updated_lib_issue(response["body"])
55+
56+
updated_libs = get_updated_libs()
57+
58+
if not args.cpython.exists():
59+
raise FileNotFoundError(f"Path {args.cpython} does not exist")
60+
if not args.cpython.is_dir():
61+
raise NotADirectoryError(f"Path {args.cpython} is not a directory")
62+
if not args.cpython.is_absolute():
63+
args.cpython = args.cpython.resolve()
64+
65+
notes: dict = {}
66+
if args.notes:
67+
# check if the file exists in the rustpython lib directory
68+
notes_path = args.notes
69+
if notes_path.exists():
70+
with open(notes_path) as f:
71+
for line in f:
72+
line = line.strip()
73+
if not line.startswith("//") and line:
74+
line_split = line.split(" ")
75+
if len(line_split) > 1:
76+
rest = " ".join(line_split[1:])
77+
if line_split[0] in notes:
78+
notes[line_split[0]].append(rest)
79+
else:
80+
notes[line_split[0]] = [rest]
81+
else:
82+
raise ValueError(f"Invalid note: {line}")
83+
84+
else:
85+
raise FileNotFoundError(f"Path {notes_path} does not exist")
86+
87+
cpython_lib = args.cpython / "Lib"
88+
rustpython_lib = pathlib.Path(__file__).parent.parent / "Lib"
89+
assert rustpython_lib.exists(), "RustPython lib directory does not exist, ensure the find_eq.py script is located in the right place"
90+
91+
ignored_objs = [
92+
"__pycache__",
93+
"test"
94+
]
95+
# loop through the top-level directories in the cpython lib directory
96+
libs = []
97+
for path in cpython_lib.iterdir():
98+
if path.is_dir() and path.name not in ignored_objs:
99+
# add the directory name to the list of libraries
100+
libs.append(path.name)
101+
elif path.is_file() and path.name.endswith(".py") and path.name not in ignored_objs:
102+
# add the file name to the list of libraries
103+
libs.append(path.name)
104+
105+
tests = []
106+
cpython_lib_test = cpython_lib / "test"
107+
for path in cpython_lib_test.iterdir():
108+
if path.is_dir() and path.name not in ignored_objs and path.name.startswith("test_"):
109+
# add the directory name to the list of libraries
110+
tests.append(path.name)
111+
elif path.is_file() and path.name.endswith(".py") and path.name not in ignored_objs and path.name.startswith("test_"):
112+
# add the file name to the list of libraries
113+
file_name = path.name.replace("test_", "")
114+
if file_name not in libs and file_name.replace(".py", "") not in libs:
115+
tests.append(path.name)
116+
117+
def check_diff(file1, file2):
118+
try:
119+
with open(file1, "r") as f1, open(file2, "r") as f2:
120+
f1_lines = f1.readlines()
121+
f2_lines = f2.readlines()
122+
diff = difflib.unified_diff(f1_lines, f2_lines, lineterm="")
123+
diff_lines = list(diff)
124+
return len(diff_lines)
125+
except UnicodeDecodeError:
126+
return False
127+
128+
def check_completion_pr(display_name):
129+
for lib in updated_libs:
130+
if lib == str(display_name):
131+
return updated_libs[lib].done, updated_libs[lib].pr
132+
return False, None
133+
134+
def check_test_completion(rustpython_path, cpython_path):
135+
if rustpython_path.exists() and rustpython_path.is_file():
136+
if cpython_path.exists() and cpython_path.is_file():
137+
if not rustpython_path.exists() or not rustpython_path.is_file():
138+
return False
139+
elif check_diff(rustpython_path, cpython_path) > 0:
140+
return False
141+
return True
142+
return False
143+
144+
def check_lib_completion(rustpython_path, cpython_path):
145+
test_name = "test_" + rustpython_path.name
146+
rustpython_test_path = rustpython_lib / "test" / test_name
147+
cpython_test_path = cpython_lib / "test" / test_name
148+
if cpython_test_path.exists() and not check_test_completion(rustpython_test_path, cpython_test_path):
149+
return False
150+
if rustpython_path.exists() and rustpython_path.is_file():
151+
if check_diff(rustpython_path, cpython_path) > 0:
152+
return False
153+
return True
154+
return False
155+
156+
def handle_notes(display_path) -> list[str]:
157+
if str(display_path) in notes:
158+
res = notes[str(display_path)]
159+
# remove the note from the notes list
160+
del notes[str(display_path)]
161+
return res
162+
return []
163+
164+
@dataclasses.dataclass
165+
class Output:
166+
name: str
167+
pr: Optional[str]
168+
completed: Optional[bool]
169+
notes: list[str]
170+
171+
update_libs_output = []
172+
add_libs_output = []
173+
for path in libs:
174+
# check if the file exists in the rustpython lib directory
175+
rustpython_path = rustpython_lib / path
176+
# remove the file extension if it exists
177+
display_path = pathlib.Path(path).with_suffix("")
178+
(completed, pr) = check_completion_pr(display_path)
179+
if rustpython_path.exists():
180+
if not completed:
181+
# check if the file exists in the cpython lib directory
182+
cpython_path = cpython_lib / path
183+
# check if the file exists in the rustpython lib directory
184+
if rustpython_path.exists() and rustpython_path.is_file():
185+
completed = check_lib_completion(rustpython_path, cpython_path)
186+
update_libs_output.append(Output(str(display_path), pr, completed, handle_notes(display_path)))
187+
else:
188+
if pr is not None and completed:
189+
update_libs_output.append(Output(str(display_path), pr, None, handle_notes(display_path)))
190+
else:
191+
add_libs_output.append(Output(str(display_path), pr, None, handle_notes(display_path)))
192+
193+
update_tests_output = []
194+
add_tests_output = []
195+
for path in tests:
196+
# check if the file exists in the rustpython lib directory
197+
rustpython_path = rustpython_lib / "test" / path
198+
# remove the file extension if it exists
199+
display_path = pathlib.Path(path).with_suffix("")
200+
(completed, pr) = check_completion_pr(display_path)
201+
if rustpython_path.exists():
202+
if not completed:
203+
# check if the file exists in the cpython lib directory
204+
cpython_path = cpython_lib / "test" / path
205+
# check if the file exists in the rustpython lib directory
206+
if rustpython_path.exists() and rustpython_path.is_file():
207+
completed = check_lib_completion(rustpython_path, cpython_path)
208+
update_tests_output.append(Output(str(display_path), pr, completed, handle_notes(display_path)))
209+
else:
210+
if pr is not None and completed:
211+
update_tests_output.append(Output(str(display_path), pr, None, handle_notes(display_path)))
212+
else:
213+
add_tests_output.append(Output(str(display_path), pr, None, handle_notes(display_path)))
214+
215+
for note in notes:
216+
# add a warning for each note that is not attached to a file
217+
for n in notes[note]:
218+
warnings.warn(f"Unattached Note: {note} - {n}")
219+
220+
env = Environment(loader=FileSystemLoader('.'))
221+
template = env.get_template("checklist_template.md")
222+
output = template.render(
223+
update_libs=update_libs_output,
224+
add_libs=add_libs_output,
225+
update_tests=update_tests_output,
226+
add_tests=add_tests_output
227+
)
228+
print(output)

scripts/notes.txt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
__future__ Related test is `test_future_stmt`
2+
abc `_collections_abc.py`
3+
abc `_py_abc.py`
4+
code Related test is `test_code_module`
5+
codecs `_pycodecs.py`
6+
collections See also #3418
7+
ctypes #5572
8+
datetime `_pydatetime.py`
9+
decimal `_pydecimal.py`
10+
dis See also #3846
11+
importlib #4565
12+
io `_pyio.py`
13+
io #3960
14+
io #4702
15+
locale #3850
16+
mailbox #4072
17+
multiprocessing #3965
18+
os Blocker: Some tests requires async comprehension
19+
os #3960
20+
os #4053
21+
pickle #3876
22+
pickle `_compat_pickle.py`
23+
pickle `test/pickletester.py` supports `test_pickle.py`
24+
pickle `test/test_picklebuffer.py`
25+
pydoc `pydoc_data`
26+
queue See also #3608
27+
re Don't forget sre files `sre_compile.py`, `sre_constants.py`, `sre_parse.py`
28+
shutil #3960
29+
site Don't forget `_sitebuiltins.py`
30+
venv #3960
31+
warnings #4013
32+
33+
// test
34+
35+
test_array #3876
36+
test_gc #4158
37+
test_marshal #3458
38+
test_mmap #3847
39+
test_posix #4496
40+
test_property #3430
41+
test_set #3992
42+
test_structseq #4063
43+
test_super #3865
44+
test_support #4538
45+
test_syntax #4469
46+
test_sys #4541
47+
test_time #3850
48+
test_time #4157

0 commit comments

Comments
 (0)