| import copy |
| import operator |
| import re |
| import sys |
| import textwrap |
| import threading |
| import unittest |
| import weakref |
| try: |
| import _testcapi |
| except ImportError: |
| _testcapi = None |
| |
| from collections.abc import Mapping |
| from test import support |
| from test.support import import_helper, threading_helper |
| from test.support.script_helper import assert_python_ok |
| from test import mapping_tests |
| |
| |
| class ClearTest(unittest.TestCase): |
| """ |
| Tests for frame.clear(). |
| """ |
| |
| def inner(self, x=5, **kwargs): |
| 1/0 |
| |
| def outer(self, **kwargs): |
| try: |
| self.inner(**kwargs) |
| except ZeroDivisionError as e: |
| exc = e |
| return exc |
| |
| def clear_traceback_frames(self, tb): |
| """ |
| Clear all frames in a traceback. |
| """ |
| while tb is not None: |
| tb.tb_frame.clear() |
| tb = tb.tb_next |
| |
| def test_clear_locals(self): |
| class C: |
| pass |
| c = C() |
| wr = weakref.ref(c) |
| exc = self.outer(c=c) |
| del c |
| support.gc_collect() |
| # A reference to c is held through the frames |
| self.assertIsNot(None, wr()) |
| self.clear_traceback_frames(exc.__traceback__) |
| support.gc_collect() |
| # The reference was released by .clear() |
| self.assertIs(None, wr()) |
| |
| def test_clear_locals_after_f_locals_access(self): |
| # see gh-113939 |
| class C: |
| pass |
| |
| wr = None |
| def inner(): |
| nonlocal wr |
| c = C() |
| wr = weakref.ref(c) |
| 1/0 |
| |
| try: |
| inner() |
| except ZeroDivisionError as exc: |
| support.gc_collect() |
| self.assertIsNotNone(wr()) |
| exc.__traceback__.tb_next.tb_frame.clear() |
| support.gc_collect() |
| self.assertIsNone(wr()) |
| |
| def test_clear_does_not_clear_specials(self): |
| class C: |
| pass |
| c = C() |
| exc = self.outer(c=c) |
| del c |
| f = exc.__traceback__.tb_frame |
| f.clear() |
| self.assertIsNot(f.f_code, None) |
| self.assertIsNot(f.f_locals, None) |
| self.assertIsNot(f.f_builtins, None) |
| self.assertIsNot(f.f_globals, None) |
| |
| def test_clear_generator(self): |
| endly = False |
| def g(): |
| nonlocal endly |
| try: |
| yield |
| self.inner() |
| finally: |
| endly = True |
| gen = g() |
| next(gen) |
| self.assertFalse(endly) |
| |
| # Cannot clear a suspended frame |
| with self.assertRaisesRegex(RuntimeError, r'suspended frame'): |
| gen.gi_frame.clear() |
| self.assertFalse(endly) |
| |
| def test_clear_executing(self): |
| # Attempting to clear an executing frame is forbidden. |
| try: |
| 1/0 |
| except ZeroDivisionError as e: |
| f = e.__traceback__.tb_frame |
| with self.assertRaises(RuntimeError): |
| f.clear() |
| with self.assertRaises(RuntimeError): |
| f.f_back.clear() |
| |
| def test_clear_executing_generator(self): |
| # Attempting to clear an executing generator frame is forbidden. |
| endly = False |
| def g(): |
| nonlocal endly |
| try: |
| 1/0 |
| except ZeroDivisionError as e: |
| f = e.__traceback__.tb_frame |
| with self.assertRaises(RuntimeError): |
| f.clear() |
| with self.assertRaises(RuntimeError): |
| f.f_back.clear() |
| yield f |
| finally: |
| endly = True |
| gen = g() |
| f = next(gen) |
| self.assertFalse(endly) |
| # Cannot clear a suspended frame |
| with self.assertRaisesRegex(RuntimeError, 'suspended frame'): |
| f.clear() |
| self.assertFalse(endly) |
| |
| def test_lineno_with_tracing(self): |
| def record_line(): |
| f = sys._getframe(1) |
| lines.append(f.f_lineno-f.f_code.co_firstlineno) |
| |
| def test(trace): |
| record_line() |
| if trace: |
| sys._getframe(0).f_trace = True |
| record_line() |
| record_line() |
| |
| expected_lines = [1, 4, 5] |
| lines = [] |
| test(False) |
| self.assertEqual(lines, expected_lines) |
| lines = [] |
| test(True) |
| self.assertEqual(lines, expected_lines) |
| |
| @support.cpython_only |
| def test_clear_refcycles(self): |
| # .clear() doesn't leave any refcycle behind |
| with support.disable_gc(): |
| class C: |
| pass |
| c = C() |
| wr = weakref.ref(c) |
| exc = self.outer(c=c) |
| del c |
| self.assertIsNot(None, wr()) |
| self.clear_traceback_frames(exc.__traceback__) |
| self.assertIs(None, wr()) |
| |
| |
| class FrameAttrsTest(unittest.TestCase): |
| |
| def make_frames(self): |
| def outer(): |
| x = 5 |
| y = 6 |
| def inner(): |
| z = x + 2 |
| 1/0 |
| t = 9 |
| return inner() |
| try: |
| outer() |
| except ZeroDivisionError as e: |
| tb = e.__traceback__ |
| frames = [] |
| while tb: |
| frames.append(tb.tb_frame) |
| tb = tb.tb_next |
| return frames |
| |
| def test_clear_locals(self): |
| # Test f_locals after clear() (issue #21897) |
| f, outer, inner = self.make_frames() |
| outer.clear() |
| inner.clear() |
| self.assertEqual(outer.f_locals, {}) |
| self.assertEqual(inner.f_locals, {}) |
| |
| def test_locals_clear_locals(self): |
| # Test f_locals before and after clear() (to exercise caching) |
| f, outer, inner = self.make_frames() |
| self.assertNotEqual(outer.f_locals, {}) |
| self.assertNotEqual(inner.f_locals, {}) |
| outer.clear() |
| inner.clear() |
| self.assertEqual(outer.f_locals, {}) |
| self.assertEqual(inner.f_locals, {}) |
| |
| def test_f_lineno_del_segfault(self): |
| f, _, _ = self.make_frames() |
| with self.assertRaises(AttributeError): |
| del f.f_lineno |
| |
| def test_f_generator(self): |
| # Test f_generator in different contexts. |
| |
| def t0(): |
| def nested(): |
| frame = sys._getframe() |
| return frame.f_generator |
| |
| def gen(): |
| yield nested() |
| |
| g = gen() |
| try: |
| return next(g) |
| finally: |
| g.close() |
| |
| def t1(): |
| frame = sys._getframe() |
| return frame.f_generator |
| |
| def t2(): |
| frame = sys._getframe() |
| yield frame.f_generator |
| |
| async def t3(): |
| frame = sys._getframe() |
| return frame.f_generator |
| |
| # For regular functions f_generator is None |
| self.assertIsNone(t0()) |
| self.assertIsNone(t1()) |
| |
| # For generators f_generator is equal to self |
| g = t2() |
| try: |
| frame_g = next(g) |
| self.assertIs(g, frame_g) |
| finally: |
| g.close() |
| |
| # Ditto for coroutines |
| c = t3() |
| try: |
| c.send(None) |
| except StopIteration as ex: |
| self.assertIs(ex.value, c) |
| else: |
| raise AssertionError('coroutine did not exit') |
| |
| |
| class ReprTest(unittest.TestCase): |
| """ |
| Tests for repr(frame). |
| """ |
| |
| def test_repr(self): |
| def outer(): |
| x = 5 |
| y = 6 |
| def inner(): |
| z = x + 2 |
| 1/0 |
| t = 9 |
| return inner() |
| |
| offset = outer.__code__.co_firstlineno |
| try: |
| outer() |
| except ZeroDivisionError as e: |
| tb = e.__traceback__ |
| frames = [] |
| while tb: |
| frames.append(tb.tb_frame) |
| tb = tb.tb_next |
| else: |
| self.fail("should have raised") |
| |
| f_this, f_outer, f_inner = frames |
| file_repr = re.escape(repr(__file__)) |
| self.assertRegex(repr(f_this), |
| r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code test_repr>$" |
| % (file_repr, offset + 23)) |
| self.assertRegex(repr(f_outer), |
| r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code outer>$" |
| % (file_repr, offset + 7)) |
| self.assertRegex(repr(f_inner), |
| r"^<frame at 0x[0-9a-fA-F]+, file %s, line %d, code inner>$" |
| % (file_repr, offset + 5)) |
| |
| class TestFrameLocals(unittest.TestCase): |
| def test_scope(self): |
| class A: |
| x = 1 |
| sys._getframe().f_locals['x'] = 2 |
| sys._getframe().f_locals['y'] = 2 |
| |
| self.assertEqual(A.x, 2) |
| self.assertEqual(A.y, 2) |
| |
| def f(): |
| x = 1 |
| sys._getframe().f_locals['x'] = 2 |
| sys._getframe().f_locals['y'] = 2 |
| self.assertEqual(x, 2) |
| self.assertEqual(locals()['y'], 2) |
| f() |
| |
| def test_closure(self): |
| x = 1 |
| y = 2 |
| |
| def f(): |
| z = x + y |
| d = sys._getframe().f_locals |
| self.assertEqual(d['x'], 1) |
| self.assertEqual(d['y'], 2) |
| d['x'] = 2 |
| d['y'] = 3 |
| |
| f() |
| self.assertEqual(x, 2) |
| self.assertEqual(y, 3) |
| |
| def test_closure_with_inline_comprehension(self): |
| lambda: k |
| k = 1 |
| lst = [locals() for k in [0]] |
| self.assertEqual(lst[0]['k'], 0) |
| |
| def test_as_dict(self): |
| x = 1 |
| y = 2 |
| d = sys._getframe().f_locals |
| # self, x, y, d |
| self.assertEqual(len(d), 4) |
| self.assertIs(d['d'], d) |
| self.assertEqual(set(d.keys()), set(['x', 'y', 'd', 'self'])) |
| self.assertEqual(len(d.values()), 4) |
| self.assertIn(1, d.values()) |
| self.assertEqual(len(d.items()), 4) |
| self.assertIn(('x', 1), d.items()) |
| self.assertEqual(d.__getitem__('x'), 1) |
| d.__setitem__('x', 2) |
| self.assertEqual(d['x'], 2) |
| self.assertEqual(d.get('x'), 2) |
| self.assertIs(d.get('non_exist', None), None) |
| self.assertEqual(d.__len__(), 4) |
| self.assertEqual(set([key for key in d]), set(['x', 'y', 'd', 'self'])) |
| self.assertIn('x', d) |
| self.assertTrue(d.__contains__('x')) |
| |
| self.assertEqual(reversed(d), list(reversed(d.keys()))) |
| |
| d.update({'x': 3, 'z': 4}) |
| self.assertEqual(d['x'], 3) |
| self.assertEqual(d['z'], 4) |
| |
| with self.assertRaises(TypeError): |
| d.update([1, 2]) |
| |
| self.assertEqual(d.setdefault('x', 5), 3) |
| self.assertEqual(d.setdefault('new', 5), 5) |
| self.assertEqual(d['new'], 5) |
| |
| with self.assertRaises(KeyError): |
| d['non_exist'] |
| |
| def test_as_number(self): |
| x = 1 |
| y = 2 |
| d = sys._getframe().f_locals |
| self.assertIn('z', d | {'z': 3}) |
| d |= {'z': 3} |
| self.assertEqual(d['z'], 3) |
| d |= {'y': 3} |
| self.assertEqual(d['y'], 3) |
| with self.assertRaises(TypeError): |
| d |= 3 |
| with self.assertRaises(TypeError): |
| _ = d | [3] |
| |
| def test_non_string_key(self): |
| d = sys._getframe().f_locals |
| d[1] = 2 |
| self.assertEqual(d[1], 2) |
| |
| def test_write_with_hidden(self): |
| def f(): |
| f_locals = [sys._getframe().f_locals for b in [0]][0] |
| f_locals['b'] = 2 |
| f_locals['c'] = 3 |
| self.assertEqual(b, 2) |
| self.assertEqual(c, 3) |
| b = 0 |
| c = 0 |
| f() |
| |
| def test_local_objects(self): |
| o = object() |
| k = '.'.join(['a', 'b', 'c']) |
| f_locals = sys._getframe().f_locals |
| f_locals['o'] = f_locals['k'] |
| self.assertEqual(o, 'a.b.c') |
| |
| def test_copy(self): |
| x = 0 |
| d = sys._getframe().f_locals |
| d_copy = d.copy() |
| self.assertIsInstance(d_copy, dict) |
| self.assertEqual(d_copy['x'], 0) |
| d_copy['x'] = 1 |
| self.assertEqual(x, 0) |
| |
| def test_update_with_self(self): |
| def f(): |
| f_locals = sys._getframe().f_locals |
| f_locals.update(f_locals) |
| f_locals.update(f_locals) |
| f_locals.update(f_locals) |
| f() |
| |
| def test_repr(self): |
| x = 1 |
| # Introduce a reference cycle |
| frame = sys._getframe() |
| self.assertEqual(repr(frame.f_locals), repr(dict(frame.f_locals))) |
| |
| def test_delete(self): |
| x = 1 |
| d = sys._getframe().f_locals |
| |
| # This needs to be tested before f_extra_locals is created |
| with self.assertRaisesRegex(KeyError, 'non_exist'): |
| del d['non_exist'] |
| |
| with self.assertRaises(KeyError): |
| d.pop('non_exist') |
| |
| with self.assertRaisesRegex(ValueError, 'local variables'): |
| del d['x'] |
| |
| with self.assertRaises(AttributeError): |
| d.clear() |
| |
| with self.assertRaises(ValueError): |
| d.pop('x') |
| |
| with self.assertRaises(ValueError): |
| d.pop('x', None) |
| |
| # 'm', 'n' is stored in f_extra_locals |
| d['m'] = 1 |
| d['n'] = 1 |
| |
| with self.assertRaises(KeyError): |
| d.pop('non_exist') |
| |
| del d['m'] |
| self.assertEqual(d.pop('n'), 1) |
| |
| self.assertNotIn('m', d) |
| self.assertNotIn('n', d) |
| |
| self.assertEqual(d.pop('n', 2), 2) |
| |
| @support.cpython_only |
| def test_sizeof(self): |
| proxy = sys._getframe().f_locals |
| support.check_sizeof(self, proxy, support.calcobjsize("P")) |
| |
| def test_unsupport(self): |
| x = 1 |
| d = sys._getframe().f_locals |
| with self.assertRaises(TypeError): |
| copy.copy(d) |
| |
| with self.assertRaises(TypeError): |
| copy.deepcopy(d) |
| |
| def test_is_mapping(self): |
| x = 1 |
| d = sys._getframe().f_locals |
| self.assertIsInstance(d, Mapping) |
| match d: |
| case {"x": value}: |
| self.assertEqual(value, 1) |
| kind = "mapping" |
| case _: |
| kind = "other" |
| self.assertEqual(kind, "mapping") |
| |
| def _x_stringlikes(self): |
| class StringSubclass(str): |
| pass |
| |
| class ImpostorX: |
| def __hash__(self): |
| return hash('x') |
| |
| def __eq__(self, other): |
| return other == 'x' |
| |
| return StringSubclass('x'), ImpostorX(), 'x' |
| |
| def test_proxy_key_stringlikes_overwrite(self): |
| def f(obj): |
| x = 1 |
| proxy = sys._getframe().f_locals |
| proxy[obj] = 2 |
| return ( |
| list(proxy.keys()), |
| dict(proxy), |
| proxy |
| ) |
| |
| for obj in self._x_stringlikes(): |
| with self.subTest(cls=type(obj).__name__): |
| |
| keys_snapshot, proxy_snapshot, proxy = f(obj) |
| expected_keys = ['obj', 'x', 'proxy'] |
| expected_dict = {'obj': 'x', 'x': 2, 'proxy': proxy} |
| self.assertEqual(proxy.keys(), expected_keys) |
| self.assertEqual(proxy, expected_dict) |
| self.assertEqual(keys_snapshot, expected_keys) |
| self.assertEqual(proxy_snapshot, expected_dict) |
| |
| def test_proxy_key_stringlikes_ftrst_write(self): |
| def f(obj): |
| proxy = sys._getframe().f_locals |
| proxy[obj] = 2 |
| self.assertEqual(x, 2) |
| x = 1 |
| |
| for obj in self._x_stringlikes(): |
| with self.subTest(cls=type(obj).__name__): |
| f(obj) |
| |
| def test_proxy_key_unhashables(self): |
| class StringSubclass(str): |
| __hash__ = None |
| |
| class ObjectSubclass: |
| __hash__ = None |
| |
| proxy = sys._getframe().f_locals |
| |
| for obj in StringSubclass('x'), ObjectSubclass(): |
| with self.subTest(cls=type(obj).__name__): |
| with self.assertRaises(TypeError): |
| proxy[obj] |
| with self.assertRaises(TypeError): |
| proxy[obj] = 0 |
| |
| def test_constructor(self): |
| FrameLocalsProxy = type([sys._getframe().f_locals |
| for x in range(1)][0]) |
| self.assertEqual(FrameLocalsProxy.__name__, 'FrameLocalsProxy') |
| |
| def make_frame(): |
| x = 1 |
| y = 2 |
| return sys._getframe() |
| |
| proxy = FrameLocalsProxy(make_frame()) |
| self.assertEqual(proxy, {'x': 1, 'y': 2}) |
| |
| # constructor expects 1 frame argument |
| with self.assertRaises(TypeError): |
| FrameLocalsProxy() # no arguments |
| with self.assertRaises(TypeError): |
| FrameLocalsProxy(123) # wrong type |
| with self.assertRaises(TypeError): |
| FrameLocalsProxy(frame=sys._getframe()) # no keyword arguments |
| |
| def test_overwrite_locals(self): |
| # Verify we do not crash if we overwrite a local passed as an argument |
| # from an ancestor in the call stack. |
| def f(): |
| xs = [1, 2, 3] |
| ys = [4, 5, 6] |
| return g(xs) |
| |
| def g(xs): |
| f = sys._getframe() |
| f.f_back.f_locals["xs"] = None |
| f.f_back.f_locals["ys"] = None |
| return xs[1] |
| |
| self.assertEqual(f(), 2) |
| |
| |
| class FrameLocalsProxyMappingTests(mapping_tests.TestHashMappingProtocol): |
| """Test that FrameLocalsProxy behaves like a Mapping (with exceptions)""" |
| |
| def _f(*args, **kwargs): |
| def _f(): |
| return sys._getframe().f_locals |
| return _f() |
| type2test = _f |
| |
| @unittest.skipIf(True, 'Locals proxies for different frames never compare as equal') |
| def test_constructor(self): |
| pass |
| |
| @unittest.skipIf(True, 'Unlike a mapping: del proxy[key] fails') |
| def test_write(self): |
| pass |
| |
| @unittest.skipIf(True, 'Unlike a mapping: no proxy.popitem') |
| def test_popitem(self): |
| pass |
| |
| @unittest.skipIf(True, 'Unlike a mapping: no proxy.pop') |
| def test_pop(self): |
| pass |
| |
| @unittest.skipIf(True, 'Unlike a mapping: no proxy.clear') |
| def test_clear(self): |
| pass |
| |
| @unittest.skipIf(True, 'Unlike a mapping: no proxy.fromkeys') |
| def test_fromkeys(self): |
| pass |
| |
| # no del |
| def test_getitem(self): |
| mapping_tests.BasicTestMappingProtocol.test_getitem(self) |
| d = self._full_mapping({'a': 1, 'b': 2}) |
| self.assertEqual(d['a'], 1) |
| self.assertEqual(d['b'], 2) |
| d['c'] = 3 |
| d['a'] = 4 |
| self.assertEqual(d['c'], 3) |
| self.assertEqual(d['a'], 4) |
| |
| @unittest.skipIf(True, 'Unlike a mapping: no proxy.update') |
| def test_update(self): |
| pass |
| |
| # proxy.copy returns a regular dict |
| def test_copy(self): |
| d = self._full_mapping({1:1, 2:2, 3:3}) |
| self.assertEqual(d.copy(), {1:1, 2:2, 3:3}) |
| d = self._empty_mapping() |
| self.assertEqual(d.copy(), d) |
| self.assertRaises(TypeError, d.copy, None) |
| |
| self.assertIsInstance(d.copy(), dict) |
| |
| @unittest.skipIf(True, 'Locals proxies for different frames never compare as equal') |
| def test_eq(self): |
| pass |
| |
| |
| class TestFrameCApi(unittest.TestCase): |
| def test_basic(self): |
| x = 1 |
| ctypes = import_helper.import_module('ctypes') |
| PyEval_GetFrameLocals = ctypes.pythonapi.PyEval_GetFrameLocals |
| PyEval_GetFrameLocals.restype = ctypes.py_object |
| frame_locals = PyEval_GetFrameLocals() |
| self.assertTrue(type(frame_locals), dict) |
| self.assertEqual(frame_locals['x'], 1) |
| frame_locals['x'] = 2 |
| self.assertEqual(x, 1) |
| |
| PyEval_GetFrameGlobals = ctypes.pythonapi.PyEval_GetFrameGlobals |
| PyEval_GetFrameGlobals.restype = ctypes.py_object |
| frame_globals = PyEval_GetFrameGlobals() |
| self.assertTrue(type(frame_globals), dict) |
| self.assertIs(frame_globals, globals()) |
| |
| PyEval_GetFrameBuiltins = ctypes.pythonapi.PyEval_GetFrameBuiltins |
| PyEval_GetFrameBuiltins.restype = ctypes.py_object |
| frame_builtins = PyEval_GetFrameBuiltins() |
| self.assertEqual(frame_builtins, __builtins__) |
| |
| PyFrame_GetLocals = ctypes.pythonapi.PyFrame_GetLocals |
| PyFrame_GetLocals.argtypes = [ctypes.py_object] |
| PyFrame_GetLocals.restype = ctypes.py_object |
| frame = sys._getframe() |
| f_locals = PyFrame_GetLocals(frame) |
| self.assertTrue(f_locals['x'], 1) |
| f_locals['x'] = 2 |
| self.assertEqual(x, 2) |
| |
| |
| class TestIncompleteFrameAreInvisible(unittest.TestCase): |
| |
| def test_issue95818(self): |
| # See GH-95818 for details |
| code = textwrap.dedent(f""" |
| import gc |
| |
| gc.set_threshold(1,1,1) |
| class GCHello: |
| def __del__(self): |
| print("Destroyed from gc") |
| |
| def gen(): |
| yield |
| |
| fd = open({__file__!r}) |
| l = [fd, GCHello()] |
| l.append(l) |
| del fd |
| del l |
| gen() |
| """) |
| assert_python_ok("-c", code) |
| |
| @support.cpython_only |
| @threading_helper.requires_working_threading() |
| def test_sneaky_frame_object_teardown(self): |
| |
| class SneakyDel: |
| def __del__(self): |
| """ |
| Stash a reference to the entire stack for walking later. |
| |
| It may look crazy, but you'd be surprised how common this is |
| when using a test runner (like pytest). The typical recipe is: |
| ResourceWarning + -Werror + a custom sys.unraisablehook. |
| """ |
| nonlocal sneaky_frame_object |
| sneaky_frame_object = sys._getframe() |
| |
| class SneakyThread(threading.Thread): |
| """ |
| A separate thread isn't needed to make this code crash, but it does |
| make crashes more consistent, since it means sneaky_frame_object is |
| backed by freed memory after the thread completes! |
| """ |
| |
| def run(self): |
| """Run SneakyDel.__del__ as this frame is popped.""" |
| ref = SneakyDel() |
| |
| sneaky_frame_object = None |
| t = SneakyThread() |
| t.start() |
| t.join() |
| # sneaky_frame_object can be anything, really, but it's crucial that |
| # SneakyThread.run's frame isn't anywhere on the stack while it's being |
| # torn down: |
| self.assertIsNotNone(sneaky_frame_object) |
| while sneaky_frame_object is not None: |
| self.assertIsNot( |
| sneaky_frame_object.f_code, SneakyThread.run.__code__ |
| ) |
| sneaky_frame_object = sneaky_frame_object.f_back |
| |
| def test_entry_frames_are_invisible_during_teardown(self): |
| class C: |
| """A weakref'able class.""" |
| |
| def f(): |
| """Try to find globals and locals as this frame is being cleared.""" |
| ref = C() |
| # Ignore the fact that exec(C()) is a nonsense callback. We're only |
| # using exec here because it tries to access the current frame's |
| # globals and locals. If it's trying to get those from a shim frame, |
| # we'll crash before raising: |
| return weakref.ref(ref, exec) |
| |
| with support.catch_unraisable_exception() as catcher: |
| # Call from C, so there is a shim frame directly above f: |
| weak = operator.call(f) # BOOM! |
| # Cool, we didn't crash. Check that the callback actually happened: |
| self.assertIs(catcher.unraisable.exc_type, TypeError) |
| self.assertIsNone(weak()) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |