]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-117482: Fix the Slot Wrapper Inheritance Tests (gh-122249)
authorEric Snow <ericsnowcurrently@gmail.com>
Mon, 29 Jul 2024 16:25:02 +0000 (10:25 -0600)
committerGitHub <noreply@github.com>
Mon, 29 Jul 2024 16:25:02 +0000 (10:25 -0600)
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 490e0ad83ac72c5688dfbbab4eac61ccfd7be5fd, AKA gh-122248)

Lib/test/support/__init__.py
Lib/test/test_embed.py
Lib/test/test_types.py
Objects/typeobject.c
Programs/_testembed.c

index 03950bf63bf4b9c80285d2d88f11d564834c948e..a6fe8f5f2b2d50f367778a67e6a9356b5d72dde2 100644 (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)
index fb7995e05152d2fdc2aa2d8b53942c14d31be1eb..9a63dbc3c6af37c26ab7e49c2a49e226111b2f0f 100644 (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)
-
-        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, '')
 
 
index c722dd3de275349a1dc547f1bc25635dc25a9a28..2844047bd8423eca1563222231988a1f5296c868 100644 (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()
index 54267dd9f9f3856955c7462b6786ee48dc6a33cd..3386eb0105ed33203462d5e277ebc59fa475c31b 100644 (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)
index d149b6a0c5cd21d8863fe749f7186d101348ceba..2c726c915c9df5ea53d9a19cd0b7116730230845 100644 (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();