Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show nicer tracebacks for BaseExceptionGroup from fixture setup/teardown #12255

Open
nicoddemus opened this issue Apr 26, 2024 · 0 comments
Open
Labels
type: enhancement new feature or API change, should be merged into features branch

Comments

@nicoddemus
Copy link
Member

Currently if an exception is raised during the setup or teardown phase of a fixture, pytest handles that and shows a nice traceback:

import pytest

@pytest.fixture
def my_setup() -> None:
    raise ValueError("e1")    

def test(my_setup) -> None:
    pass
λ pytest bar.py --no-header
======================== test session starts ========================
collected 1 item

bar.py E                                                       [100%]

============================== ERRORS ===============================
______________________ ERROR at setup of test _______________________

    @pytest.fixture
    def my_setup() -> None:
>       raise ValueError("e1")
E       ValueError: e1

bar.py:6: ValueError
====================== short test summary info ======================
ERROR bar.py::test - ValueError: e1
========================= 1 error in 0.23s ==========================

However if we raise an ExceptionGroup, there is no special handling and the full traceback is shown:

import pytest


@pytest.fixture
def my_setup() -> None:
    raise ExceptionGroup("some errors", [ValueError("e1"), ValueError("e2")])


def test(my_setup) -> None:
    pass
λ pytest bar.py --no-header
======================== test session starts ========================
collected 1 item

bar.py E                                                       [100%]

============================== ERRORS ===============================
______________________ ERROR at setup of test _______________________
  + Exception Group Traceback (most recent call last):
  |   File "E:\projects\pytest\src\_pytest\runner.py", line 341, in from_call
  |     result: Optional[TResult] = func()
  |                                 ^^^^^^
  |   File "E:\projects\pytest\src\_pytest\runner.py", line 241, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_hooks.py", line 513, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 139, in _multicall
  |     raise exception.with_traceback(exception.__traceback__)
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 122, in _multicall
  |     teardown.throw(exception)  # type: ignore[union-attr]
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\unraisableexception.py", line 85, in pytest_runtest_setup
  |     yield from unraisable_exception_runtest_hook()
  |   File "E:\projects\pytest\src\_pytest\unraisableexception.py", line 65, in unraisable_exception_runtest_hook
  |     yield
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 122, in _multicall
  |     teardown.throw(exception)  # type: ignore[union-attr]
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\logging.py", line 844, in pytest_runtest_setup
  |     yield from self._runtest_for(item, "setup")
  |   File "E:\projects\pytest\src\_pytest\logging.py", line 833, in _runtest_for
  |     yield
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 122, in _multicall
  |     teardown.throw(exception)  # type: ignore[union-attr]
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\capture.py", line 873, in pytest_runtest_setup
  |     return (yield)
  |             ^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 122, in _multicall
  |     teardown.throw(exception)  # type: ignore[union-attr]
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\threadexception.py", line 82, in pytest_runtest_setup
  |     yield from thread_exception_runtest_hook()
  |   File "E:\projects\pytest\src\_pytest\threadexception.py", line 63, in thread_exception_runtest_hook
  |     yield
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 103, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\runner.py", line 159, in pytest_runtest_setup
  |     item.session._setupstate.setup(item)
  |   File "E:\projects\pytest\src\_pytest\runner.py", line 515, in setup
  |     raise exc
  |   File "E:\projects\pytest\src\_pytest\runner.py", line 512, in setup
  |     col.setup()
  |   File "E:\projects\pytest\src\_pytest\python.py", line 1630, in setup
  |     self._request._fillfixtures()
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 695, in _fillfixtures
  |     item.funcargs[argname] = self.getfixturevalue(argname)
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 552, in getfixturevalue
  |     fixturedef = self._get_active_fixturedef(argname)
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 581, in _get_active_fixturedef
  |     self._compute_fixture_value(fixturedef)
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 656, in _compute_fixture_value
  |     fixturedef.execute(request=subrequest)
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 1086, in execute
  |     result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_hooks.py", line 513, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 139, in _multicall
  |     raise exception.with_traceback(exception.__traceback__)
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 122, in _multicall
  |     teardown.throw(exception)  # type: ignore[union-attr]
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\setuponly.py", line 36, in pytest_fixture_setup
  |     return (yield)
  |             ^^^^^
  |   File "E:\projects\pytest\.env312\Lib\site-packages\pluggy\_callers.py", line 103, in _multicall
  |     res = hook_impl.function(*args)
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 1135, in pytest_fixture_setup
  |     result = call_fixture_func(fixturefunc, request, kwargs)
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "E:\projects\pytest\src\_pytest\fixtures.py", line 903, in call_fixture_func
  |     fixture_result = fixturefunc(**kwargs)
  |                      ^^^^^^^^^^^^^^^^^^^^^
  |   File "e:\projects\pytest\.tmp\bar.py", line 7, in my_setup
  |     raise ExceptionGroup("some errors", [ValueError("e1"), ValueError("e2")])
  | ExceptionGroup: some errors (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | ValueError: e1
    +---------------- 2 ----------------
    | ValueError: e2
    +------------------------------------
====================== short test summary info ======================
ERROR bar.py::test - ExceptionGroup: some errors (2 sub-exceptions)
========================= 1 error in 0.05s ==========================

I think pytest should be able to also handle ExceptionGroup and show a nicer traceback.

Noticed this while working on #12250.

@nicoddemus nicoddemus added the type: enhancement new feature or API change, should be merged into features branch label Apr 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement new feature or API change, should be merged into features branch
Projects
None yet
Development

No branches or pull requests

1 participant