[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 490e0ad83a, AKA gh-122248)
This commit is contained in:
Eric Snow 2024-07-29 10:25:02 -06:00 committed by GitHub
parent b5e8b10de7
commit 10cf7d6d00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 131 additions and 51 deletions

View File

@ -2589,6 +2589,61 @@ def copy_python_src_ignore(path, names):
return ignored
def iter_builtin_types():
for obj in __builtins__.values():
if not isinstance(obj, type):
continue
cls = obj
if cls.__module__ != 'builtins':
continue
yield cls
def iter_slot_wrappers(cls):
assert cls.__module__ == 'builtins', cls
def is_slot_wrapper(name, value):
if not isinstance(value, types.WrapperDescriptorType):
assert not repr(value).startswith('<slot wrapper '), (cls, name, value)
return False
assert repr(value).startswith('<slot wrapper '), (cls, name, value)
assert callable(value), (cls, name, value)
assert name.startswith('__') and name.endswith('__'), (cls, name, value)
return True
ns = vars(cls)
unused = set(ns)
for name in dir(cls):
if name in ns:
unused.remove(name)
try:
value = getattr(cls, name)
except AttributeError:
# It's as though it weren't in __dir__.
assert name in ('__annotate__', '__annotations__', '__abstractmethods__'), (cls, name)
if name in ns and is_slot_wrapper(name, ns[name]):
unused.add(name)
continue
if not name.startswith('__') or not name.endswith('__'):
assert not is_slot_wrapper(name, value), (cls, name, value)
if not is_slot_wrapper(name, value):
if name in ns:
assert not is_slot_wrapper(name, ns[name]), (cls, name, value, ns[name])
else:
if name in ns:
assert ns[name] is value, (cls, name, value, ns[name])
yield name, True
else:
yield name, False
for name in unused:
value = ns[name]
if is_slot_wrapper(cls, name, value):
yield name, True
def force_not_colorized(func):
"""Force the terminal not to be colorized."""
@functools.wraps(func)

View File

@ -417,29 +417,47 @@ class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase):
self.assertEqual(out, '20000101\n' * INIT_LOOPS)
def test_static_types_inherited_slots(self):
slots = []
script = ['import sys']
from test.test_types import iter_builtin_types, iter_own_slot_wrappers
for cls in iter_builtin_types():
for slot in iter_own_slot_wrappers(cls):
slots.append((cls, slot))
attr = f'{cls.__name__}.{slot}'
script.append(f'print("{attr}:", {attr}, file=sys.stderr)')
script.append('')
script = os.linesep.join(script)
script = textwrap.dedent("""
import test.support
with contextlib.redirect_stderr(io.StringIO()) as stderr:
exec(script)
expected = stderr.getvalue().splitlines()
results = {}
def add(cls, slot, own):
value = getattr(cls, slot)
try:
subresults = results[cls.__name__]
except KeyError:
subresults = results[cls.__name__] = {}
subresults[slot] = [repr(value), own]
out, err = self.run_embedded_interpreter("test_repeated_init_exec", script)
for cls in test.support.iter_builtin_types():
for slot, own in test.support.iter_slot_wrappers(cls):
add(cls, slot, own)
""")
ns = {}
exec(script, ns, ns)
all_expected = ns['results']
del ns
script += textwrap.dedent("""
import json
import sys
text = json.dumps(results)
print(text, file=sys.stderr)
""")
out, err = self.run_embedded_interpreter(
"test_repeated_init_exec", script, script)
results = err.split('--- Loop #')[1:]
results = [res.rpartition(' ---\n')[-1] for res in results]
self.maxDiff = None
for i, result in enumerate(results, start=1):
with self.subTest(loop=i):
self.assertEqual(result.splitlines(), expected)
for i, text in enumerate(results, start=1):
result = json.loads(text)
for classname, expected in all_expected.items():
with self.subTest(loop=i, cls=classname):
slots = result.pop(classname)
self.assertEqual(slots, expected)
self.assertEqual(result, {})
self.assertEqual(out, '')

View File

@ -1,6 +1,10 @@
# Python test set -- part 6, built-in types
from test.support import run_with_locale, is_apple_mobile, cpython_only, MISSING_C_DOCSTRINGS
from test.support import (
run_with_locale, is_apple_mobile, cpython_only,
iter_builtin_types, iter_slot_wrappers,
MISSING_C_DOCSTRINGS,
)
from test.test_import import no_rerun
import collections.abc
from collections import namedtuple, UserDict
@ -30,26 +34,6 @@ def clear_typing_caches():
f()
def iter_builtin_types():
for obj in __builtins__.values():
if not isinstance(obj, type):
continue
cls = obj
if cls.__module__ != 'builtins':
continue
yield cls
@cpython_only
def iter_own_slot_wrappers(cls):
for name, value in vars(cls).items():
if not name.startswith('__') or not name.endswith('__'):
continue
if 'slot wrapper' not in str(value):
continue
yield name
class TypesTests(unittest.TestCase):
def test_truth_values(self):
@ -2380,14 +2364,14 @@ class SubinterpreterTests(unittest.TestCase):
@cpython_only
@no_rerun('channels (and queues) might have a refleak; see gh-122199')
def test_slot_wrappers(self):
def test_static_types_inherited_slots(self):
rch, sch = interpreters.channels.create()
slots = []
script = ''
for cls in iter_builtin_types():
for slot in iter_own_slot_wrappers(cls):
slots.append((cls, slot))
for slot, own in iter_slot_wrappers(cls):
slots.append((cls, slot, own))
script += textwrap.dedent(f"""
text = repr({cls.__name__}.{slot})
sch.send_nowait(({cls.__name__!r}, {slot!r}, text))
@ -2395,9 +2379,9 @@ class SubinterpreterTests(unittest.TestCase):
exec(script)
all_expected = []
for cls, slot in slots:
for cls, slot, _ in slots:
result = rch.recv()
assert result == (cls.__name__, slot, result[2]), (cls, slot, result)
assert result == (cls.__name__, slot, result[-1]), (cls, slot, result)
all_expected.append(result)
interp = interpreters.create()
@ -2405,7 +2389,7 @@ class SubinterpreterTests(unittest.TestCase):
interp.prepare_main(sch=sch)
interp.exec(script)
for i, _ in enumerate(slots):
for i, (cls, slot, _) in enumerate(slots):
with self.subTest(cls=cls, slot=slot):
expected = all_expected[i]
result = rch.recv()

View File

@ -10879,7 +10879,25 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
&& typeobj != PyExc_StopIteration
&& typeobj != PyExc_SyntaxError
&& typeobj != PyExc_UnicodeDecodeError
&& typeobj != PyExc_UnicodeEncodeError)
&& typeobj != PyExc_UnicodeEncodeError
&& type != &PyBool_Type
&& type != &PyBytes_Type
&& type != &PyMemoryView_Type
&& type != &PyComplex_Type
&& type != &PyEnum_Type
&& type != &PyFilter_Type
&& type != &PyFloat_Type
&& type != &PyFrozenSet_Type
&& type != &PyLong_Type
&& type != &PyMap_Type
&& type != &PyRange_Type
&& type != &PyReversed_Type
&& type != &PySlice_Type
&& type != &PyTuple_Type
&& type != &PyUnicode_Type
&& type != &PyZip_Type)
{
return 1;
}
@ -10897,10 +10915,8 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
/* This is a best-effort list of builtin types
that have their own tp_getattr function. */
if (typeobj == PyExc_BaseException
|| type == &PyBool_Type
|| type == &PyByteArray_Type
|| type == &PyBytes_Type
|| type == &PyClassMethod_Type
|| type == &PyComplex_Type
|| type == &PyDict_Type
|| type == &PyEnum_Type
@ -10914,7 +10930,6 @@ expect_manually_inherited(PyTypeObject *type, void **slot)
|| type == &PyReversed_Type
|| type == &PySet_Type
|| type == &PySlice_Type
|| type == &PyStaticMethod_Type
|| type == &PySuper_Type
|| type == &PyTuple_Type
|| type == &PyZip_Type)

View File

@ -170,15 +170,23 @@ PyInit_embedded_ext(void)
static int test_repeated_init_exec(void)
{
if (main_argc < 3) {
fprintf(stderr, "usage: %s test_repeated_init_exec CODE\n", PROGRAM);
fprintf(stderr,
"usage: %s test_repeated_init_exec CODE ...\n", PROGRAM);
exit(1);
}
const char *code = main_argv[2];
int loops = main_argc > 3
? main_argc - 2
: INIT_LOOPS;
for (int i=1; i <= INIT_LOOPS; i++) {
fprintf(stderr, "--- Loop #%d ---\n", i);
for (int i=0; i < loops; i++) {
fprintf(stderr, "--- Loop #%d ---\n", i+1);
fflush(stderr);
if (main_argc > 3) {
code = main_argv[i+2];
}
_testembed_Py_InitializeFromConfig();
int err = PyRun_SimpleString(code);
Py_Finalize();