diff --git a/AUTHORS b/AUTHORS index 972f39aa45e..fdbe2a7522c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -231,6 +231,7 @@ Jeff Widman Jenni Rinker Jens Tröger Jiajun Xu +Jinhyuk Sung John Eddie Ayson John Litborn John Towler diff --git a/changelog/14575.bugfix.rst b/changelog/14575.bugfix.rst new file mode 100644 index 00000000000..c7605ec89f0 --- /dev/null +++ b/changelog/14575.bugfix.rst @@ -0,0 +1 @@ +``--last-failed`` now reruns ``unittest.TestCase`` tests that contain failed subtests. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 6bcac1ad97a..d63c7ead4e7 100644 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -32,6 +32,7 @@ from _pytest.nodes import Directory from _pytest.nodes import File from _pytest.reports import TestReport +from _pytest.subtests import SubtestReport CACHEDIR_FILES: dict[str, bytes] = { @@ -319,6 +320,7 @@ def __init__(self, config: Config) -> None: self.active = any(config.getoption(key) for key in active_keys) assert config.cache self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) + self._tests_with_failed_subtests: set[str] = set() self._previously_failed_count: int | None = None self._report_status: str | None = None self._skipped_files = 0 # count skipped files during collection due to --lf @@ -346,7 +348,18 @@ def pytest_report_collectionfinish(self) -> str | None: return None def pytest_runtest_logreport(self, report: TestReport) -> None: - if (report.when == "call" and report.passed) or report.skipped: + if report.when == "setup": + self._tests_with_failed_subtests.discard(report.nodeid) + + if isinstance(report, SubtestReport): + if report.failed: + self._tests_with_failed_subtests.add(report.nodeid) + self.lastfailed[report.nodeid] = True + return + + if ( + (report.when == "call" and report.passed) or report.skipped + ) and report.nodeid not in self._tests_with_failed_subtests: self.lastfailed.pop(report.nodeid, None) elif report.failed: self.lastfailed[report.nodeid] = True diff --git a/testing/test_subtests.py b/testing/test_subtests.py index 877e32b5204..f6f68435e21 100644 --- a/testing/test_subtests.py +++ b/testing/test_subtests.py @@ -464,6 +464,34 @@ def test_zaz(self): ] ) + def test_last_failed( + self, pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch + ) -> None: + """Check that --last-failed reruns tests with failed unittest subtests.""" + monkeypatch.setenv("COLUMNS", "120") + pytester.makepyfile( + """ + from unittest import TestCase + + class T(TestCase): + def test_foo(self): + for i in range(3): + with self.subTest(index=i): + assert i % 2 == 0 + """ + ) + result = pytester.runpytest("-v") + result.stdout.fnmatch_lines(["* 1 failed, 1 passed, 2 subtests passed in *"]) + + result = pytester.runpytest("-v", "--last-failed") + result.stdout.fnmatch_lines( + [ + "*collected 1 item", + "run-last-failure: rerun previous 1 failure", + "* 1 failed, 1 passed, 2 subtests passed in *", + ] + ) + def test_passes( self, pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch ) -> None: