--lf now skips colletion of files without failed tests

Fix #5172
This commit is contained in:
Bruno Oliveira 2019-04-28 16:23:38 -03:00
parent ebc0cea226
commit 08734bdd18
3 changed files with 97 additions and 6 deletions

View File

@ -0,0 +1,2 @@
The ``--last-failed`` (``--lf``) option got smarter and will now skip entire files if all tests
of that test file have passed in previous runs, greatly speeding up collection.

View File

@ -158,6 +158,33 @@ class LFPlugin(object):
self.lastfailed = config.cache.get("cache/lastfailed", {}) self.lastfailed = config.cache.get("cache/lastfailed", {})
self._previously_failed_count = None self._previously_failed_count = None
self._report_status = None self._report_status = None
self._skipped_files = 0 # count skipped files during collection due to --lf
def last_failed_paths(self):
"""Returns a set with all Paths()s of the previously failed nodeids (cached).
"""
result = getattr(self, "_last_failed_paths", None)
if result is None:
rootpath = Path(self.config.rootdir)
result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
self._last_failed_paths = result
return result
def pytest_ignore_collect(self, path):
"""
Ignore this file path if we are in --lf mode and it is not in the list of
previously failed files.
"""
if (
self.active
and self.config.getoption("lf")
and path.isfile()
and self.lastfailed
):
skip_it = Path(path) not in self.last_failed_paths()
if skip_it:
self._skipped_files += 1
return skip_it
def pytest_report_collectionfinish(self): def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0: if self.active and self.config.getoption("verbose") >= 0:
@ -206,9 +233,19 @@ class LFPlugin(object):
items[:] = previously_failed + previously_passed items[:] = previously_failed + previously_passed
noun = "failure" if self._previously_failed_count == 1 else "failures" noun = "failure" if self._previously_failed_count == 1 else "failures"
if self._skipped_files > 0:
files_noun = "file" if self._skipped_files == 1 else "files"
skipped_files_msg = " (skipped {files} {files_noun})".format(
files=self._skipped_files, files_noun=files_noun
)
else:
skipped_files_msg = ""
suffix = " first" if self.config.getoption("failedfirst") else "" suffix = " first" if self.config.getoption("failedfirst") else ""
self._report_status = "rerun previous {count} {noun}{suffix}".format( self._report_status = "rerun previous {count} {noun}{suffix}{skipped_files}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun count=self._previously_failed_count,
suffix=suffix,
noun=noun,
skipped_files=skipped_files_msg,
) )
else: else:
self._report_status = "no previously failed tests, " self._report_status = "no previously failed tests, "

View File

@ -445,9 +445,9 @@ class TestLastFailed(object):
result = testdir.runpytest("--lf") result = testdir.runpytest("--lf")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"collected 4 items / 2 deselected / 2 selected", "collected 2 items",
"run-last-failure: rerun previous 2 failures", "run-last-failure: rerun previous 2 failures (skipped 1 file)",
"*2 failed, 2 deselected in*", "*2 failed in*",
] ]
) )
@ -718,7 +718,7 @@ class TestLastFailed(object):
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]
result = testdir.runpytest("--last-failed") result = testdir.runpytest("--last-failed")
result.stdout.fnmatch_lines(["*1 failed, 3 deselected*"]) result.stdout.fnmatch_lines(["*1 failed, 1 deselected*"])
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]
# 3. fix test_foo_4, run only test_foo.py # 3. fix test_foo_4, run only test_foo.py
@ -779,6 +779,58 @@ class TestLastFailed(object):
result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none") result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none")
result.stdout.fnmatch_lines(["*2 desel*"]) result.stdout.fnmatch_lines(["*2 desel*"])
def test_lastfailed_skip_collection(self, testdir):
"""
Test --lf behavior regarding skipping collection of files that are not marked as
failed in the cache (#5172).
"""
testdir.makepyfile(
**{
"pkg1/test_1.py": """
import pytest
@pytest.mark.parametrize('i', range(3))
def test_1(i): pass
""",
"pkg2/test_2.py": """
import pytest
@pytest.mark.parametrize('i', range(5))
def test_1(i):
assert i not in (1, 3)
""",
}
)
# first run: collects 8 items (test_1: 3, test_2: 5)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["collected 8 items", "*2 failed*6 passed*"])
# second run: collects only 5 items from test_2, because all tests from test_1 have passed
result = testdir.runpytest("--lf")
result.stdout.fnmatch_lines(
[
"collected 5 items / 3 deselected / 2 selected",
"run-last-failure: rerun previous 2 failures (skipped 1 file)",
"*2 failed*3 deselected*",
]
)
# add another file and check if message is correct when skipping more than 1 file
testdir.makepyfile(
**{
"pkg1/test_3.py": """
def test_3(): pass
"""
}
)
result = testdir.runpytest("--lf")
result.stdout.fnmatch_lines(
[
"collected 5 items / 3 deselected / 2 selected",
"run-last-failure: rerun previous 2 failures (skipped 2 files)",
"*2 failed*3 deselected*",
]
)
class TestNewFirst(object): class TestNewFirst(object):
def test_newfirst_usecase(self, testdir): def test_newfirst_usecase(self, testdir):