Skip to content

Failure in fixture cleanup code causing a crash when --exitfirst is set #10490

@kriek

Description

@kriek

I've noticed a corner case where a failure during fixture cleanup code can crash pytest when --exitfirst is set. Minimal reproduction example is below. Note that a very similar crash can also be observed with no --exitfirst but a KeyboardInterrupt instead. Another minimal example is also provided below.

pytest and operating system versions

Windows 10
python 3.10.8
pytest 7.2.0

Minimal example:

test_exit_first.py content:

import pytest

@pytest.fixture(scope="module")  # mind the scope
def fixture():
    yield
    assert False

def test_1(fixture):
    assert False

def test_2():  # mind the presence of another test after the first failing one
    pass
$ PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest test_exit_first.py -v -x
================================= test session starts ==================================
platform win32 -- Python 3.10.8, pytest-7.2.0, pluggy-1.0.0 -- D:\Python310\python.exe
cachedir: .pytest_cache
rootdir: D:\tmp
collected 2 items

test_exit_first.py::test_1 FAILED                                                 [ 50%]
Traceback (most recent call last):
  File "D:\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "D:\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "D:\Python310\Scripts\pytest.exe\__main__.py", line 7, in <module>
    sys.exit(console_main())
  File "D:\Python310\lib\site-packages\_pytest\config\__init__.py", line 190, in console_main
    code = main()
  File "D:\Python310\lib\site-packages\_pytest\config\__init__.py", line 167, in main
    ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
  File "D:\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 60, in _multicall
    return outcome.get_result()
  File "D:\Python310\lib\site-packages\pluggy\_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "D:\Python310\lib\site-packages\_pytest\main.py", line 317, in pytest_cmdline_main
    return wrap_session(config, _main)
  File "D:\Python310\lib\site-packages\_pytest\main.py", line 305, in wrap_session
    config.hook.pytest_sessionfinish(
  File "D:\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 55, in _multicall
    gen.send(outcome)
  File "D:\Python310\lib\site-packages\_pytest\terminal.py", line 808, in pytest_sessionfinish
    outcome.get_result()
  File "D:\Python310\lib\site-packages\pluggy\_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "D:\Python310\lib\site-packages\_pytest\runner.py", line 106, in pytest_sessionfinish
    session._setupstate.teardown_exact(None)
  File "D:\Python310\lib\site-packages\_pytest\runner.py", line 530, in teardown_exact
    raise exc
  File "D:\Python310\lib\site-packages\_pytest\runner.py", line 523, in teardown_exact
    fin()
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 684, in <lambda>
    subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest))
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 1036, in finish
    raise exc
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 1029, in finish
    func()
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 916, in _teardown_yield_fixture
    next(it)
  File "D:\tmp\test_exit_first.py", line 6, in fixture
    assert False
AssertionError: assert False

Alternative KeyboardInterrupt example

test_keyboard_interrupt.py

import pytest
import time

@pytest.fixture(scope="module")
def fixture():
    yield
    assert False

def test_1(fixture):
    time.sleep(1)  # Ctrl-C here
$ PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest test_keyboard_interrupt.py -v
============================================== test session starts ===============================================
platform win32 -- Python 3.10.8, pytest-7.2.0, pluggy-1.0.0 -- D:\Python310\python.exe
cachedir: .pytest_cache
rootdir: D:\tmp
collected 1 item

test_keyboard_interrupt.py::test_1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! KeyboardInterrupt !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
D:\tmp\test_keyboard_interrupt.py:10: KeyboardInterrupt
(to show a full traceback on KeyboardInterrupt use --full-trace)
Traceback (most recent call last):
  File "D:\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "D:\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "D:\Python310\Scripts\pytest.exe\__main__.py", line 7, in <module>
    sys.exit(console_main())
  File "D:\Python310\lib\site-packages\_pytest\config\__init__.py", line 190, in console_main
    code = main()
  File "D:\Python310\lib\site-packages\_pytest\config\__init__.py", line 167, in main
    ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
  File "D:\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 60, in _multicall
    return outcome.get_result()
  File "D:\Python310\lib\site-packages\pluggy\_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "D:\Python310\lib\site-packages\_pytest\main.py", line 317, in pytest_cmdline_main
    return wrap_session(config, _main)
  File "D:\Python310\lib\site-packages\_pytest\main.py", line 305, in wrap_session
    config.hook.pytest_sessionfinish(
  File "D:\Python310\lib\site-packages\pluggy\_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 55, in _multicall
    gen.send(outcome)
  File "D:\Python310\lib\site-packages\_pytest\terminal.py", line 808, in pytest_sessionfinish
    outcome.get_result()
  File "D:\Python310\lib\site-packages\pluggy\_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "D:\Python310\lib\site-packages\pluggy\_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "D:\Python310\lib\site-packages\_pytest\runner.py", line 106, in pytest_sessionfinish
    session._setupstate.teardown_exact(None)
  File "D:\Python310\lib\site-packages\_pytest\runner.py", line 530, in teardown_exact
    raise exc
  File "D:\Python310\lib\site-packages\_pytest\runner.py", line 523, in teardown_exact
    fin()
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 684, in <lambda>
    subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest))
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 1036, in finish
    raise exc
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 1029, in finish
    func()
  File "D:\Python310\lib\site-packages\_pytest\fixtures.py", line 916, in _teardown_yield_fixture
    next(it)
  File "D:\tmp\test_keyboard_interrupt.py", line 7, in fixture
    assert False
AssertionError: assert False

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: fixturesanything involving fixtures directly or indirectly

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions