Skip to content

Commit 5bd2ea2

Browse files
[3.12] gh-117482: Fix the Slot Wrapper Inheritance Tests (gh-122250)
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 e299104 commit 5bd2ea2

File tree

5 files changed

+131
-52
lines changed

5 files changed

+131
-52
lines changed

Lib/test/support/__init__.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,3 +2413,58 @@ def copy_python_src_ignore(path, names):
24132413
'build',
24142414
}
24152415
return ignored
2416+
2417+
2418+
def iter_builtin_types():
2419+
for obj in __builtins__.values():
2420+
if not isinstance(obj, type):
2421+
continue
2422+
cls = obj
2423+
if cls.__module__ != 'builtins':
2424+
continue
2425+
yield cls
2426+
2427+
2428+
def iter_slot_wrappers(cls):
2429+
assert cls.__module__ == 'builtins', cls
2430+
2431+
def is_slot_wrapper(name, value):
2432+
if not isinstance(value, types.WrapperDescriptorType):
2433+
assert not repr(value).startswith('<slot wrapper '), (cls, name, value)
2434+
return False
2435+
assert repr(value).startswith('<slot wrapper '), (cls, name, value)
2436+
assert callable(value), (cls, name, value)
2437+
assert name.startswith('__') and name.endswith('__'), (cls, name, value)
2438+
return True
2439+
2440+
ns = vars(cls)
2441+
unused = set(ns)
2442+
for name in dir(cls):
2443+
if name in ns:
2444+
unused.remove(name)
2445+
2446+
try:
2447+
value = getattr(cls, name)
2448+
except AttributeError:
2449+
# It's as though it weren't in __dir__.
2450+
assert name in ('__annotate__', '__annotations__', '__abstractmethods__'), (cls, name)
2451+
if name in ns and is_slot_wrapper(name, ns[name]):
2452+
unused.add(name)
2453+
continue
2454+
2455+
if not name.startswith('__') or not name.endswith('__'):
2456+
assert not is_slot_wrapper(name, value), (cls, name, value)
2457+
if not is_slot_wrapper(name, value):
2458+
if name in ns:
2459+
assert not is_slot_wrapper(name, ns[name]), (cls, name, value, ns[name])
2460+
else:
2461+
if name in ns:
2462+
assert ns[name] is value, (cls, name, value, ns[name])
2463+
yield name, True
2464+
else:
2465+
yield name, False
2466+
2467+
for name in unused:
2468+
value = ns[name]
2469+
if is_slot_wrapper(cls, name, value):
2470+
yield name, True

Lib/test/test_embed.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -391,29 +391,47 @@ def test_ucnhash_capi_reset(self):
391391
self.assertEqual(out, '9\n' * INIT_LOOPS)
392392

393393
def test_static_types_inherited_slots(self):
394-
slots = []
395-
script = ['import sys']
396-
from test.test_types import iter_builtin_types, iter_own_slot_wrappers
397-
for cls in iter_builtin_types():
398-
for slot in iter_own_slot_wrappers(cls):
399-
slots.append((cls, slot))
400-
attr = f'{cls.__name__}.{slot}'
401-
script.append(f'print("{attr}:", {attr}, file=sys.stderr)')
402-
script.append('')
403-
script = os.linesep.join(script)
404-
405-
with contextlib.redirect_stderr(io.StringIO()) as stderr:
406-
exec(script)
407-
expected = stderr.getvalue().splitlines()
408-
409-
out, err = self.run_embedded_interpreter("test_repeated_init_exec", script)
394+
script = textwrap.dedent("""
395+
import test.support
396+
397+
results = {}
398+
def add(cls, slot, own):
399+
value = getattr(cls, slot)
400+
try:
401+
subresults = results[cls.__name__]
402+
except KeyError:
403+
subresults = results[cls.__name__] = {}
404+
subresults[slot] = [repr(value), own]
405+
406+
for cls in test.support.iter_builtin_types():
407+
for slot, own in test.support.iter_slot_wrappers(cls):
408+
add(cls, slot, own)
409+
""")
410+
411+
ns = {}
412+
exec(script, ns, ns)
413+
all_expected = ns['results']
414+
del ns
415+
416+
script += textwrap.dedent("""
417+
import json
418+
import sys
419+
text = json.dumps(results)
420+
print(text, file=sys.stderr)
421+
""")
422+
out, err = self.run_embedded_interpreter(
423+
"test_repeated_init_exec", script, script)
410424
results = err.split('--- Loop #')[1:]
411425
results = [res.rpartition(' ---\n')[-1] for res in results]
412426

413427
self.maxDiff = None
414-
for i, result in enumerate(results, start=1):
415-
with self.subTest(loop=i):
416-
self.assertEqual(result.splitlines(), expected)
428+
for i, text in enumerate(results, start=1):
429+
result = json.loads(text)
430+
for classname, expected in all_expected.items():
431+
with self.subTest(loop=i, cls=classname):
432+
slots = result.pop(classname)
433+
self.assertEqual(slots, expected)
434+
self.assertEqual(result, {})
417435
self.assertEqual(out, '')
418436

419437

Lib/test/test_types.py

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Python test set -- part 6, built-in types
22

3-
from test.support import run_with_locale, cpython_only, MISSING_C_DOCSTRINGS
3+
from test.support import (
4+
run_with_locale, cpython_only, iter_builtin_types, iter_slot_wrappers,
5+
MISSING_C_DOCSTRINGS,
6+
)
47
from test.test_import import no_rerun
58
import collections.abc
69
from collections import namedtuple
@@ -29,26 +32,6 @@ def clear_typing_caches():
2932
f()
3033

3134

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

5437
def test_truth_values(self):
@@ -2286,22 +2269,22 @@ def setUpClass(cls):
22862269

22872270
@cpython_only
22882271
@no_rerun('channels (and queues) might have a refleak; see gh-122199')
2289-
def test_slot_wrappers(self):
2272+
def test_static_types_inherited_slots(self):
22902273
rch, sch = interpreters.create_channel()
22912274

22922275
slots = []
22932276
script = ''
22942277
for cls in iter_builtin_types():
2295-
for slot in iter_own_slot_wrappers(cls):
2296-
slots.append((cls, slot))
2278+
for slot, own in iter_slot_wrappers(cls):
2279+
slots.append((cls, slot, own))
22972280
attr = f'{cls.__name__}.{slot}'
22982281
script += textwrap.dedent(f"""
22992282
sch.send_nowait('{attr}: ' + repr({attr}))
23002283
""")
23012284

23022285
exec(script)
23032286
all_expected = []
2304-
for cls, slot in slots:
2287+
for cls, slot, _ in slots:
23052288
result = rch.recv()
23062289
assert result.startswith(f'{cls.__name__}.{slot}: '), (cls, slot, result)
23072290
all_expected.append(result)
@@ -2313,7 +2296,7 @@ def test_slot_wrappers(self):
23132296
"""))
23142297
interp.run(script)
23152298

2316-
for i, _ in enumerate(slots):
2299+
for i, (cls, slot, _) in enumerate(slots):
23172300
with self.subTest(cls=cls, slot=slot):
23182301
expected = all_expected[i]
23192302
result = rch.recv()

Objects/typeobject.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10094,7 +10094,25 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
1009410094
&& typeobj != PyExc_StopIteration
1009510095
&& typeobj != PyExc_SyntaxError
1009610096
&& typeobj != PyExc_UnicodeDecodeError
10097-
&& typeobj != PyExc_UnicodeEncodeError)
10097+
&& typeobj != PyExc_UnicodeEncodeError
10098+
10099+
&& type != &PyBool_Type
10100+
&& type != &PyBytes_Type
10101+
&& type != &PyMemoryView_Type
10102+
&& type != &PyComplex_Type
10103+
&& type != &PyEnum_Type
10104+
&& type != &PyFilter_Type
10105+
&& type != &PyFloat_Type
10106+
&& type != &PyFrozenSet_Type
10107+
&& type != &PyLong_Type
10108+
&& type != &PyMap_Type
10109+
&& type != &PyRange_Type
10110+
&& type != &PyReversed_Type
10111+
&& type != &PySlice_Type
10112+
&& type != &PyTuple_Type
10113+
&& type != &PyUnicode_Type
10114+
&& type != &PyZip_Type)
10115+
1009810116
{
1009910117
return 1;
1010010118
}
@@ -10112,10 +10130,8 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
1011210130
/* This is a best-effort list of builtin types
1011310131
that have their own tp_getattr function. */
1011410132
if (typeobj == PyExc_BaseException
10115-
|| type == &PyBool_Type
1011610133
|| type == &PyByteArray_Type
1011710134
|| type == &PyBytes_Type
10118-
|| type == &PyClassMethod_Type
1011910135
|| type == &PyComplex_Type
1012010136
|| type == &PyDict_Type
1012110137
|| type == &PyEnum_Type
@@ -10129,7 +10145,6 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
1012910145
|| type == &PyReversed_Type
1013010146
|| type == &PySet_Type
1013110147
|| type == &PySlice_Type
10132-
|| type == &PyStaticMethod_Type
1013310148
|| type == &PySuper_Type
1013410149
|| type == &PyTuple_Type
1013510150
|| type == &PyZip_Type)

Programs/_testembed.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,23 @@ PyInit_embedded_ext(void)
163163
static int test_repeated_init_exec(void)
164164
{
165165
if (main_argc < 3) {
166-
fprintf(stderr, "usage: %s test_repeated_init_exec CODE\n", PROGRAM);
166+
fprintf(stderr,
167+
"usage: %s test_repeated_init_exec CODE ...\n", PROGRAM);
167168
exit(1);
168169
}
169170
const char *code = main_argv[2];
171+
int loops = main_argc > 3
172+
? main_argc - 2
173+
: INIT_LOOPS;
170174

171-
for (int i=1; i <= INIT_LOOPS; i++) {
172-
fprintf(stderr, "--- Loop #%d ---\n", i);
175+
for (int i=0; i < loops; i++) {
176+
fprintf(stderr, "--- Loop #%d ---\n", i+1);
173177
fflush(stderr);
174178

179+
if (main_argc > 3) {
180+
code = main_argv[i+2];
181+
}
182+
175183
_testembed_Py_InitializeFromConfig();
176184
int err = PyRun_SimpleString(code);
177185
Py_Finalize();

0 commit comments

Comments
 (0)