diff --git a/changelog/6701.bugfix.rst b/changelog/6701.bugfix.rst new file mode 100644 index 00000000000..6edb893be0d --- /dev/null +++ b/changelog/6701.bugfix.rst @@ -0,0 +1 @@ +Node ids for paths outside of the rootdir are generated properly, e.g. for ``pytest testing --rootdir=/tmp -vv``. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index c53db166a90..aa1f168c49b 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -415,12 +415,6 @@ def _prunetraceback(self, excinfo): excinfo.traceback = ntraceback -def _check_initialpaths_for_relpath(session, fspath): - for initial_path in session._initialpaths: - if fspath.common(initial_path) == initial_path: - return fspath.relto(initial_path) - - class FSHookProxy: def __init__( self, fspath: py.path.local, pm: PytestPluginManager, remove_mods @@ -437,7 +431,12 @@ def __getattr__(self, name: str): class FSCollector(Collector): def __init__( - self, fspath: py.path.local, parent=None, config=None, session=None, nodeid=None + self, + fspath: py.path.local, + parent=None, + config=None, + session=None, + nodeid: Optional[str] = None, ) -> None: name = fspath.basename if parent is not None: @@ -450,11 +449,8 @@ def __init__( session = session or parent.session if nodeid is None: - nodeid = self.fspath.relto(session.config.rootdir) - - if not nodeid: - nodeid = _check_initialpaths_for_relpath(session, fspath) - if nodeid and os.sep != SEP: + nodeid = session._node_location_to_relpath(self.fspath) + if os.sep != SEP: nodeid = nodeid.replace(os.sep, SEP) super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 48509fa913a..08fdd3d15f6 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -737,8 +737,9 @@ def test_cmdline_python_namespace_package(self, testdir, monkeypatch): assert result.ret == 0 result.stdout.fnmatch_lines( [ - "test_hello.py::test_hello*PASSED*", - "test_hello.py::test_other*PASSED*", + "rootdir: {}/world".format(testdir.tmpdir), + "../hello/ns_pkg/hello/test_hello.py::test_hello PASSED *", + "../hello/ns_pkg/hello/test_hello.py::test_other PASSED *", "ns_pkg/world/test_world.py::test_world*PASSED*", "ns_pkg/world/test_world.py::test_other*PASSED*", "*4 passed in*", @@ -752,7 +753,11 @@ def test_cmdline_python_namespace_package(self, testdir, monkeypatch): ) assert result.ret == 0 result.stdout.fnmatch_lines( - ["*test_world.py::test_other*PASSED*", "*1 passed*"] + [ + "rootdir: {}".format(testdir.tmpdir), + "world/ns_pkg/world/test_world.py::test_other PASSED *", + "=* 1 passed in *=", + ] ) def test_invoke_test_and_doctestmodules(self, testdir): diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index bfbe359515c..babf3d966a1 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3690,6 +3690,7 @@ def test_foo(request): result = testdir.runpytest() result.stdout.fnmatch_lines( [ + "test_foos.py F *", "The requested fixture has no parameter defined for test:", " test_foos.py::test_foo", "", @@ -3707,8 +3708,9 @@ def test_foo(request): result = testdir.runpytest("--rootdir", rootdir, tests_dir) result.stdout.fnmatch_lines( [ + "../tests/test_foos.py F *", "The requested fixture has no parameter defined for test:", - " test_foos.py::test_foo", + " ../tests/test_foos.py::test_foo", "", "Requested fixture 'fix_with_param' defined in:", "{}:4".format(fixfile), diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 9e893152d1a..0a8133bc2d9 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -274,7 +274,14 @@ def fixture(): build.join(f).mksymlinkto(real.join(f)) build.chdir() result = testdir.runpytest("-vs", "app/test_foo.py") - result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"]) + result.stdout.fnmatch_lines( + [ + "conftest_loaded", + "../real/app/test_foo.py::test1 fixture_used", + "PASSED", + "*= 1 passed in *", + ] + ) assert result.ret == ExitCode.OK diff --git a/testing/test_nodes.py b/testing/test_nodes.py index b13ce1fe604..cc1ee1583ec 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,5 +1,3 @@ -import py - import pytest from _pytest import nodes @@ -31,23 +29,3 @@ def test(): ) with pytest.raises(ValueError, match=".*instance of PytestWarning.*"): items[0].warn(UserWarning("some warning")) - - -def test__check_initialpaths_for_relpath(): - """Ensure that it handles dirs, and does not always use dirname.""" - cwd = py.path.local() - - class FakeSession: - _initialpaths = [cwd] - - assert nodes._check_initialpaths_for_relpath(FakeSession, cwd) == "" - - sub = cwd.join("file") - - class FakeSession: - _initialpaths = [cwd] - - assert nodes._check_initialpaths_for_relpath(FakeSession, sub) == "file" - - outside = py.path.local("/outside") - assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None diff --git a/testing/test_session.py b/testing/test_session.py index 9138e2cb383..3cd8828b5f7 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -1,5 +1,8 @@ +import os + import pytest from _pytest.main import ExitCode +from _pytest.pytester import Testdir class SessionTests: @@ -334,7 +337,7 @@ def pytest_sessionfinish(): @pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"]) -def test_rootdir_option_arg(testdir, monkeypatch, path): +def test_rootdir_option_arg(testdir: Testdir, monkeypatch, path: str) -> None: monkeypatch.setenv("PY_ROOTDIR_PATH", str(testdir.tmpdir)) path = path.format(relative=str(testdir.tmpdir), environment="$PY_ROOTDIR_PATH") @@ -344,14 +347,19 @@ def test_rootdir_option_arg(testdir, monkeypatch, path): """ import os def test_one(): - assert 1 - """ + assert os.getcwd() == {!r} + """.format( + os.getcwd() + ) ) + expected = [ + "*rootdir: ~/root", + "test_rootdir_option_arg.py *", + "*1 passed*", + ] result = testdir.runpytest("--rootdir={}".format(path)) - result.stdout.fnmatch_lines( - ["*rootdir: ~/root", "root/test_rootdir_option_arg.py *", "*1 passed*"] - ) + result.stdout.fnmatch_lines(expected) def test_rootdir_wrong_option_arg(testdir):