Skip to content

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

Closed

Description

@nicoddemus

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.

Activity

  1. added
    type: enhancementnew feature or API change, should be merged into features branch
    on Apr 26, 2024
  2. tjkuson commented on Jul 21, 2026

    @tjkuson
    Contributor

    This can be closed via #13380. The output now looks like this:

    ========================================== test session starts ==========================================
    platform darwin -- Python 3.13.0, pytest-9.1.1, pluggy-1.6.0
    rootdir: /Users/tjkuson/workspace/pytest
    configfile: pyproject.toml
    collected 1 item
    
    t.py E                                                                                            [100%]
    
    ================================================ ERRORS =================================================
    ________________________________________ ERROR at setup of test _________________________________________
      + Exception Group Traceback (most recent call last):
      |   File "/Users/tjkuson/workspace/pytest/t.py", line 8, 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 t.py::test - ExceptionGroup: some errors (2 sub-exceptions)
    =========================================== 1 error in 0.01s ============================================
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: enhancementnew feature or API change, should be merged into features branch

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions