Skip to content

Commit 490e0ad

Browse files
gh-117482: Fix the Slot Wrapper Inheritance Tests (gh-122248)
The tests were only checking cases where the slot wrapper was present in the initial case. They were missing when the slot wrapper was added in the additional initializations. This fixes that.
1 parent 89fa05f commit 490e0ad

File tree

4 files changed

+143
-48
lines changed

4 files changed

+143
-48
lines changed

Lib/test/support/__init__.py

+55
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,61 @@ def copy_python_src_ignore(path, names):
26082608
return ignored
26092609

26102610

2611+
def iter_builtin_types():
2612+
for obj in __builtins__.values():
2613+
if not isinstance(obj, type):
2614+
continue
2615+
cls = obj
2616+
if cls.__module__ != 'builtins':
2617+
continue
2618+
yield cls
2619+
2620+
2621+
def iter_slot_wrappers(cls):
2622+
assert cls.__module__ == 'builtins', cls
2623+
2624+
def is_slot_wrapper(name, value):
2625+
if not isinstance(value, types.WrapperDescriptorType):
2626+
assert not repr(value).startswith('<slot wrapper '), (cls, name, value)
2627+
return False
2628+
assert repr(value).startswith('<slot wrapper '), (cls, name, value)
2629+
assert callable(value), (cls, name, value)
2630+
assert name.startswith('__') and name.endswith('__'), (cls, name, value)
2631+
return True
2632+
2633+
ns = vars(cls)
2634+
unused = set(ns)
2635+
for name in dir(cls):
2636+
if name in ns:
2637+
unused.remove(name)
2638+
2639+
try:
2640+
value = getattr(cls, name)
2641+
except AttributeError:
2642+
# It's as though it weren't in __dir__.
2643+
assert name in ('__annotate__', '__annotations__', '__abstractmethods__'), (cls, name)
2644+
if name in ns and is_slot_wrapper(name, ns[name]):
2645+
unused.add(name)
2646+
continue
2647+
2648+
if not name.startswith('__') or not name.endswith('__'):
2649+
assert not is_slot_wrapper(name, value), (cls, name, value)
2650+
if not is_slot_wrapper(name, value):
2651+
if name in ns:
2652+
assert not is_slot_wrapper(name, ns[name]), (cls, name, value, ns[name])
2653+
else:
2654+
if name in ns:
2655+
assert ns[name] is value, (cls, name, value, ns[name])
2656+
yield name, True
2657+
else:
2658+
yield name, False
2659+
2660+
for name in unused:
2661+
value = ns[name]
2662+
if is_slot_wrapper(cls, name, value):
2663+
yield name, True
2664+
2665+
26112666
def force_not_colorized(func):
26122667
"""Force the terminal not to be colorized."""
26132668
@functools.wraps(func)

Lib/test/test_embed.py

+38-19
Original file line numberDiff line numberDiff line change
@@ -416,30 +416,49 @@ def test_datetime_reset_strptime(self):
416416
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
417417
self.assertEqual(out, '20000101\n' * INIT_LOOPS)
418418

419+
@unittest.skip('inheritance across re-init is currently broken; see gh-117482')
419420
def test_static_types_inherited_slots(self):
420-
slots = []
421-
script = ['import sys']
422-
from test.test_types import iter_builtin_types, iter_own_slot_wrappers
423-
for cls in iter_builtin_types():
424-
for slot in iter_own_slot_wrappers(cls):
425-
slots.append((cls, slot))
426-
attr = f'{cls.__name__}.{slot}'
427-
script.append(f'print("{attr}:", {attr}, file=sys.stderr)')
428-
script.append('')
429-
script = os.linesep.join(script)
430-
431-
with contextlib.redirect_stderr(io.StringIO()) as stderr:
432-
exec(script)
433-
expected = stderr.getvalue().splitlines()
434-
435-
out, err = self.run_embedded_interpreter("test_repeated_init_exec", script)
421+
script = textwrap.dedent("""
422+
import test.support
423+
424+
results = {}
425+
def add(cls, slot, own):
426+
value = getattr(cls, slot)
427+
try:
428+
subresults = results[cls.__name__]
429+
except KeyError:
430+
subresults = results[cls.__name__] = {}
431+
subresults[slot] = [repr(value), own]
432+
433+
for cls in test.support.iter_builtin_types():
434+
for slot, own in test.support.iter_slot_wrappers(cls):
435+
add(cls, slot, own)
436+
""")
437+
438+
ns = {}
439+
exec(script, ns, ns)
440+
all_expected = ns['results']
441+
del ns
442+
443+
script += textwrap.dedent("""
444+
import json
445+
import sys
446+
text = json.dumps(results)
447+
print(text, file=sys.stderr)
448+
""")
449+
out, err = self.run_embedded_interpreter(
450+
"test_repeated_init_exec", script, script)
436451
results = err.split('--- Loop #')[1:]
437452
results = [res.rpartition(' ---\n')[-1] for res in results]
438453

439454
self.maxDiff = None
440-
for i, result in enumerate(results, start=1):
441-
with self.subTest(loop=i):
442-
self.assertEqual(result.splitlines(), expected)
455+
for i, text in enumerate(results, start=1):
456+
result = json.loads(text)
457+
for classname, expected in all_expected.items():
458+
with self.subTest(loop=i, cls=classname):
459+
slots = result.pop(classname)
460+
self.assertEqual(slots, expected)
461+
self.assertEqual(result, {})
443462
self.assertEqual(out, '')
444463

445464

Lib/test/test_types.py

+39-26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from test.support import (
44
run_with_locale, is_apple_mobile, cpython_only, no_rerun,
5+
iter_builtin_types, iter_slot_wrappers,
56
MISSING_C_DOCSTRINGS,
67
)
78
import collections.abc
@@ -32,26 +33,6 @@ def clear_typing_caches():
3233
f()
3334

3435

35-
def iter_builtin_types():
36-
for obj in __builtins__.values():
37-
if not isinstance(obj, type):
38-
continue
39-
cls = obj
40-
if cls.__module__ != 'builtins':
41-
continue
42-
yield cls
43-
44-
45-
@cpython_only
46-
def iter_own_slot_wrappers(cls):
47-
for name, value in vars(cls).items():
48-
if not name.startswith('__') or not name.endswith('__'):
49-
continue
50-
if 'slot wrapper' not in str(value):
51-
continue
52-
yield name
53-
54-
5536
class TypesTests(unittest.TestCase):
5637

5738
def test_truth_values(self):
@@ -2371,6 +2352,36 @@ def ex(a, /, b, *, c):
23712352

23722353
class SubinterpreterTests(unittest.TestCase):
23732354

2355+
NUMERIC_METHODS = {
2356+
'__abs__',
2357+
'__add__',
2358+
'__bool__',
2359+
'__divmod__',
2360+
'__float__',
2361+
'__floordiv__',
2362+
'__index__',
2363+
'__int__',
2364+
'__lshift__',
2365+
'__mod__',
2366+
'__mul__',
2367+
'__neg__',
2368+
'__pos__',
2369+
'__pow__',
2370+
'__radd__',
2371+
'__rdivmod__',
2372+
'__rfloordiv__',
2373+
'__rlshift__',
2374+
'__rmod__',
2375+
'__rmul__',
2376+
'__rpow__',
2377+
'__rrshift__',
2378+
'__rshift__',
2379+
'__rsub__',
2380+
'__rtruediv__',
2381+
'__sub__',
2382+
'__truediv__',
2383+
}
2384+
23742385
@classmethod
23752386
def setUpClass(cls):
23762387
global interpreters
@@ -2382,32 +2393,34 @@ def setUpClass(cls):
23822393

23832394
@cpython_only
23842395
@no_rerun('channels (and queues) might have a refleak; see gh-122199')
2385-
def test_slot_wrappers(self):
2396+
def test_static_types_inherited_slots(self):
23862397
rch, sch = interpreters.channels.create()
23872398

23882399
slots = []
23892400
script = ''
23902401
for cls in iter_builtin_types():
2391-
for slot in iter_own_slot_wrappers(cls):
2392-
slots.append((cls, slot))
2402+
for slot, own in iter_slot_wrappers(cls):
2403+
if cls is bool and slot in self.NUMERIC_METHODS:
2404+
continue
2405+
slots.append((cls, slot, own))
23932406
script += textwrap.dedent(f"""
23942407
text = repr({cls.__name__}.{slot})
23952408
sch.send_nowait(({cls.__name__!r}, {slot!r}, text))
23962409
""")
23972410

23982411
exec(script)
23992412
all_expected = []
2400-
for cls, slot in slots:
2413+
for cls, slot, _ in slots:
24012414
result = rch.recv()
2402-
assert result == (cls.__name__, slot, result[2]), (cls, slot, result)
2415+
assert result == (cls.__name__, slot, result[-1]), (cls, slot, result)
24032416
all_expected.append(result)
24042417

24052418
interp = interpreters.create()
24062419
interp.exec('from test.support import interpreters')
24072420
interp.prepare_main(sch=sch)
24082421
interp.exec(script)
24092422

2410-
for i, _ in enumerate(slots):
2423+
for i, (cls, slot, _) in enumerate(slots):
24112424
with self.subTest(cls=cls, slot=slot):
24122425
expected = all_expected[i]
24132426
result = rch.recv()

Programs/_testembed.c

+11-3
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,23 @@ PyInit_embedded_ext(void)
170170
static int test_repeated_init_exec(void)
171171
{
172172
if (main_argc < 3) {
173-
fprintf(stderr, "usage: %s test_repeated_init_exec CODE\n", PROGRAM);
173+
fprintf(stderr,
174+
"usage: %s test_repeated_init_exec CODE ...\n", PROGRAM);
174175
exit(1);
175176
}
176177
const char *code = main_argv[2];
178+
int loops = main_argc > 3
179+
? main_argc - 2
180+
: INIT_LOOPS;
177181

178-
for (int i=1; i <= INIT_LOOPS; i++) {
179-
fprintf(stderr, "--- Loop #%d ---\n", i);
182+
for (int i=0; i < loops; i++) {
183+
fprintf(stderr, "--- Loop #%d ---\n", i+1);
180184
fflush(stderr);
181185

186+
if (main_argc > 3) {
187+
code = main_argv[i+2];
188+
}
189+
182190
_testembed_Py_InitializeFromConfig();
183191
int err = PyRun_SimpleString(code);
184192
Py_Finalize();

0 commit comments

Comments
 (0)