Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.12] gh-117482: Fix the Slot Wrapper Inheritance Tests #122250

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2413,3 +2413,58 @@ def copy_python_src_ignore(path, names):
'build',
}
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
56 changes: 37 additions & 19 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,29 +391,47 @@ def test_ucnhash_capi_reset(self):
self.assertEqual(out, '9\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)

with contextlib.redirect_stderr(io.StringIO()) as stderr:
exec(script)
expected = stderr.getvalue().splitlines()

out, err = self.run_embedded_interpreter("test_repeated_init_exec", script)
script = textwrap.dedent("""
import test.support

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]

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, '')


Expand Down
35 changes: 9 additions & 26 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Python test set -- part 6, built-in types

from test.support import run_with_locale, cpython_only, MISSING_C_DOCSTRINGS
from test.support import (
run_with_locale, 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
Expand Down Expand Up @@ -29,26 +32,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):
Expand Down Expand Up @@ -2286,22 +2269,22 @@ def setUpClass(cls):

@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.create_channel()

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))
attr = f'{cls.__name__}.{slot}'
script += textwrap.dedent(f"""
sch.send_nowait('{attr}: ' + repr({attr}))
""")

exec(script)
all_expected = []
for cls, slot in slots:
for cls, slot, _ in slots:
result = rch.recv()
assert result.startswith(f'{cls.__name__}.{slot}: '), (cls, slot, result)
all_expected.append(result)
Expand All @@ -2313,7 +2296,7 @@ def test_slot_wrappers(self):
"""))
interp.run(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()
Expand Down
23 changes: 19 additions & 4 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -10094,7 +10094,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;
}
Expand All @@ -10112,10 +10130,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
Expand All @@ -10129,7 +10145,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)
Expand Down
14 changes: 11 additions & 3 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,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();
Expand Down
Loading