diff --git a/changelog/4321.bugfix.rst b/changelog/4321.bugfix.rst new file mode 100644 index 000000000..8bfa9efde --- /dev/null +++ b/changelog/4321.bugfix.rst @@ -0,0 +1 @@ +Fix ``item.nodeid`` with resolved symlinks. diff --git a/changelog/4325.bugfix.rst b/changelog/4325.bugfix.rst new file mode 100644 index 000000000..71f13f429 --- /dev/null +++ b/changelog/4325.bugfix.rst @@ -0,0 +1 @@ +Fix collection of direct symlinked files, where the target does not match ``python_files``. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 910812419..6e0eea0f0 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -488,7 +488,7 @@ class Session(nodes.FSCollector): from _pytest.python import Package names = self._parsearg(arg) - argpath = names.pop(0).realpath() + argpath = names.pop(0) # Start with a Session root, and delve to argpath item (dir or file) # and stack all Packages found on the way. @@ -636,7 +636,7 @@ class Session(nodes.FSCollector): "file or package not found: " + arg + " (missing __init__.py?)" ) raise UsageError("file not found: " + arg) - parts[0] = path + parts[0] = path.realpath() return parts def matchnodes(self, matching, names): diff --git a/testing/test_collection.py b/testing/test_collection.py index 18033b9c0..62bc0caf8 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -6,6 +6,8 @@ import pprint import sys import textwrap +import py + import pytest from _pytest.main import _in_venv from _pytest.main import EXIT_NOTESTSCOLLECTED @@ -1051,3 +1053,55 @@ def test_collect_handles_raising_on_dunder_class(testdir): result = testdir.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed in*"]) + + +@pytest.mark.skipif( + not hasattr(py.path.local, "mksymlinkto"), + reason="symlink not available on this platform", +) +def test_collect_symlink_file_arg(testdir): + """Test that collecting a direct symlink, where the target does not match python_files works (#4325).""" + real = testdir.makepyfile( + real=""" + def test_nodeid(request): + assert request.node.nodeid == "real.py::test_nodeid" + """ + ) + symlink = testdir.tmpdir.join("symlink.py") + symlink.mksymlinkto(real) + result = testdir.runpytest("-v", symlink) + result.stdout.fnmatch_lines(["real.py::test_nodeid PASSED*", "*1 passed in*"]) + assert result.ret == 0 + + +@pytest.mark.skipif( + not hasattr(py.path.local, "mksymlinkto"), + reason="symlink not available on this platform", +) +def test_collect_symlink_out_of_tree(testdir): + """Test collection of symlink via out-of-tree rootdir.""" + sub = testdir.tmpdir.join("sub") + real = sub.join("test_real.py") + real.write( + textwrap.dedent( + """ + def test_nodeid(request): + # Should not contain sub/ prefix. + assert request.node.nodeid == "test_real.py::test_nodeid" + """ + ), + ensure=True, + ) + + out_of_tree = testdir.tmpdir.join("out_of_tree").ensure(dir=True) + symlink_to_sub = out_of_tree.join("symlink_to_sub") + symlink_to_sub.mksymlinkto(sub) + sub.chdir() + result = testdir.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub) + result.stdout.fnmatch_lines( + [ + # Should not contain "sub/"! + "test_real.py::test_nodeid PASSED" + ] + ) + assert result.ret == 0