pytest icon indicating copy to clipboard operation
pytest copied to clipboard

Unhandled exceptions in `pytest_unconfigure` hooks exit `1` and not `3` - `ExitCode.INTERNAL_ERROR`

Open symonk opened this issue 3 years ago • 2 comments

Hi, as (briefly) discussed in this discussion, pytest_unconfigure differs from pytest_configure in terms of exiting.

When a pytest_configure(...) implementation raises an unhandled exception; pytest exits 3 - ExitCode.INTERNAL_ERROR; this is outlined below:

# conftest.py
import pytest

@pytest.hookimpl
def pytest_configure(config: pytest.Config):
    raise Exception("Unhandled exception; exit will be 3.")

# test_foo.py

def test_foo():
    assert True
pytest .
echo $?
3

However; the reverse is not true, an unhandled exception in the pytest_unconfigure(...) cases forces pytest to exit 1 instead.

# conftest.py
import pytest

@pytest.hookimpl
def pytest_configure(config: pytest.Config) -> None:
    raise Exception("Unhandled exc; exit will be 3.")


@pytest.hookimpl
def pytest_unconfigure(config: pytest.Config) -> None:
    raise Exception("No internal error here; pytest will exit 1")

# test_foo.py

def test_foo():
    assert True

and a 1 exit code:

    raise Exception("No internal error here; pytest will exit 1")
Exception: No internal error here; pytest will exit 1
(venv)  ✘ sy  ~/PycharmProjects/pythonProject45  echo $?
1

symonk avatar Mar 22 '22 13:03 symonk

https://github.com/pytest-dev/pytest/blob/c326c0449433ff812e1b3b81b2590f0d1d797fde/src/_pytest/main.py#L255-L311

we pretty much dont handle exceptons from that particualr hook

on a sidenote, i also beleive its not expected that it causes issues in the form of exceptions - its too late for errors

RonnyPfannschmidt avatar Mar 22 '22 14:03 RonnyPfannschmidt

I've just started having a look at this one; heres an example test to prove out the case:

def test_unconfigure_exc_handling(pytester: Pytester) -> None:
    pytester.makeconftest("""
    import pytest
    
    @pytest.hookimpl
    def pytest_configure(config: pytest.Config) -> None:
        raise Exception("before")
    
    
    @pytest.hookimpl
    def pytest_unconfigure(config: pytest.Config) -> None:
        raise Exception("after")
        """)
    pytester.makepyfile("""
        def test_foo():
            assert True
            
        def test_bar():
            assert False
    """)
    output = pytester.runpytest_subprocess()
    assert output.ret == ExitCode.TESTS_FAILED

@RonnyPfannschmidt @nicoddemus my question is: what is the best approach here in 'solving' it? do we need to guard the cleanup (pytest_unconfigure and registered callables on the config? There is a lot of guarding on on the setup/runtestloop aspects of things and none here as Ronny mentions, on the 'it's too late for errors' could you elaborate that and how you would expect it to be handled?

Thanks!

symonk avatar Apr 29 '22 17:04 symonk