Skip to content

Commit

Permalink
--lf now skips colletion of files without failed tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Apr 28, 2019
1 parent ebc0cea commit b654e58
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 6 deletions.
2 changes: 2 additions & 0 deletions changelog/5172.feature.rst
@@ -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.
34 changes: 32 additions & 2 deletions src/_pytest/cacheprovider.py
Expand Up @@ -158,6 +158,26 @@ def __init__(self, config):
self.lastfailed = config.cache.get("cache/lastfailed", {})
self._previously_failed_count = None
self._report_status = None
self._skipped_files = 0

def last_failed_paths(self):
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):
if not self.active or not self.config.getoption("lf") or not path.isfile():
return
last_failed_paths = self.last_failed_paths()
if not last_failed_paths:
return
result = Path(path) not in self.last_failed_paths()
if result:
self._skipped_files += 1
return result

def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0:
Expand Down Expand Up @@ -206,9 +226,19 @@ def pytest_collection_modifyitems(self, session, config, items):
items[:] = previously_failed + previously_passed

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 ""
self._report_status = "rerun previous {count} {noun}{suffix}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun
self._report_status = "rerun previous {count} {noun}{suffix}{skipped_files}".format(
count=self._previously_failed_count,
suffix=suffix,
noun=noun,
skipped_files=skipped_files_msg,
)
else:
self._report_status = "no previously failed tests, "
Expand Down
60 changes: 56 additions & 4 deletions testing/test_cacheprovider.py
Expand Up @@ -445,9 +445,9 @@ def test_b2():
result = testdir.runpytest("--lf")
result.stdout.fnmatch_lines(
[
"collected 4 items / 2 deselected / 2 selected",
"run-last-failure: rerun previous 2 failures",
"*2 failed, 2 deselected in*",
"collected 2 items",
"run-last-failure: rerun previous 2 failures (skipped 1 file)",
"*2 failed in*",
]
)

Expand Down Expand Up @@ -718,7 +718,7 @@ def test_bar_2():
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]

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"]

# 3. fix test_foo_4, run only test_foo.py
Expand Down Expand Up @@ -779,6 +779,58 @@ def test_2():
result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none")
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):
def test_newfirst_usecase(self, testdir):
Expand Down

0 comments on commit b654e58

Please sign in to comment.