diff --git a/testing/test_helpers.py b/testing/test_helpers.py index 4fe26a57..47bffc02 100644 --- a/testing/test_helpers.py +++ b/testing/test_helpers.py @@ -53,6 +53,46 @@ class F: assert varnames(F) == ((), ()) +def test_varnames_catch_all() -> None: + """ + Adding coverage for special cases. According to the code in varnames(), we should handle + several unusual exception-throwing scenarios. + """ + + class NoInitMeta(type): + def __getattribute__(self, item): + if item == "__init__": + raise AttributeError( + "Testing a class where we can't look at the __init__ attr" + ) + else: + return object.__getattribute__(self, item) + + class NoInit(metaclass=NoInitMeta): + """A class that throws AttributeError for '__init__'""" + + class CantCallMe: + def __getattribute__(self, item): + if item == "__call__": + raise ValueError("Don't look at me") + else: + return object.__getattribute__(self, item) + + def __call__(self, *args, **kwargs): + pass + + def has_weird_signature(): + pass + + setattr( + has_weird_signature, "__signature__", "inspect will fail b/c this is a string" + ) + + assert varnames(NoInit) == ((), ()) + assert varnames(CantCallMe()) == ((), ()) + assert varnames(has_weird_signature) == ((), ()) + + def test_varnames_keyword_only() -> None: def f1(x, *, y) -> None: pass diff --git a/testing/test_multicall.py b/testing/test_multicall.py index 7d8d8f28..12c4f54e 100644 --- a/testing/test_multicall.py +++ b/testing/test_multicall.py @@ -249,17 +249,35 @@ def m1(): assert out == ["cleanup", "finally"] +@pytest.mark.parametrize( + "wrapper_style_new", [True, False], ids=["new-style", "old-style"] +) @pytest.mark.parametrize("exc", [ValueError, SystemExit]) -def test_hookwrapper_exception(exc: "Type[BaseException]") -> None: +def test_hookwrapper_exception(exc: "Type[BaseException]", wrapper_style_new) -> None: out = [] - @hookimpl(hookwrapper=True) + if wrapper_style_new: + as_wrapper = hookimpl(wrapper=True) + else: + as_wrapper = hookimpl(hookwrapper=True) + + @as_wrapper def m1(): out.append("m1 init") - result = yield - assert isinstance(result.exception, exc) - assert result.excinfo[0] == exc - out.append("m1 finish") + + if wrapper_style_new: + try: + _ = yield + pytest.fail("yield is expected to raise an exception here") + except BaseException as e: + assert isinstance(e, exc) + out.append("m1 finish") + raise e + else: + result = yield + assert isinstance(result.exception, exc) + assert result.excinfo[0] == exc + out.append("m1 finish") @hookimpl def m2(): @@ -345,6 +363,75 @@ def m2(): assert out == ["m1 init", "m2 init", "m1 finish"] +@pytest.mark.parametrize("raise_exception_at", ["exc-before", "exc-after"]) +@pytest.mark.parametrize( + "has_second_yield", [True, False], ids=["with-second-yield", "no-second-yield"] +) +@pytest.mark.parametrize( + "wrapper_style_new", [True, False], ids=["new-style", "old-style"] +) +@pytest.mark.parametrize( + "hook_throws", [True, False], ids=["hook-throws", "hook-returns"] +) +def test_wrapper_raises_exception( + wrapper_style_new, raise_exception_at, has_second_yield, hook_throws +): + out = [] + if wrapper_style_new: + as_wrapper = hookimpl(wrapper=True) + else: + as_wrapper = hookimpl(hookwrapper=True) + + @as_wrapper + def m1(): + out.append("m1 init") + if raise_exception_at == "exc-before": + raise ValueError("Raising early exception") + + _ = yield + + if raise_exception_at == "exc-after": + raise ValueError("Raising late exception") + + out.append("should not see this") + + @hookimpl(wrapper=True) + def m2(): + out.append("m2 init") + if raise_exception_at == "exc-before": + raise ValueError("m2: Raising early exception") + + _ = yield + + if has_second_yield: + _ = yield + + if raise_exception_at == "exc-after": + raise ValueError("m2: Raising late exception") + + out.append("m2 should not see this") + + @hookimpl + def m3(): + if hook_throws: + raise ValueError("Hook failed!") + else: + return 123 + + if has_second_yield and raise_exception_at == "exc-after" and not hook_throws: + expect_exception: Type[Exception] = RuntimeError + else: + expect_exception = ValueError + + with pytest.raises(expect_exception): + MC([m3, m2, m1], {}) + + if raise_exception_at == "exc-before": + assert out == ["m1 init"] + else: + assert out == ["m1 init", "m2 init"] + + def test_wrapper_exception_chaining() -> None: @hookimpl def m1():