# Test the most dynamic corner cases of Python's runtime semantics. import builtins import sys import unittest from test.support import is_wasi, Py_DEBUG, swap_item, swap_attr 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, "stack depth too shallow in pydebug WASI") 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()