Skip to content

Commit c5d6ef1

Browse files
authored
Merge pull request #2448 from verhovsky/turn-whats-left-into-python-script
Convert whats_left into a CPython script
2 parents 9e9d610 + c5ab11b commit c5d6ef1

File tree

4 files changed

+127
-90
lines changed

4 files changed

+127
-90
lines changed

.github/workflows/cron-ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ jobs:
8383
run: cargo build --release --verbose
8484
- name: Collect what is left data
8585
run: |
86-
chmod +x ./whats_left.sh
87-
./whats_left.sh > whats_left.temp
86+
chmod +x ./whats_left.py
87+
./whats_left.py > whats_left.temp
8888
env:
8989
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
9090
- name: Upload data to the website

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ To enhance CPython compatibility, try to increase unittest coverage by checking
198198
Another approach is to checkout the source code: builtin functions and object
199199
methods are often the simplest and easiest way to contribute.
200200

201-
You can also simply run `./whats_left.sh` to assist in finding any unimplemented
201+
You can also simply run `./whats_left.py` to assist in finding any unimplemented
202202
method.
203203

204204
## Compiling to WebAssembly

extra_tests/not_impl_gen.py renamed to whats_left.py

Lines changed: 124 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,61 @@
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+
if not sys.flags.isolated:
29+
print("running without -I option.")
30+
print("python -I whats_left.py")
31+
exit(1)
32+
33+
GENERATED_FILE = "extra_tests/snippets/not_impl.py"
34+
35+
implementation = platform.python_implementation()
36+
if implementation != "CPython":
37+
sys.exit("whats_left.py must be run under CPython, got {implementation} instead")
38+
39+
40+
def parse_args():
41+
parser = argparse.ArgumentParser(description="Process some integers.")
42+
parser.add_argument(
43+
"--signature",
44+
action="store_true",
45+
help="print functions whose signatures don't match CPython's",
46+
)
47+
parser.add_argument(
48+
"--json",
49+
action="store_true",
50+
help="print output as JSON (instead of line by line)",
51+
)
52+
53+
args = parser.parse_args()
54+
return args
55+
56+
57+
args = parse_args()
58+
1859

1960
# modules suggested for deprecation by PEP 594 (www.python.org/dev/peps/pep-0594/)
2061
# some of these might be implemented, but they are not a priority
@@ -54,7 +95,7 @@
5495
'_testbuffer', '_testcapi', '_testimportmultiple', '_testinternalcapi', '_testmultiphase',
5596
}
5697

57-
IGNORED_MODULES = {'this', 'antigravity'} | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
98+
IGNORED_MODULES = {"this", "antigravity"} | PEP_594_MODULES | CPYTHON_SPECIFIC_MODS
5899

59100
sys.path = [
60101
path
@@ -188,6 +229,7 @@ def onerror(modname):
188229
def import_module(module_name):
189230
import io
190231
from contextlib import redirect_stdout
232+
191233
# Importing modules causes ('Constant String', 2, None, 4) and
192234
# "Hello world!" to be printed to stdout.
193235
f = io.StringIO()
@@ -203,6 +245,7 @@ def import_module(module_name):
203245

204246
def is_child(module, item):
205247
import inspect
248+
206249
item_mod = inspect.getmodule(item)
207250
return item_mod is module
208251

@@ -250,7 +293,7 @@ def gen_modules():
250293
output += gen_methods()
251294
output += f"""
252295
cpymods = {gen_modules()!r}
253-
libdir = {os.path.abspath("../Lib/").encode('utf8')!r}
296+
libdir = {os.path.abspath("Lib/").encode('utf8')!r}
254297
255298
"""
256299

@@ -260,7 +303,7 @@ def gen_modules():
260303
extra_info,
261304
dir_of_mod_or_error,
262305
import_module,
263-
is_child
306+
is_child,
264307
]
265308
for fn in REUSED:
266309
output += "".join(inspect.getsourcelines(fn)[0]) + "\n\n"
@@ -278,7 +321,7 @@ def compare():
278321
import sys
279322
import warnings
280323
from contextlib import redirect_stdout
281-
324+
import json
282325
import platform
283326

284327
def method_incompatability_reason(typ, method_name, real_method_value):
@@ -288,7 +331,7 @@ def method_incompatability_reason(typ, method_name, real_method_value):
288331

289332
is_inherited = not attr_is_not_inherited(typ, method_name)
290333
if is_inherited:
291-
return "inherited"
334+
return "(inherited)"
292335

293336
value = extra_info(getattr(typ, method_name))
294337
if value != real_method_value:
@@ -321,16 +364,20 @@ def method_incompatability_reason(typ, method_name, real_method_value):
321364

322365
rustpymods = {mod: dir_of_mod_or_error(mod) for mod in mod_names}
323366

324-
not_implemented = {}
325-
failed_to_import = {}
326-
missing_items = {}
327-
mismatched_items = {}
367+
result = {
368+
"cpython_modules": {},
369+
"implemented": {},
370+
"not_implemented": {},
371+
"failed_to_import": {},
372+
"missing_items": {},
373+
"mismatched_items": {},
374+
}
328375
for modname, cpymod in cpymods.items():
329376
rustpymod = rustpymods.get(modname)
330377
if rustpymod is None:
331-
not_implemented[modname] = None
378+
result["not_implemented"][modname] = None
332379
elif isinstance(rustpymod, Exception):
333-
failed_to_import[modname] = rustpymod
380+
result["failed_to_import"][modname] = rustpymod.__class__.__name__ + str(rustpymod)
334381
else:
335382
implemented_items = sorted(set(cpymod) & set(rustpymod))
336383
mod_missing_items = set(cpymod) - set(rustpymod)
@@ -343,48 +390,18 @@ def method_incompatability_reason(typ, method_name, real_method_value):
343390
if rustpymod[item] != cpymod[item]
344391
and not isinstance(cpymod[item], Exception)
345392
]
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}")
393+
if mod_missing_items or mod_mismatched_items:
394+
if mod_missing_items:
395+
result["missing_items"][modname] = mod_missing_items
396+
if mod_mismatched_items:
397+
result["mismatched_items"][modname] = mod_mismatched_items
398+
else:
399+
result["implemented"][modname] = None
376400

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-
}
401+
result["cpython_modules"] = cpymods
402+
result["not_implementeds"] = not_implementeds
383403

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

389406

390407
def remove_one_indent(s):
@@ -395,5 +412,54 @@ def remove_one_indent(s):
395412
compare_src = inspect.getsourcelines(compare)[0][1:]
396413
output += "".join(remove_one_indent(line) for line in compare_src)
397414

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

whats_left.sh

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

0 commit comments

Comments
 (0)