def test_simple_failing_func(decorator: Decorator) -> None: """Check we can construct a key, and cache in a dict.""" # store a ref to the thrown exception outside the function # so we can check it's the same one returned exception = None @decorator def f(a: int, b: int) -> int: nonlocal exception # this exception should be cached by the wrapper # so we only see it once exception = RuntimeError("failure") raise exception with Context(dict()) as d: try: f(1, 2) except RuntimeError as e: assert e is exception with pytest.raises(RuntimeError): f(1, 2) assert type(d[key(f, 1, 2)]) is Exception assert d[key(f, 1, 2)].args[0] is exception
def test_mock_null_handler(decorator: Decorator) -> None: """Check that a null mock handler is called correctly.""" handler = MagicMock() handler.__contains__.return_value = False handler.__getitem__.return_value = None handler.__setitem__.return_value = None @decorator def f(a: int, b: int) -> int: return a + b with Context(handler): assert f(1, 2) == 3 handler.__contains__.assert_called_once_with(key(f, 1, 2)) handler.__getitem__.assert_not_called() handler.__setitem__.assert_called_once_with(key(f, 1, 2), 3)
def test_fib(decorator: Decorator) -> None: """Check that caching fibonacci works.""" @decorator def fib(x: int) -> int: if x <= 1: return 1 return fib(x - 1) + fib(x - 2) with Context(dict()) as d: assert fib(0) == 1 assert fib(1) == 1 assert fib(2) == 2 assert fib(3) == 3 assert d[key(fib, 0)] == 1 assert d[key(fib, 1)] == 1 assert d[key(fib, 2)] == 2 assert d[key(fib, 3)] == 3
def test_simple_func(decorator: Decorator, ) -> None: """Check we can construct a key, and cache in a dict.""" @decorator def f(a: int, b: int) -> int: return a + b with Context(dict()) as d: f(1, 2) f(1, 2) assert d[key(f, 1, 2)] == 3
def test_mock_cached_handler(decorator: Decorator) -> None: """Check that a fixed value mock handler is called correctly.""" return_value = -1 handler = MagicMock() handler.__contains__.return_value = True handler.__getitem__.return_value = return_value handler.__setitem__.return_value = None @decorator def f(a: int, b: int) -> int: raise AssertionError("this function should not be called") with pytest.raises(AssertionError): f(1, 2) with Context(handler): assert f(1, 2) is return_value handler.__contains__.assert_called_once_with(key(f, 1, 2)) handler.__getitem__.assert_called_once_with(key(f, 1, 2)) handler.__setitem__.assert_not_called()
def test_simple_graph_bump(print: Callable[..., Any], decorator: Decorator) -> None: """Try bumping some functions and check that the graph is consistent.""" @decorator def f(x: int) -> int: return x @decorator def g(x: int, y: int) -> int: return f(x) + f(y) handler = GraphCallHandler() with Context(handler): assert g(1, 2) == 3 assert g(1, 3) == 4 print(handler.__dict__) bumped = handler.bump({key(f, 1): 10}) with Context(bumped): print(bumped.__dict__) # assert bumped.parents == {key(f, 2): set(), key(f, 3): set()} # no nodes have any children left - we evicted them all assert not bumped.children assert bumped.retvals == {key(f, 2): 2, key(f, 3): 3, key(f, 1): 10} assert g(1, 2) == 12 print(bumped.__dict__)
def test_simple_graph_exception(decorator: Decorator) -> None: """Check we can construct a key, and cache in a dict.""" # store a ref to the thrown exception outside the function # so we can check it's the same one returned exception = None @decorator def f(a: int, b: int) -> int: nonlocal exception # this exception should be cached by the wrapper # so we only see it once exception = RuntimeError("failure") raise exception @decorator def g(a: int, b: int) -> int: return f(a, b) a = 1 b = 2 handler = GraphCallHandler() with Context(handler): try: g(a, b) except RuntimeError as e: assert e is exception with pytest.raises(RuntimeError): g(a, b) # exceptions get cached twice - should this be the case, or do # we re-call an throw from source? assert type(handler.retvals[key(f, a, b)]) is Exception assert type(handler.retvals[key(g, a, b)]) is Exception assert handler.retvals[key(f, a, b)].args[0] is exception assert handler.retvals[key(g, a, b)].args[0] is exception assert handler.parents[key(g, a, b)] == set() assert handler.parents[key(f, a, b)] == {key(g, a, b)}
def test_simple_graph(decorator: Decorator) -> None: """Verify we construct an accurate call graph.""" @decorator def f(a: int, b: int) -> int: return a + b @decorator def g(a: int, b: int) -> int: return f(a, b) a = 1 b = 2 handler = GraphCallHandler() with Context(handler): g(a, b) g(a, b) assert handler.parents[key(g, a, b)] == set() assert handler.parents[key(f, a, b)] == {key(g, a, b)} assert handler.children[key(f, a, b)] == set() assert handler.children[key(g, a, b)] == {key(f, a, b)} assert key(f, a, b) in handler.children assert key(f, a, b) in handler.retvals assert key(g, a, b) in handler.retvals assert handler.retvals[key(f, a, b)] == a + b assert handler.retvals[key(g, a, b)] == a + b # tweak the cache, check it is used handler.retvals[key(g, a, b)] = 123 with Context(handler): assert g(a, b) == 123