Merge branch 'main' into improve-high-scope-fixtures-teardown-issue-3806

This commit is contained in:
Sadra Barikbin 2023-06-04 01:09:21 +03:30 committed by GitHub
commit 6fc6b153fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 20 deletions

View File

@ -0,0 +1 @@
Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.

View File

@ -27,7 +27,7 @@ from _pytest.deprecated import check_ispytest
from _pytest.fixtures import fixture from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FixtureRequest
from _pytest.main import Session from _pytest.main import Session
from _pytest.python import Module from _pytest.nodes import File
from _pytest.python import Package from _pytest.python import Package
from _pytest.reports import TestReport from _pytest.reports import TestReport
@ -226,15 +226,23 @@ class LFPluginCollWrapper:
# Sort any lf-paths to the beginning. # Sort any lf-paths to the beginning.
lf_paths = self.lfplugin._last_failed_paths lf_paths = self.lfplugin._last_failed_paths
# Use stable sort to priorize last failed.
def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool:
# Package.path is the __init__.py file, we need the directory.
if isinstance(node, Package):
path = node.path.parent
else:
path = node.path
return path in lf_paths
res.result = sorted( res.result = sorted(
res.result, res.result,
# use stable sort to priorize last failed key=sort_key,
key=lambda x: x.path in lf_paths,
reverse=True, reverse=True,
) )
return return
elif isinstance(collector, Module): elif isinstance(collector, File):
if collector.path in self.lfplugin._last_failed_paths: if collector.path in self.lfplugin._last_failed_paths:
out = yield out = yield
res = out.get_result() res = out.get_result()
@ -272,10 +280,9 @@ class LFPluginCollSkipfiles:
def pytest_make_collect_report( def pytest_make_collect_report(
self, collector: nodes.Collector self, collector: nodes.Collector
) -> Optional[CollectReport]: ) -> Optional[CollectReport]:
# Packages are Modules, but _last_failed_paths only contains # Packages are Files, but we only want to skip test-bearing Files,
# test-bearing paths and doesn't try to include the paths of their # so don't filter Packages.
# packages, so don't filter them. if isinstance(collector, File) and not isinstance(collector, Package):
if isinstance(collector, Module) and not isinstance(collector, Package):
if collector.path not in self.lfplugin._last_failed_paths: if collector.path not in self.lfplugin._last_failed_paths:
self.lfplugin._skipped_files += 1 self.lfplugin._skipped_files += 1
@ -305,9 +312,14 @@ class LFPlugin:
) )
def get_last_failed_paths(self) -> Set[Path]: def get_last_failed_paths(self) -> Set[Path]:
"""Return a set with all Paths()s of the previously failed nodeids.""" """Return a set with all Paths of the previously failed nodeids and
their parents."""
rootpath = self.config.rootpath rootpath = self.config.rootpath
result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed} result = set()
for nodeid in self.lastfailed:
path = rootpath / nodeid.split("::")[0]
result.add(path)
result.update(path.parents)
return {x for x in result if x.exists()} return {x for x in result if x.exists()}
def pytest_report_collectionfinish(self) -> Optional[str]: def pytest_report_collectionfinish(self) -> Optional[str]:

View File

@ -56,7 +56,6 @@ from _pytest.config import ExitCode
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest from _pytest.deprecated import check_ispytest
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
from _pytest.deprecated import INSTANCE_COLLECTOR from _pytest.deprecated import INSTANCE_COLLECTOR
from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.deprecated import NOSE_SUPPORT_METHOD
from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureDef
@ -698,14 +697,6 @@ class Package(Module):
func = partial(_call_with_optional_argument, teardown_module, self.obj) func = partial(_call_with_optional_argument, teardown_module, self.obj)
self.addfinalizer(func) self.addfinalizer(func)
def gethookproxy(self, fspath: "os.PathLike[str]"):
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
return self.session.gethookproxy(fspath)
def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
return self.session.isinitpath(path)
def _recurse(self, direntry: "os.DirEntry[str]") -> bool: def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
if direntry.name == "__pycache__": if direntry.name == "__pycache__":
return False return False

View File

@ -105,7 +105,7 @@ def tw_mock():
@pytest.fixture @pytest.fixture
def dummy_yaml_custom_test(pytester: Pytester): def dummy_yaml_custom_test(pytester: Pytester) -> None:
"""Writes a conftest file that collects and executes a dummy yaml test. """Writes a conftest file that collects and executes a dummy yaml test.
Taken from the docs, but stripped down to the bare minimum, useful for Taken from the docs, but stripped down to the bare minimum, useful for

View File

@ -854,6 +854,33 @@ class TestLastFailed:
] ]
) )
def test_lastfailed_skip_collection_with_nesting(self, pytester: Pytester) -> None:
"""Check that file skipping works even when the file with failures is
nested at a different level of the collection tree."""
pytester.makepyfile(
**{
"test_1.py": """
def test_1(): pass
""",
"pkg/__init__.py": "",
"pkg/test_2.py": """
def test_2(): assert False
""",
}
)
# first run
result = pytester.runpytest()
result.stdout.fnmatch_lines(["collected 2 items", "*1 failed*1 passed*"])
# second run - test_1.py is skipped.
result = pytester.runpytest("--lf")
result.stdout.fnmatch_lines(
[
"collected 1 item",
"run-last-failure: rerun previous 1 failure (skipped 1 file)",
"*= 1 failed in *",
]
)
def test_lastfailed_with_known_failures_not_being_selected( def test_lastfailed_with_known_failures_not_being_selected(
self, pytester: Pytester self, pytester: Pytester
) -> None: ) -> None:
@ -1058,6 +1085,28 @@ class TestLastFailed:
result = pytester.runpytest("--lf") result = pytester.runpytest("--lf")
result.assert_outcomes(failed=3) result.assert_outcomes(failed=3)
def test_non_python_file_skipped(
self,
pytester: Pytester,
dummy_yaml_custom_test: None,
) -> None:
pytester.makepyfile(
**{
"test_bad.py": """def test_bad(): assert False""",
},
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"])
result = pytester.runpytest("--lf")
result.stdout.fnmatch_lines(
[
"collected 1 item",
"run-last-failure: rerun previous 1 failure (skipped 1 file)",
"* 1 failed in *",
]
)
class TestNewFirst: class TestNewFirst:
def test_newfirst_usecase(self, pytester: Pytester) -> None: def test_newfirst_usecase(self, pytester: Pytester) -> None: