Merge branch 'main' into improve-high-scope-fixtures-teardown-issue-3806
This commit is contained in:
commit
6fc6b153fd
|
@ -0,0 +1 @@
|
||||||
|
Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
|
|
@ -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]:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue