Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--lf now skips colletion of files without failed tests #5185

Merged
merged 1 commit into from
Apr 29, 2019
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
2 changes: 2 additions & 0 deletions changelog/5172.feature.rst
Original file line number Diff line number Diff line change
@@ -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.
41 changes: 39 additions & 2 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,33 @@ def __init__(self, config):
self.lastfailed = config.cache.get("cache/lastfailed", {})
self._previously_failed_count = 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):
if self.active and self.config.getoption("verbose") >= 0:
Expand Down Expand Up @@ -206,9 +233,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
Original file line number Diff line number Diff line change
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