1# Test the most dynamic corner cases of Python's runtime semantics.
2
3import builtins
4import sys
5import unittest
6
7from test.support import swap_item, swap_attr
8
9
10class RebindBuiltinsTests(unittest.TestCase):
11
12    """Test all the ways that we can change/shadow globals/builtins."""
13
14    def configure_func(self, func, *args):
15        """Perform TestCase-specific configuration on a function before testing.
16
17        By default, this does nothing. Example usage: spinning a function so
18        that a JIT will optimize it. Subclasses should override this as needed.
19
20        Args:
21            func: function to configure.
22            *args: any arguments that should be passed to func, if calling it.
23
24        Returns:
25            Nothing. Work will be performed on func in-place.
26        """
27        pass
28
29    def test_globals_shadow_builtins(self):
30        # Modify globals() to shadow an entry in builtins.
31        def foo():
32            return len([1, 2, 3])
33        self.configure_func(foo)
34
35        self.assertEqual(foo(), 3)
36        with swap_item(globals(), "len", lambda x: 7):
37            self.assertEqual(foo(), 7)
38
39    def test_modify_builtins(self):
40        # Modify the builtins module directly.
41        def foo():
42            return len([1, 2, 3])
43        self.configure_func(foo)
44
45        self.assertEqual(foo(), 3)
46        with swap_attr(builtins, "len", lambda x: 7):
47            self.assertEqual(foo(), 7)
48
49    def test_modify_builtins_while_generator_active(self):
50        # Modify the builtins out from under a live generator.
51        def foo():
52            x = range(3)
53            yield len(x)
54            yield len(x)
55        self.configure_func(foo)
56
57        g = foo()
58        self.assertEqual(next(g), 3)
59        with swap_attr(builtins, "len", lambda x: 7):
60            self.assertEqual(next(g), 7)
61
62    def test_modify_builtins_from_leaf_function(self):
63        # Verify that modifications made by leaf functions percolate up the
64        # callstack.
65        with swap_attr(builtins, "len", len):
66            def bar():
67                builtins.len = lambda x: 4
68
69            def foo(modifier):
70                l = []
71                l.append(len(range(7)))
72                modifier()
73                l.append(len(range(7)))
74                return l
75            self.configure_func(foo, lambda: None)
76
77            self.assertEqual(foo(bar), [7, 4])
78
79    def test_cannot_change_globals_or_builtins_with_eval(self):
80        def foo():
81            return len([1, 2, 3])
82        self.configure_func(foo)
83
84        # Note that this *doesn't* change the definition of len() seen by foo().
85        builtins_dict = {"len": lambda x: 7}
86        globals_dict = {"foo": foo, "__builtins__": builtins_dict,
87                        "len": lambda x: 8}
88        self.assertEqual(eval("foo()", globals_dict), 3)
89
90        self.assertEqual(eval("foo()", {"foo": foo}), 3)
91
92    def test_cannot_change_globals_or_builtins_with_exec(self):
93        def foo():
94            return len([1, 2, 3])
95        self.configure_func(foo)
96
97        globals_dict = {"foo": foo}
98        exec("x = foo()", globals_dict)
99        self.assertEqual(globals_dict["x"], 3)
100
101        # Note that this *doesn't* change the definition of len() seen by foo().
102        builtins_dict = {"len": lambda x: 7}
103        globals_dict = {"foo": foo, "__builtins__": builtins_dict,
104                        "len": lambda x: 8}
105
106        exec("x = foo()", globals_dict)
107        self.assertEqual(globals_dict["x"], 3)
108
109    def test_cannot_replace_builtins_dict_while_active(self):
110        def foo():
111            x = range(3)
112            yield len(x)
113            yield len(x)
114        self.configure_func(foo)
115
116        g = foo()
117        self.assertEqual(next(g), 3)
118        with swap_item(globals(), "__builtins__", {"len": lambda x: 7}):
119            self.assertEqual(next(g), 3)
120
121    def test_cannot_replace_builtins_dict_between_calls(self):
122        def foo():
123            return len([1, 2, 3])
124        self.configure_func(foo)
125
126        self.assertEqual(foo(), 3)
127        with swap_item(globals(), "__builtins__", {"len": lambda x: 7}):
128            self.assertEqual(foo(), 3)
129
130    def test_eval_gives_lambda_custom_globals(self):
131        globals_dict = {"len": lambda x: 7}
132        foo = eval("lambda: len([])", globals_dict)
133        self.configure_func(foo)
134
135        self.assertEqual(foo(), 7)
136
137    def test_load_global_specialization_failure_keeps_oparg(self):
138        # https://github.com/python/cpython/issues/91625
139        class MyGlobals(dict):
140            def __missing__(self, key):
141                return int(key.removeprefix("_number_"))
142
143        code = "lambda: " + "+".join(f"_number_{i}" for i in range(1000))
144        sum_1000 = eval(code, MyGlobals())
145        expected = sum(range(1000))
146        # Warm up the the function for quickening (PEP 659)
147        for _ in range(30):
148            self.assertEqual(sum_1000(), expected)
149
150
151class TestTracing(unittest.TestCase):
152
153    def setUp(self):
154        self.addCleanup(sys.settrace, sys.gettrace())
155        sys.settrace(None)
156
157    def test_after_specialization(self):
158
159        def trace(frame, event, arg):
160            return trace
161
162        turn_on_trace = False
163
164        class C:
165            def __init__(self, x):
166                self.x = x
167            def __del__(self):
168                if turn_on_trace:
169                    sys.settrace(trace)
170
171        def f():
172            # LOAD_GLOBAL[_BUILTIN] immediately follows the call to C.__del__
173            C(0).x, len
174
175        def g():
176            # BINARY_SUSCR[_LIST_INT] immediately follows the call to C.__del__
177            [0][C(0).x]
178
179        def h():
180            # BINARY_OP[_ADD_INT] immediately follows the call to C.__del__
181            0 + C(0).x
182
183        for func in (f, g, h):
184            with self.subTest(func.__name__):
185                for _ in range(58):
186                    func()
187                turn_on_trace = True
188                func()
189                sys.settrace(None)
190                turn_on_trace = False
191
192
193if __name__ == "__main__":
194    unittest.main()
195