Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/237.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensure teardown reports are passed to ``pytest_runtest_logreport`` for rerun attempts.
11 changes: 6 additions & 5 deletions src/pytest_rerunfailures.py
Original file line number Diff line number Diff line change
Expand Up @@ -966,10 +966,11 @@ def pytest_runtest_protocol(item, nextitem):
item.ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
reports = runtestprotocol(item, nextitem=nextitem, log=False)

rerun_triggered = False
for report in reports: # 3 reports: setup, call, teardown
report.rerun = item.execution_count - 1
if _should_not_rerun(item, report, reruns):
# last run or no failure detected, log normally
if rerun_triggered or _should_not_rerun(item, report, reruns):
# no rerun needed or one already triggered, log normally
item.ihook.pytest_runtest_logreport(report=report)
else:
# failure detected and reruns not exhausted, since i < reruns
Expand All @@ -995,9 +996,9 @@ def pytest_runtest_protocol(item, nextitem):
_remove_failed_subtests_from_report(item, report)
_remove_failed_subtest_reports_from_stats(item)

break # trigger rerun
else:
need_to_run = False
rerun_triggered = True

need_to_run = rerun_triggered

item.ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location)

Expand Down
60 changes: 60 additions & 0 deletions tests/test_pytest_rerunfailures.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,66 @@ def pytest_runtest_logreport(report):
assert_outcomes(result, failed=1, rerun=2, passed=0)


def test_rerun_report_includes_teardown_from_each_attempt(testdir):
testdir.makepyfile("def test_fail(): assert False")
testdir.makeconftest(
"""
reports = []

def pytest_runtest_logreport(report):
reports.append((report.when, report.outcome, report.rerun))

def pytest_sessionfinish():
print(f"REPORTS: {reports!r}")
"""
)

result = testdir.runpytest("--reruns", "1")

assert_outcomes(result, failed=1, rerun=1, passed=0)
expected_reports = (
"REPORTS: [('setup', 'passed', 0), ('call', 'rerun', 0), "
"('teardown', 'passed', 0), ('setup', 'passed', 1), "
"('call', 'failed', 1), ('teardown', 'passed', 1)]"
)
assert expected_reports in result.stdout.str()


def test_single_attempt_triggers_at_most_one_rerun(testdir):
testdir.makepyfile("def test_fail(failing_teardown): assert False")
testdir.makeconftest(
"""
import pytest

attempts = 0
first_attempt_reports = []

@pytest.fixture
def failing_teardown():
global attempts
attempts += 1
yield
raise RuntimeError("teardown failure")

def pytest_runtest_logreport(report):
if report.rerun == 0 and report.when in ("call", "teardown"):
first_attempt_reports.append((report.when, report.outcome))

def pytest_sessionfinish():
print(f"ATTEMPTS: {attempts}")
print(f"FIRST ATTEMPT REPORTS: {first_attempt_reports!r}")
"""
)

result = testdir.runpytest("--reruns", "1")

stdout = result.stdout.str()
assert "ATTEMPTS: 2" in stdout
assert (
"FIRST ATTEMPT REPORTS: [('call', 'rerun'), ('teardown', 'failed')]" in stdout
)


def test_pytest_runtest_logfinish_is_called(testdir):
hook_message = "Message from pytest_runtest_logfinish hook"
testdir.makepyfile("def test_pass(): pass")
Expand Down
Loading