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