Skip to content

pytest.exit() not working when in ExceptionGroup #13650

@mtrzm

Description

@mtrzm

Hi

I have a situation where I have two teardown fixtures:

  • one raises exception
  • second performs pytest.exit() - I use it to stop testing session when only specific test fails.

Simplified example:

import pytest

@pytest.fixture
def failing_teardown():
    yield
    raise IOError("Exception in teardown")

@pytest.fixture
def exit_session():
    yield 
    pytest.exit("Forced exit")

def test_1(): return

@pytest.mark.usefixtures("failing_teardown", "exit_session")
def test_failure(): return

def test_3(): return

My expectation is that:

  • test_1 passes
  • test_2 call passes, teardown reports error (due to exception in failing_teardown) and test session finishes due to pytest.exit() in exit_session fixture
  • test_3 is not executed

But the exception from _pytest.outcomes.Exit is only logged inside ExceptionGroup and exit is not performed:

$ pytest
================================= test session starts ==================================
platform linux -- Python 3.13.3, pytest-8.4.1, pluggy-1.6.0
rootdir: ./pytest-exit-fail
plugins: ordering-0.6
collected 3 items                                                                                                                                                                                                            

test_dummy.py ..E.                                                                                                                                                                                                     [100%]

======================================== ERRORS ========================================
__________________________ ERROR at teardown of test_failure ___________________________
  + Exception Group Traceback (most recent call last):
  |   File ".venv/lib/python3.13/site-packages/_pytest/runner.py", line 344, in from_call
  |     result: TResult | None = func()
  |                              ~~~~^^
  |   File ".venv/lib/python3.13/site-packages/_pytest/runner.py", line 246, in <lambda>
  |     lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
  |             ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  |   File ".venv/lib/python3.13/site-packages/pluggy/_hooks.py", line 512, in __call__
  |     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  |            ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".venv/lib/python3.13/site-packages/pluggy/_manager.py", line 120, in _hookexec
  |     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  |            ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File ".venv/lib/python3.13/site-packages/pluggy/_callers.py", line 167, in _multicall
  |     raise exception
  |   File ".venv/lib/python3.13/site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |     ~~~~~~~~~~~~~~^^^^^^^^^^^
  |   File ".venv/lib/python3.13/site-packages/_pytest/logging.py", line 858, in pytest_runtest_teardown
  |     yield
  |   File ".venv/lib/python3.13/site-packages/pluggy/_callers.py", line 139, in _multicall
  |     teardown.throw(exception)
  |     ~~~~~~~~~~~~~~^^^^^^^^^^^
  |   File ".venv/lib/python3.13/site-packages/_pytest/capture.py", line 905, in pytest_runtest_teardown
  |     return (yield)
  |             ^^^^^
  |   File ".venv/lib/python3.13/site-packages/pluggy/_callers.py", line 121, in _multicall
  |     res = hook_impl.function(*args)
  |   File ".venv/lib/python3.13/site-packages/_pytest/runner.py", line 193, in pytest_runtest_teardown
  |     item.session._setupstate.teardown_exact(nextitem)
  |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  |   File ".venv/lib/python3.13/site-packages/_pytest/runner.py", line 557, in teardown_exact
  |     raise exceptions[0]
  | ExceptionGroup: errors while tearing down <Function test_failure> (2 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File ".venv/lib/python3.13/site-packages/_pytest/runner.py", line 546, in teardown_exact
    |     fin()
    |     ~~~^^
    |   File ".venv/lib/python3.13/site-packages/_pytest/fixtures.py", line 1068, in finish
    |     raise exceptions[0]
    |   File ".venv/lib/python3.13/site-packages/_pytest/fixtures.py", line 1057, in finish
    |     fin()
    |     ~~~^^
    |   File ".venv/lib/python3.13/site-packages/_pytest/fixtures.py", line 938, in _teardown_yield_fixture
    |     next(it)
    |     ~~~~^^^^
    |   File "./test_dummy.py", line 7, in failing_teardown
    |     raise IOError("Exception in teardown")
    | OSError: Exception in teardown
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File ".venv/lib/python3.13/site-packages/_pytest/runner.py", line 546, in teardown_exact
    |     fin()
    |     ~~~^^
    |   File ".venv/lib/python3.13/site-packages/_pytest/fixtures.py", line 1068, in finish
    |     raise exceptions[0]
    |   File ".venv/lib/python3.13/site-packages/_pytest/fixtures.py", line 1057, in finish
    |     fin()
    |     ~~~^^
    |   File ".venv/lib/python3.13/site-packages/_pytest/fixtures.py", line 938, in _teardown_yield_fixture
    |     next(it)
    |     ~~~~^^^^
    |   File "./test_dummy.py", line 13, in exit_session
    |     pytest.exit("Forced exit")
    |     ~~~~~~~~~~~^^^^^^^^^^^^^^^
    |   File ".venv/lib/python3.13/site-packages/_pytest/outcomes.py", line 122, in exit
    |     raise Exit(reason, returncode)
    | _pytest.outcomes.Exit: Forced exit
    +------------------------------------
=============================== short test summary info ================================
ERROR test_dummy.py::test_failure - ExceptionGroup: errors while tearing down <Function test_failure> (2 sub-exceptions)
============================== 3 passed, 1 error in 0.01s ==============================

If I remove failing_teardown fixture, pytest.exit() works:

import pytest


@pytest.fixture
def exit_session():
    yield 
    pytest.exit("Forced exit")


def test_1(): return

@pytest.mark.usefixtures("exit_session")
def test_failure(): return

def test_3(): return

log:

$ pytest
================================= test session starts ==================================
platform linux -- Python 3.13.3, pytest-8.4.1, pluggy-1.6.0
rootdir: ./pytest-exit-fail
plugins: ordering-0.6
collected 3 items                                                                                                                                                                                                            

test_dummy.py ..

!!!!!!!!!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Forced exit !!!!!!!!!!!!!!!!!!!!!!!!!!
================================== 2 passed in 0.15s ===================================

Environment:

  • platform: linux
  • Python 3.13.3
  • pytest-8.4.1
  • pluggy-1.6.0

Metadata

Metadata

Assignees

Labels

needs backportapplied to PRs, indicates that it should be ported to the current bug-fix branchtype: bugproblem that needs to be addressed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions