parent
ebc0cea226
commit
08734bdd18
|
@ -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.
|
|
@ -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, "
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue