Skip to content

Commit 65f0399

Browse files
borisyouknowone
andcommitted
Convert whats_left into a CPython script
Co-Authored-By: Jeong YunWon <jeong@youknowone.org>
1 parent 9e9d610 commit 65f0399

File tree

2 files changed

+119
-87
lines changed

2 files changed

+119
-87
lines changed

extra_tests/not_impl_gen.py renamed to whats_left.py

Lines changed: 119 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,56 @@
1-
# It's recommended to run this with `python3 -I not_impl_gen.py`, to make sure
2-
# that nothing in your global Python environment interferes with what's being
3-
# extracted here.
4-
#
1+
#!/usr/bin/env python3 -I
2+
53
# This script generates Lib/snippets/whats_left_data.py with these variables defined:
64
# expected_methods - a dictionary mapping builtin objects to their methods
75
# cpymods - a dictionary mapping module names to their contents
86
# libdir - the location of RustPython's Lib/ directory.
97

10-
import inspect
11-
import io
8+
#
9+
# TODO: include this:
10+
# which finds all modules it has available and
11+
# creates a Python dictionary mapping module names to their contents, which is
12+
# in turn used to generate a second Python script that also finds which modules
13+
# it has available and compares that against the first dictionary we generated.
14+
# We then run this second generated script with RustPython.
15+
16+
import argparse
17+
import re
1218
import os
1319
import re
1420
import sys
21+
import json
1522
import warnings
23+
import inspect
24+
import subprocess
25+
import platform
1626
from pydoc import ModuleScanner
1727

28+
GENERATED_FILE = "extra_tests/snippets/not_impl.py"
29+
30+
implementation = platform.python_implementation()
31+
if implementation != "CPython":
32+
sys.exit("whats_left.py must be run under CPython, got {implementation} instead")
33+
34+
35+
def parse_args():
36+
parser = argparse.ArgumentParser(description="Process some integers.")
37+
parser.add_argument(
38+
"--signature",
39+
action="store_true",
40+
help="print functions whose signatures don't match CPython's",
41+
)
42+
parser.add_argument(
43+
"--json",
44+
action="store_true",
45+
help="print output as JSON (instead of line by line)",
46+
)
47+
48+
args = parser.parse_args()
49+
return args
50+
51+
52+
args = parse_args()
53+
1854

1955
# modules suggested for deprecation by PEP 594 (www.python.org/dev/peps/pep-0594/)
2056
# some of these might be implemented, but they are not a priority
@@ -54,7 +90,7 @@
5490
'_testbuffer', '_testcapi', '_testimportmultiple', '_testinternalcapi', '_testmultiphase',
5591
}
5692

57-
IGNORED_MODULES = {'this', 'antigravity'} | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
93+
IGNORED_MODULES = {"this", "antigravity"} | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
5894

5995
sys.path = [
6096
path
@@ -188,6 +224,7 @@ def onerror(modname):
188224
def import_module(module_name):
189225
import io
190226
from contextlib import redirect_stdout
227+
191228
# Importing modules causes ('Constant String', 2, None, 4) and
192229
# "Hello world!" to be printed to stdout.
193230
f = io.StringIO()
@@ -203,6 +240,7 @@ def import_module(module_name):
203240

204241
def is_child(module, item):
205242
import inspect
243+
206244
item_mod = inspect.getmodule(item)
207245
return item_mod is module
208246

@@ -250,7 +288,7 @@ def gen_modules():
250288
output += gen_methods()
251289
output += f"""
252290
cpymods = {gen_modules()!r}
253-
libdir = {os.path.abspath("../Lib/").encode('utf8')!r}
291+
libdir = {os.path.abspath("Lib/").encode('utf8')!r}
254292
255293
"""
256294

@@ -260,7 +298,7 @@ def gen_modules():
260298
extra_info,
261299
dir_of_mod_or_error,
262300
import_module,
263-
is_child
301+
is_child,
264302
]
265303
for fn in REUSED:
266304
output += "".join(inspect.getsourcelines(fn)[0]) + "\n\n"
@@ -278,7 +316,7 @@ def compare():
278316
import sys
279317
import warnings
280318
from contextlib import redirect_stdout
281-
319+
import json
282320
import platform
283321

284322
def method_incompatability_reason(typ, method_name, real_method_value):
@@ -288,7 +326,7 @@ def method_incompatability_reason(typ, method_name, real_method_value):
288326

289327
is_inherited = not attr_is_not_inherited(typ, method_name)
290328
if is_inherited:
291-
return "inherited"
329+
return "(inherited)"
292330

293331
value = extra_info(getattr(typ, method_name))
294332
if value != real_method_value:
@@ -321,16 +359,20 @@ def method_incompatability_reason(typ, method_name, real_method_value):
321359

322360
rustpymods = {mod: dir_of_mod_or_error(mod) for mod in mod_names}
323361

324-
not_implemented = {}
325-
failed_to_import = {}
326-
missing_items = {}
327-
mismatched_items = {}
362+
result = {
363+
"cpython_modules": {},
364+
"implemented": {},
365+
"not_implemented": {},
366+
"failed_to_import": {},
367+
"missing_items": {},
368+
"mismatched_items": {},
369+
}
328370
for modname, cpymod in cpymods.items():
329371
rustpymod = rustpymods.get(modname)
330372
if rustpymod is None:
331-
not_implemented[modname] = None
373+
result["not_implemented"][modname] = None
332374
elif isinstance(rustpymod, Exception):
333-
failed_to_import[modname] = rustpymod
375+
result["failed_to_import"][modname] = rustpymod.__class__.__name__ + str(rustpymod)
334376
else:
335377
implemented_items = sorted(set(cpymod) & set(rustpymod))
336378
mod_missing_items = set(cpymod) - set(rustpymod)
@@ -343,48 +385,18 @@ def method_incompatability_reason(typ, method_name, real_method_value):
343385
if rustpymod[item] != cpymod[item]
344386
and not isinstance(cpymod[item], Exception)
345387
]
346-
if mod_missing_items:
347-
missing_items[modname] = mod_missing_items
348-
if mod_mismatched_items:
349-
mismatched_items[modname] = mod_mismatched_items
350-
351-
# missing entire module
352-
print("# modules")
353-
for modname in not_implemented:
354-
print(modname, "(entire module)")
355-
for modname, exception in failed_to_import.items():
356-
print(f"{modname} (exists but not importable: {exception})")
357-
358-
# missing from builtins
359-
print("\n# builtin items")
360-
for module, missing_methods in not_implementeds.items():
361-
for method, reason in missing_methods.items():
362-
print(f"{module}.{method}" + (f" ({reason})" if reason else ""))
363-
364-
# missing from modules
365-
print("\n# stdlib items")
366-
for modname, missing in missing_items.items():
367-
for item in missing:
368-
print(item)
369-
370-
print("\n# mismatching signatures (warnings)")
371-
for modname, mismatched in mismatched_items.items():
372-
for (item, rustpy_value, cpython_value) in mismatched:
373-
if cpython_value == "ValueError('no signature found')":
374-
continue # these items will never match
375-
print(f"{item} {rustpy_value} != {cpython_value}")
388+
if mod_missing_items or mod_mismatched_items:
389+
if mod_missing_items:
390+
result["missing_items"][modname] = mod_missing_items
391+
if mod_mismatched_items:
392+
result["mismatched_items"][modname] = mod_mismatched_items
393+
else:
394+
result["implemented"][modname] = None
376395

377-
result = {
378-
"not_implemented": not_implemented,
379-
"failed_to_import": failed_to_import,
380-
"missing_items": missing_items,
381-
"mismatched_items": mismatched_items,
382-
}
396+
result["cpython_modules"] = cpymods
397+
result["not_implementeds"] = not_implementeds
383398

384-
print()
385-
print("# out of", len(cpymods), "modules:")
386-
for error_type, modules in result.items():
387-
print("# ", error_type, len(modules))
399+
print(json.dumps(result))
388400

389401

390402
def remove_one_indent(s):
@@ -395,5 +407,54 @@ def remove_one_indent(s):
395407
compare_src = inspect.getsourcelines(compare)[0][1:]
396408
output += "".join(remove_one_indent(line) for line in compare_src)
397409

398-
with open("not_impl.py", "w") as f:
410+
with open(GENERATED_FILE, "w") as f:
399411
f.write(output + "\n")
412+
413+
414+
subprocess.run(["cargo", "build", "--release", "--features=ssl"], check=True)
415+
result = subprocess.run(
416+
["cargo", "run", "--release", "--features=ssl", "-q", "--", GENERATED_FILE],
417+
env={**os.environ.copy(), "RUSTPYTHONPATH": "Lib"},
418+
text=True,
419+
capture_output=True,
420+
)
421+
# The last line should be json output, the rest of the lines can contain noise
422+
# because importing certain modules can print stuff to stdout/stderr
423+
result = json.loads(result.stdout.splitlines()[-1])
424+
425+
if args.json:
426+
print(json.dumps(result))
427+
sys.exit()
428+
429+
430+
# missing entire modules
431+
print("# modules")
432+
for modname in result["not_implemented"]:
433+
print(modname, "(entire module)")
434+
for modname, exception in result["failed_to_import"].items():
435+
print(f"{modname} (exists but not importable: {exception})")
436+
437+
# missing from builtins
438+
print("\n# builtin items")
439+
for module, missing_methods in result["not_implementeds"].items():
440+
for method, reason in missing_methods.items():
441+
print(f"{module}.{method}" + (f" {reason}" if reason else ""))
442+
443+
# missing from modules
444+
print("\n# stdlib items")
445+
for modname, missing in result["missing_items"].items():
446+
for item in missing:
447+
print(item)
448+
449+
if args.signature:
450+
print("\n# mismatching signatures (warnings)")
451+
for modname, mismatched in result["mismatched_items"].items():
452+
for (item, rustpy_value, cpython_value) in mismatched:
453+
if cpython_value == "ValueError('no signature found')":
454+
continue # these items will never match
455+
print(f"{item} {rustpy_value} != {cpython_value}")
456+
457+
print()
458+
print("# summary")
459+
for error_type, modules in result.items():
460+
print("# ", error_type, len(modules))

whats_left.sh

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)