Skip to content

Commit 10cf7d6

Browse files
[3.13] gh-117482: Fix the Slot Wrapper Inheritance Tests (gh-122249)
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. (cherry-picked from commit 490e0ad, AKA gh-122248)
1 parent b5e8b10 commit 10cf7d6

File tree

5 files changed

+133
-53
lines changed

5 files changed

+133
-53
lines changed

Lib/test/support/__init__.py

+55
Original file line numberDiff line numberDiff line change
@@ -2589,6 +2589,61 @@ def copy_python_src_ignore(path, names):
25892589
return ignored
25902590

25912591

2592+
def iter_builtin_types():
2593+
for obj in __builtins__.values():
2594+
if not isinstance(obj, type):
2595+
continue
2596+
cls = obj
2597+
if cls.__module__ != 'builtins':
2598+
continue
2599+
yield cls
2600+
2601+
2602+
def iter_slot_wrappers(cls):
2603+
assert cls.__module__ == 'builtins', cls
2604+
2605+
def is_slot_wrapper(name, value):
2606+
if not isinstance(value, types.WrapperDescriptorType):
2607+
assert not repr(value).startswith('<slot wrapper '), (cls, name, value)
2608+
return False
2609+
assert repr(value).startswith('<slot wrapper '), (cls, name, value)
2610+
assert callable(value), (cls, name, value)
2611+
assert name.startswith('__') and name.endswith('__'), (cls, name, value)
2612+
return True
2613+
2614+
ns = vars(cls)
2615+
unused = set(ns)
2616+
for name in dir(cls):
2617+
if name in ns:
2618+
unused.remove(name)
2619+
2620+
try:
2621+
value = getattr(cls, name)
2622+
except AttributeError:
2623+
# It's as though it weren't in __dir__.
2624+
assert name in ('__annotate__', '__annotations__', '__abstractmethods__'), (cls, name)
2625+
if name in ns and is_slot_wrapper(name, ns[name]):
2626+
unused.add(name)
2627+
continue
2628+
2629+
if not name.startswith('__') or not name.endswith('__'):
2630+
assert not is_slot_wrapper(name, value), (cls, name, value)
2631+
if not is_slot_wrapper(name, value):
2632+
if name in ns:
2633+
assert not is_slot_wrapper(name, ns[name]), (cls, name, value, ns[name])
2634+
else:
2635+
if name in ns:
2636+
assert ns[name] is value, (cls, name, value, ns[name])
2637+
yield name, True
2638+
else:
2639+
yield name, False
2640+
2641+
for name in unused:
2642+
value = ns[name]
2643+
if is_slot_wrapper(cls, name, value):
2644+
yield name, True
2645+
2646+
25922647
def force_not_colorized(func):
25932648
"""Force the terminal not to be colorized."""
25942649
@functools.wraps(func)

Lib/test/test_embed.py

+37-19
Original file line numberDiff line numberDiff line change
@@ -417,29 +417,47 @@ def test_datetime_reset_strptime(self):
417417
self.assertEqual(out, '20000101\n' * INIT_LOOPS)
418418

419419
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)
420+
script = textwrap.dedent("""
421+
import test.support
422+
423+
results = {}
424+
def add(cls, slot, own):
425+
value = getattr(cls, slot)
426+
try:
427+
subresults = results[cls.__name__]
428+
except KeyError:
429+
subresults = results[cls.__name__] = {}
430+
subresults[slot] = [repr(value), own]
431+
432+
for cls in test.support.iter_builtin_types():
433+
for slot, own in test.support.iter_slot_wrappers(cls):
434+
add(cls, slot, own)
435+
""")
436+
437+
ns = {}
438+
exec(script, ns, ns)
439+
all_expected = ns['results']
440+
del ns
441+
442+
script += textwrap.dedent("""
443+
import json
444+
import sys
445+
text = json.dumps(results)
446+
print(text, file=sys.stderr)
447+
""")
448+
out, err = self.run_embedded_interpreter(
449+
"test_repeated_init_exec", script, script)
436450
results = err.split('--- Loop #')[1:]
437451
results = [res.rpartition(' ---\n')[-1] for res in results]
438452

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

445463

Lib/test/test_types.py

+11-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Python test set -- part 6, built-in types
22

3-
from test.support import run_with_locale, is_apple_mobile, cpython_only, MISSING_C_DOCSTRINGS
3+
from test.support import (
4+
run_with_locale, is_apple_mobile, cpython_only,
5+
iter_builtin_types, iter_slot_wrappers,
6+
MISSING_C_DOCSTRINGS,
7+
)
48
from test.test_import import no_rerun
59
import collections.abc
610
from collections import namedtuple, UserDict
@@ -30,26 +34,6 @@ def clear_typing_caches():
3034
f()
3135

3236

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

5539
def test_truth_values(self):
@@ -2380,32 +2364,32 @@ def setUpClass(cls):
23802364

23812365
@cpython_only
23822366
@no_rerun('channels (and queues) might have a refleak; see gh-122199')
2383-
def test_slot_wrappers(self):
2367+
def test_static_types_inherited_slots(self):
23842368
rch, sch = interpreters.channels.create()
23852369

23862370
slots = []
23872371
script = ''
23882372
for cls in iter_builtin_types():
2389-
for slot in iter_own_slot_wrappers(cls):
2390-
slots.append((cls, slot))
2373+
for slot, own in iter_slot_wrappers(cls):
2374+
slots.append((cls, slot, own))
23912375
script += textwrap.dedent(f"""
23922376
text = repr({cls.__name__}.{slot})
23932377
sch.send_nowait(({cls.__name__!r}, {slot!r}, text))
23942378
""")
23952379

23962380
exec(script)
23972381
all_expected = []
2398-
for cls, slot in slots:
2382+
for cls, slot, _ in slots:
23992383
result = rch.recv()
2400-
assert result == (cls.__name__, slot, result[2]), (cls, slot, result)
2384+
assert result == (cls.__name__, slot, result[-1]), (cls, slot, result)
24012385
all_expected.append(result)
24022386

24032387
interp = interpreters.create()
24042388
interp.exec('from test.support import interpreters')
24052389
interp.prepare_main(sch=sch)
24062390
interp.exec(script)
24072391

2408-
for i, _ in enumerate(slots):
2392+
for i, (cls, slot, _) in enumerate(slots):
24092393
with self.subTest(cls=cls, slot=slot):
24102394
expected = all_expected[i]
24112395
result = rch.recv()

Objects/typeobject.c

+19-4
Original file line numberDiff line numberDiff line change
@@ -10879,7 +10879,25 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
1087910879
&& typeobj != PyExc_StopIteration
1088010880
&& typeobj != PyExc_SyntaxError
1088110881
&& typeobj != PyExc_UnicodeDecodeError
10882-
&& typeobj != PyExc_UnicodeEncodeError)
10882+
&& typeobj != PyExc_UnicodeEncodeError
10883+
10884+
&& type != &PyBool_Type
10885+
&& type != &PyBytes_Type
10886+
&& type != &PyMemoryView_Type
10887+
&& type != &PyComplex_Type
10888+
&& type != &PyEnum_Type
10889+
&& type != &PyFilter_Type
10890+
&& type != &PyFloat_Type
10891+
&& type != &PyFrozenSet_Type
10892+
&& type != &PyLong_Type
10893+
&& type != &PyMap_Type
10894+
&& type != &PyRange_Type
10895+
&& type != &PyReversed_Type
10896+
&& type != &PySlice_Type
10897+
&& type != &PyTuple_Type
10898+
&& type != &PyUnicode_Type
10899+
&& type != &PyZip_Type)
10900+
1088310901
{
1088410902
return 1;
1088510903
}
@@ -10897,10 +10915,8 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
1089710915
/* This is a best-effort list of builtin types
1089810916
that have their own tp_getattr function. */
1089910917
if (typeobj == PyExc_BaseException
10900-
|| type == &PyBool_Type
1090110918
|| type == &PyByteArray_Type
1090210919
|| type == &PyBytes_Type
10903-
|| type == &PyClassMethod_Type
1090410920
|| type == &PyComplex_Type
1090510921
|| type == &PyDict_Type
1090610922
|| type == &PyEnum_Type
@@ -10914,7 +10930,6 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
1091410930
|| type == &PyReversed_Type
1091510931
|| type == &PySet_Type
1091610932
|| type == &PySlice_Type
10917-
|| type == &PyStaticMethod_Type
1091810933
|| type == &PySuper_Type
1091910934
|| type == &PyTuple_Type
1092010935
|| type == &PyZip_Type)

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)