diff --git a/CHANGES.rst b/CHANGES.rst index dd6389d..8638e1c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changelog 16.2 (unreleased) ----------------- -- Nothing changed yet. +- Fix missing teardown for session and module scoped fixtures when fixture teardown fails. + Fixes `#314 `_. 16.1 (2025-10-10) diff --git a/src/pytest_rerunfailures.py b/src/pytest_rerunfailures.py index c344d12..63223ae 100644 --- a/src/pytest_rerunfailures.py +++ b/src/pytest_rerunfailures.py @@ -250,11 +250,11 @@ def _remove_failed_setup_state_from_session(item): """ Clean up setup state. - Note: remove all failures from every node in _setupstate stack - and clean the stack itself + Note: remove only the current item, not higher-scoped items """ setup_state = item.session._setupstate - setup_state.stack = {} + if item in setup_state.stack: + del setup_state.stack[item] def _get_rerun_filter_regex(item, regex_name): diff --git a/tests/test_pytest_rerunfailures.py b/tests/test_pytest_rerunfailures.py index 4220a98..7156bc9 100644 --- a/tests/test_pytest_rerunfailures.py +++ b/tests/test_pytest_rerunfailures.py @@ -1170,6 +1170,40 @@ def test_2(): assert_outcomes(result, failed=8, passed=2, rerun=18, skipped=5, error=1) +def test_run_session_teardown_when_fixture_teardown_fails(testdir): + testdir.makepyfile( + """ + import pytest + + @pytest.fixture(scope='session', autouse=True) + def session_fixture(): + yield + print('session teardown') + + @pytest.fixture(scope='module', autouse=True) + def module_fixture(): + yield + print('module teardown') + + @pytest.fixture + def broken_fixture(): + yield + raise Exception("fixture teardown error") + + def test_fail_in_fixture(broken_fixture): + pass + + def test_ok(): + pass + """ + ) + + result = testdir.runpytest("--reruns", "1", "-s") + result.stdout.fnmatch_lines("*session teardown*") + result.stdout.fnmatch_lines("*module teardown*") + assert_outcomes(result, passed=3, rerun=1, error=1) + + def test_exception_matches_rerun_except_query(testdir): testdir.makepyfile( """