Skip to content

Commit 6727700

Browse files
committed
Fix Missing Fixture Teardown operations
When using the only_rerun and rerun_except queries (or both), the plug-in was removing the teardown operations from the call-stack before checking to see if the test should be re-run. This resulted in the stack having all fixture operations removed that did not correspond to a function fixture. This commit adds a private variable to each test item that keeps track of whether a test encountered a terminal error. The plugin now checks if a test has encountered a terminal error before attempting to clear the stack. This commit fixes: - pytest-dev#241 - pytest-dev#234
1 parent 3b9ad82 commit 6727700

File tree

3 files changed

+211
-1
lines changed

3 files changed

+211
-1
lines changed

CHANGES.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ Changelog
44
13.1 (unreleased)
55
-----------------
66

7+
Bug fixes
8+
+++++++++
9+
10+
- Fix missing teardown for non-function scoped fixtures when using only_rerun or rerun_except queries.
11+
(`#234 <https://github.com/pytest-dev/pytest-rerunfailures/issues/234>`_)
12+
and (`#241 <https://github.com/pytest-dev/pytest-rerunfailures/issues/241>`_)
13+
714
Breaking changes
815
++++++++++++++++
916

src/pytest_rerunfailures.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,15 @@ def pytest_runtest_teardown(item, nextitem):
481481
return
482482

483483
_test_failed_statuses = getattr(item, "_test_failed_statuses", {})
484-
if item.execution_count <= reruns and any(_test_failed_statuses.values()):
484+
485+
# Only remove non-function level actions from the stack if the test is to be re-run
486+
# Exceeding re-run limits, being free of failue statuses, and encountering
487+
# allowable exceptions indicate that the test is not to be re-ran.
488+
if (
489+
item.execution_count <= reruns
490+
and any(_test_failed_statuses.values())
491+
and not any(item._terminal_errors.values())
492+
):
485493
# clean cashed results from any level of setups
486494
_remove_cached_results_from_failed_fixtures(item)
487495

@@ -498,10 +506,16 @@ def pytest_runtest_makereport(item, call):
498506
if result.when == "setup":
499507
# clean failed statuses at the beginning of each test/rerun
500508
setattr(item, "_test_failed_statuses", {})
509+
510+
# create a dict to store error-check results for each stage
511+
setattr(item, "_terminal_errors", {})
512+
501513
_test_failed_statuses = getattr(item, "_test_failed_statuses", {})
502514
_test_failed_statuses[result.when] = result.failed
503515
item._test_failed_statuses = _test_failed_statuses
504516

517+
item._terminal_errors[result.when] = _should_hard_fail_on_error(item, result)
518+
505519

506520
def pytest_runtest_protocol(item, nextitem):
507521
"""

tests/test_pytest_rerunfailures.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,3 +1083,192 @@ def test_2():
10831083

10841084
logging.info.assert_has_calls(expected_calls, any_order=False)
10851085
assert_outcomes(result, failed=8, passed=2, rerun=18, skipped=5, error=1)
1086+
1087+
1088+
def test_exception_matches_rerun_except_query(testdir):
1089+
testdir.makepyfile(
1090+
"""
1091+
import pytest
1092+
1093+
@pytest.fixture(scope="session", autouse=True)
1094+
def session_fixture():
1095+
print("session setup")
1096+
yield "session"
1097+
print("session teardown")
1098+
1099+
@pytest.fixture(scope="package", autouse=True)
1100+
def package_fixture():
1101+
print("package setup")
1102+
yield "package"
1103+
print("package teardown")
1104+
1105+
@pytest.fixture(scope="module", autouse=True)
1106+
def module_fixture():
1107+
print("module setup")
1108+
yield "module"
1109+
print("module teardown")
1110+
1111+
@pytest.fixture(scope="class", autouse=True)
1112+
def class_fixture():
1113+
print("class setup")
1114+
yield "class"
1115+
print("class teardown")
1116+
1117+
@pytest.fixture(scope="function", autouse=True)
1118+
def function_fixture():
1119+
print("function setup")
1120+
yield "function"
1121+
print("function teardown")
1122+
1123+
@pytest.mark.flaky(reruns=1, rerun_except=["AssertionError"])
1124+
class TestStuff:
1125+
def test_1(self):
1126+
raise AssertionError("fail")
1127+
1128+
def test_2(self):
1129+
assert False
1130+
1131+
"""
1132+
)
1133+
result = testdir.runpytest()
1134+
assert_outcomes(result, passed=0, failed=2, rerun=1)
1135+
result.stdout.fnmatch_lines("session teardown")
1136+
result.stdout.fnmatch_lines("package teardown")
1137+
result.stdout.fnmatch_lines("module teardown")
1138+
result.stdout.fnmatch_lines("class teardown")
1139+
result.stdout.fnmatch_lines("function teardown")
1140+
1141+
1142+
def test_exception_not_match_rerun_except_query(testdir):
1143+
testdir.makepyfile(
1144+
"""
1145+
import pytest
1146+
1147+
@pytest.fixture(scope="session", autouse=True)
1148+
def session_fixture():
1149+
print("session setup")
1150+
yield "session"
1151+
print("session teardown")
1152+
1153+
@pytest.fixture(scope="function", autouse=True)
1154+
def function_fixture():
1155+
print("function setup")
1156+
yield "function"
1157+
print("function teardown")
1158+
1159+
@pytest.mark.flaky(reruns=1, rerun_except="AssertionError")
1160+
def test_1(session_fixture, function_fixture):
1161+
raise ValueError("value")
1162+
"""
1163+
)
1164+
result = testdir.runpytest()
1165+
assert_outcomes(result, passed=0, failed=1, rerun=1)
1166+
result.stdout.fnmatch_lines("session teardown")
1167+
1168+
1169+
def test_exception_matches_only_rerun_query(testdir):
1170+
testdir.makepyfile(
1171+
"""
1172+
import pytest
1173+
1174+
@pytest.fixture(scope="session", autouse=True)
1175+
def session_fixture():
1176+
print("session setup")
1177+
yield "session"
1178+
print("session teardown")
1179+
1180+
@pytest.fixture(scope="function", autouse=True)
1181+
def function_fixture():
1182+
print("function setup")
1183+
yield "function"
1184+
print("function teardown")
1185+
1186+
@pytest.mark.flaky(reruns=1, only_rerun=["AssertionError"])
1187+
def test_1(session_fixture, function_fixture):
1188+
raise AssertionError("fail")
1189+
"""
1190+
)
1191+
result = testdir.runpytest()
1192+
assert_outcomes(result, passed=0, failed=1, rerun=1)
1193+
result.stdout.fnmatch_lines("session teardown")
1194+
1195+
1196+
def test_exception_not_match_only_rerun_query(testdir):
1197+
testdir.makepyfile(
1198+
"""
1199+
import pytest
1200+
1201+
@pytest.fixture(scope="session", autouse=True)
1202+
def session_fixture():
1203+
print("session setup")
1204+
yield "session"
1205+
print("session teardown")
1206+
1207+
@pytest.fixture(scope="function", autouse=True)
1208+
def function_fixture():
1209+
print("function setup")
1210+
yield "function"
1211+
print("function teardown")
1212+
1213+
@pytest.mark.flaky(reruns=1, only_rerun=["AssertionError"])
1214+
def test_1(session_fixture, function_fixture):
1215+
raise ValueError("fail")
1216+
"""
1217+
)
1218+
result = testdir.runpytest()
1219+
assert_outcomes(result, passed=0, failed=1)
1220+
result.stdout.fnmatch_lines("session teardown")
1221+
1222+
1223+
def test_exception_match_rerun_except_in_dual_query(testdir):
1224+
testdir.makepyfile(
1225+
"""
1226+
import pytest
1227+
1228+
@pytest.fixture(scope="session", autouse=True)
1229+
def session_fixture():
1230+
print("session setup")
1231+
yield "session"
1232+
print("session teardown")
1233+
1234+
@pytest.fixture(scope="function", autouse=True)
1235+
def function_fixture():
1236+
print("function setup")
1237+
yield "function"
1238+
print("function teardown")
1239+
1240+
@pytest.mark.flaky(reruns=1, rerun_except=["Exception"], only_rerun=["Not"])
1241+
def test_1(session_fixture, function_fixture):
1242+
raise Exception("fail")
1243+
"""
1244+
)
1245+
result = testdir.runpytest()
1246+
assert_outcomes(result, passed=0, failed=1)
1247+
result.stdout.fnmatch_lines("session teardown")
1248+
1249+
1250+
def test_exception_match_only_rerun_in_dual_query(testdir):
1251+
testdir.makepyfile(
1252+
"""
1253+
import pytest
1254+
1255+
@pytest.fixture(scope="session", autouse=True)
1256+
def session_fixture():
1257+
print("session setup")
1258+
yield "session"
1259+
print("session teardown")
1260+
1261+
@pytest.fixture(scope="function", autouse=True)
1262+
def function_fixture():
1263+
print("function setup")
1264+
yield "function"
1265+
print("function teardown")
1266+
1267+
@pytest.mark.flaky(reruns=1, rerun_except=["Not"], only_rerun=["Exception"])
1268+
def test_1(session_fixture, function_fixture):
1269+
raise Exception("fail")
1270+
"""
1271+
)
1272+
result = testdir.runpytest()
1273+
assert_outcomes(result, passed=0, failed=1, rerun=1)
1274+
result.stdout.fnmatch_lines("session teardown")

0 commit comments

Comments
 (0)