Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
This commit is contained in:
parent
c17d50829f
commit
322190fd84
|
@ -0,0 +1,9 @@
|
||||||
|
symlinks are no longer resolved during collection and matching `conftest.py` files with test file paths.
|
||||||
|
|
||||||
|
Resolving symlinks for the current directory and during collection was introduced as a bugfix in 3.9.0, but it actually is a new feature which had unfortunate consequences in Windows and surprising results in other platforms.
|
||||||
|
|
||||||
|
The team decided to step back on resolving symlinks at all, planning to review this in the future with a more solid solution (see discussion in
|
||||||
|
`#6523 <https://github.com/pytest-dev/pytest/pull/6523>`__ for details).
|
||||||
|
|
||||||
|
This might break test suites which made use of this feature; the fix is to create a symlink
|
||||||
|
for the entire test tree, and not only to partial files/tress as it was possible previously.
|
|
@ -123,9 +123,9 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
buffered = hasattr(stream.buffer, "raw")
|
buffered = hasattr(stream.buffer, "raw")
|
||||||
raw_stdout = stream.buffer.raw if buffered else stream.buffer
|
raw_stdout = stream.buffer.raw if buffered else stream.buffer # type: ignore[attr-defined]
|
||||||
|
|
||||||
if not isinstance(raw_stdout, io._WindowsConsoleIO):
|
if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined]
|
||||||
return
|
return
|
||||||
|
|
||||||
def _reopen_stdio(f, mode):
|
def _reopen_stdio(f, mode):
|
||||||
|
@ -135,7 +135,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
|
||||||
buffering = -1
|
buffering = -1
|
||||||
|
|
||||||
return io.TextIOWrapper(
|
return io.TextIOWrapper(
|
||||||
open(os.dup(f.fileno()), mode, buffering),
|
open(os.dup(f.fileno()), mode, buffering), # type: ignore[arg-type]
|
||||||
f.encoding,
|
f.encoding,
|
||||||
f.errors,
|
f.errors,
|
||||||
f.newlines,
|
f.newlines,
|
||||||
|
|
|
@ -232,7 +232,7 @@ def get_config(args=None, plugins=None):
|
||||||
config = Config(
|
config = Config(
|
||||||
pluginmanager,
|
pluginmanager,
|
||||||
invocation_params=Config.InvocationParams(
|
invocation_params=Config.InvocationParams(
|
||||||
args=args or (), plugins=plugins, dir=Path().resolve()
|
args=args or (), plugins=plugins, dir=Path.cwd()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ class PytestPluginManager(PluginManager):
|
||||||
# and allow users to opt into looking into the rootdir parent
|
# and allow users to opt into looking into the rootdir parent
|
||||||
# directories instead of requiring to specify confcutdir
|
# directories instead of requiring to specify confcutdir
|
||||||
clist = []
|
clist = []
|
||||||
for parent in directory.realpath().parts():
|
for parent in directory.parts():
|
||||||
if self._confcutdir and self._confcutdir.relto(parent):
|
if self._confcutdir and self._confcutdir.relto(parent):
|
||||||
continue
|
continue
|
||||||
conftestpath = parent.join("conftest.py")
|
conftestpath = parent.join("conftest.py")
|
||||||
|
@ -798,7 +798,7 @@ class Config:
|
||||||
|
|
||||||
if invocation_params is None:
|
if invocation_params is None:
|
||||||
invocation_params = self.InvocationParams(
|
invocation_params = self.InvocationParams(
|
||||||
args=(), plugins=None, dir=Path().resolve()
|
args=(), plugins=None, dir=Path.cwd()
|
||||||
)
|
)
|
||||||
|
|
||||||
self.option = argparse.Namespace()
|
self.option = argparse.Namespace()
|
||||||
|
|
|
@ -1496,7 +1496,7 @@ class FixtureManager:
|
||||||
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
|
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
|
||||||
nodeid = None
|
nodeid = None
|
||||||
try:
|
try:
|
||||||
p = py.path.local(plugin.__file__).realpath() # type: ignore[attr-defined] # noqa: F821
|
p = py.path.local(plugin.__file__) # type: ignore[attr-defined] # noqa: F821
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -665,7 +665,6 @@ class Session(nodes.FSCollector):
|
||||||
"file or package not found: " + arg + " (missing __init__.py?)"
|
"file or package not found: " + arg + " (missing __init__.py?)"
|
||||||
)
|
)
|
||||||
raise UsageError("file not found: " + arg)
|
raise UsageError("file not found: " + arg)
|
||||||
fspath = fspath.realpath()
|
|
||||||
return (fspath, parts)
|
return (fspath, parts)
|
||||||
|
|
||||||
def matchnodes(
|
def matchnodes(
|
||||||
|
|
|
@ -18,6 +18,7 @@ from typing import Set
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
from _pytest.outcomes import skip
|
||||||
from _pytest.warning_types import PytestWarning
|
from _pytest.warning_types import PytestWarning
|
||||||
|
|
||||||
if sys.version_info[:2] >= (3, 6):
|
if sys.version_info[:2] >= (3, 6):
|
||||||
|
@ -397,3 +398,11 @@ def fnmatch_ex(pattern: str, path) -> bool:
|
||||||
def parts(s: str) -> Set[str]:
|
def parts(s: str) -> Set[str]:
|
||||||
parts = s.split(sep)
|
parts = s.split(sep)
|
||||||
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
|
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
|
||||||
|
|
||||||
|
|
||||||
|
def symlink_or_skip(src, dst, **kwargs):
|
||||||
|
"""Makes a symlink or skips the test in case symlinks are not supported."""
|
||||||
|
try:
|
||||||
|
os.symlink(str(src), str(dst), **kwargs)
|
||||||
|
except OSError as e:
|
||||||
|
skip("symlinks not supported: {}".format(e))
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
@ -9,6 +8,7 @@ import py
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import importlib_metadata
|
from _pytest.compat import importlib_metadata
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
|
from _pytest.pathlib import symlink_or_skip
|
||||||
from _pytest.pytester import Testdir
|
from _pytest.pytester import Testdir
|
||||||
|
|
||||||
|
|
||||||
|
@ -266,29 +266,6 @@ class TestGeneralUsage:
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
assert "should be seen" in result.stdout.str()
|
assert "should be seen" in result.stdout.str()
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(py.path.local, "mksymlinkto"),
|
|
||||||
reason="symlink not available on this platform",
|
|
||||||
)
|
|
||||||
def test_chdir(self, testdir):
|
|
||||||
testdir.tmpdir.join("py").mksymlinkto(py._pydir)
|
|
||||||
p = testdir.tmpdir.join("main.py")
|
|
||||||
p.write(
|
|
||||||
textwrap.dedent(
|
|
||||||
"""\
|
|
||||||
import sys, os
|
|
||||||
sys.path.insert(0, '')
|
|
||||||
import py
|
|
||||||
print(py.__file__)
|
|
||||||
print(py.__path__)
|
|
||||||
os.chdir(os.path.dirname(os.getcwd()))
|
|
||||||
print(py.log)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result = testdir.runpython(p)
|
|
||||||
assert not result.ret
|
|
||||||
|
|
||||||
def test_issue109_sibling_conftests_not_loaded(self, testdir):
|
def test_issue109_sibling_conftests_not_loaded(self, testdir):
|
||||||
sub1 = testdir.mkdir("sub1")
|
sub1 = testdir.mkdir("sub1")
|
||||||
sub2 = testdir.mkdir("sub2")
|
sub2 = testdir.mkdir("sub2")
|
||||||
|
@ -762,19 +739,9 @@ class TestInvocationVariants:
|
||||||
|
|
||||||
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
|
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
|
||||||
"""
|
"""
|
||||||
test --pyargs option with packages with path containing symlink can
|
--pyargs with packages with path containing symlink can have conftest.py in
|
||||||
have conftest.py in their package (#2985)
|
their package (#2985)
|
||||||
"""
|
"""
|
||||||
# dummy check that we can actually create symlinks: on Windows `os.symlink` is available,
|
|
||||||
# but normal users require special admin privileges to create symlinks.
|
|
||||||
if sys.platform == "win32":
|
|
||||||
try:
|
|
||||||
os.symlink(
|
|
||||||
str(testdir.tmpdir.ensure("tmpfile")),
|
|
||||||
str(testdir.tmpdir.join("tmpfile2")),
|
|
||||||
)
|
|
||||||
except OSError as e:
|
|
||||||
pytest.skip(str(e.args[0]))
|
|
||||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||||
|
|
||||||
dirname = "lib"
|
dirname = "lib"
|
||||||
|
@ -790,13 +757,13 @@ class TestInvocationVariants:
|
||||||
"import pytest\n@pytest.fixture\ndef a_fixture():pass"
|
"import pytest\n@pytest.fixture\ndef a_fixture():pass"
|
||||||
)
|
)
|
||||||
|
|
||||||
d_local = testdir.mkdir("local")
|
d_local = testdir.mkdir("symlink_root")
|
||||||
symlink_location = os.path.join(str(d_local), "lib")
|
symlink_location = d_local / "lib"
|
||||||
os.symlink(str(d), symlink_location, target_is_directory=True)
|
symlink_or_skip(d, symlink_location, target_is_directory=True)
|
||||||
|
|
||||||
# The structure of the test directory is now:
|
# The structure of the test directory is now:
|
||||||
# .
|
# .
|
||||||
# ├── local
|
# ├── symlink_root
|
||||||
# │ └── lib -> ../lib
|
# │ └── lib -> ../lib
|
||||||
# └── lib
|
# └── lib
|
||||||
# └── foo
|
# └── foo
|
||||||
|
@ -807,32 +774,23 @@ class TestInvocationVariants:
|
||||||
# └── test_bar.py
|
# └── test_bar.py
|
||||||
|
|
||||||
# NOTE: the different/reversed ordering is intentional here.
|
# NOTE: the different/reversed ordering is intentional here.
|
||||||
search_path = ["lib", os.path.join("local", "lib")]
|
search_path = ["lib", os.path.join("symlink_root", "lib")]
|
||||||
monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path))
|
monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path))
|
||||||
for p in search_path:
|
for p in search_path:
|
||||||
monkeypatch.syspath_prepend(p)
|
monkeypatch.syspath_prepend(p)
|
||||||
|
|
||||||
# module picked up in symlink-ed directory:
|
# module picked up in symlink-ed directory:
|
||||||
# It picks up local/lib/foo/bar (symlink) via sys.path.
|
# It picks up symlink_root/lib/foo/bar (symlink) via sys.path.
|
||||||
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
|
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
|
||||||
testdir.chdir()
|
testdir.chdir()
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
if hasattr(py.path.local, "mksymlinkto"):
|
result.stdout.fnmatch_lines(
|
||||||
result.stdout.fnmatch_lines(
|
[
|
||||||
[
|
"symlink_root/lib/foo/bar/test_bar.py::test_bar PASSED*",
|
||||||
"lib/foo/bar/test_bar.py::test_bar PASSED*",
|
"symlink_root/lib/foo/bar/test_bar.py::test_other PASSED*",
|
||||||
"lib/foo/bar/test_bar.py::test_other PASSED*",
|
"*2 passed*",
|
||||||
"*2 passed*",
|
]
|
||||||
]
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
[
|
|
||||||
"*lib/foo/bar/test_bar.py::test_bar PASSED*",
|
|
||||||
"*lib/foo/bar/test_bar.py::test_other PASSED*",
|
|
||||||
"*2 passed*",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_cmdline_python_package_not_exists(self, testdir):
|
def test_cmdline_python_package_not_exists(self, testdir):
|
||||||
result = testdir.runpytest("--pyargs", "tpkgwhatv")
|
result = testdir.runpytest("--pyargs", "tpkgwhatv")
|
||||||
|
|
|
@ -3,12 +3,11 @@ import pprint
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.main import _in_venv
|
from _pytest.main import _in_venv
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
|
from _pytest.pathlib import symlink_or_skip
|
||||||
from _pytest.pytester import Testdir
|
from _pytest.pytester import Testdir
|
||||||
|
|
||||||
|
|
||||||
|
@ -1164,29 +1163,21 @@ def test_collect_pyargs_with_testpaths(testdir, monkeypatch):
|
||||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
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):
|
def test_collect_symlink_file_arg(testdir):
|
||||||
"""Test that collecting a direct symlink, where the target does not match python_files works (#4325)."""
|
"""Collect a direct symlink works even if it does not match python_files (#4325)."""
|
||||||
real = testdir.makepyfile(
|
real = testdir.makepyfile(
|
||||||
real="""
|
real="""
|
||||||
def test_nodeid(request):
|
def test_nodeid(request):
|
||||||
assert request.node.nodeid == "real.py::test_nodeid"
|
assert request.node.nodeid == "symlink.py::test_nodeid"
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
symlink = testdir.tmpdir.join("symlink.py")
|
symlink = testdir.tmpdir.join("symlink.py")
|
||||||
symlink.mksymlinkto(real)
|
symlink_or_skip(real, symlink)
|
||||||
result = testdir.runpytest("-v", symlink)
|
result = testdir.runpytest("-v", symlink)
|
||||||
result.stdout.fnmatch_lines(["real.py::test_nodeid PASSED*", "*1 passed in*"])
|
result.stdout.fnmatch_lines(["symlink.py::test_nodeid PASSED*", "*1 passed in*"])
|
||||||
assert result.ret == 0
|
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):
|
def test_collect_symlink_out_of_tree(testdir):
|
||||||
"""Test collection of symlink via out-of-tree rootdir."""
|
"""Test collection of symlink via out-of-tree rootdir."""
|
||||||
sub = testdir.tmpdir.join("sub")
|
sub = testdir.tmpdir.join("sub")
|
||||||
|
@ -1204,7 +1195,7 @@ def test_collect_symlink_out_of_tree(testdir):
|
||||||
|
|
||||||
out_of_tree = testdir.tmpdir.join("out_of_tree").ensure(dir=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 = out_of_tree.join("symlink_to_sub")
|
||||||
symlink_to_sub.mksymlinkto(sub)
|
symlink_or_skip(sub, symlink_to_sub)
|
||||||
sub.chdir()
|
sub.chdir()
|
||||||
result = testdir.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub)
|
result = testdir.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub)
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
|
@ -1270,22 +1261,19 @@ def test_collect_pkg_init_only(testdir):
|
||||||
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
|
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(py.path.local, "mksymlinkto"),
|
|
||||||
reason="symlink not available on this platform",
|
|
||||||
)
|
|
||||||
@pytest.mark.parametrize("use_pkg", (True, False))
|
@pytest.mark.parametrize("use_pkg", (True, False))
|
||||||
def test_collect_sub_with_symlinks(use_pkg, testdir):
|
def test_collect_sub_with_symlinks(use_pkg, testdir):
|
||||||
|
"""Collection works with symlinked files and broken symlinks"""
|
||||||
sub = testdir.mkdir("sub")
|
sub = testdir.mkdir("sub")
|
||||||
if use_pkg:
|
if use_pkg:
|
||||||
sub.ensure("__init__.py")
|
sub.ensure("__init__.py")
|
||||||
sub.ensure("test_file.py").write("def test_file(): pass")
|
sub.join("test_file.py").write("def test_file(): pass")
|
||||||
|
|
||||||
# Create a broken symlink.
|
# Create a broken symlink.
|
||||||
sub.join("test_broken.py").mksymlinkto("test_doesnotexist.py")
|
symlink_or_skip("test_doesnotexist.py", sub.join("test_broken.py"))
|
||||||
|
|
||||||
# Symlink that gets collected.
|
# Symlink that gets collected.
|
||||||
sub.join("test_symlink.py").mksymlinkto("test_file.py")
|
symlink_or_skip("test_file.py", sub.join("test_symlink.py"))
|
||||||
|
|
||||||
result = testdir.runpytest("-v", str(sub))
|
result = testdir.runpytest("-v", str(sub))
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import pytest
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.config import PytestPluginManager
|
from _pytest.config import PytestPluginManager
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
|
from _pytest.pathlib import symlink_or_skip
|
||||||
|
|
||||||
|
|
||||||
def ConftestWithSetinitial(path):
|
def ConftestWithSetinitial(path):
|
||||||
|
@ -190,16 +191,25 @@ def test_conftest_confcutdir(testdir):
|
||||||
result.stdout.no_fnmatch_line("*warning: could not load initial*")
|
result.stdout.no_fnmatch_line("*warning: could not load initial*")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(py.path.local, "mksymlinkto"),
|
|
||||||
reason="symlink not available on this platform",
|
|
||||||
)
|
|
||||||
def test_conftest_symlink(testdir):
|
def test_conftest_symlink(testdir):
|
||||||
"""Ensure that conftest.py is used for resolved symlinks."""
|
"""
|
||||||
|
conftest.py discovery follows normal path resolution and does not resolve symlinks.
|
||||||
|
"""
|
||||||
|
# Structure:
|
||||||
|
# /real
|
||||||
|
# /real/conftest.py
|
||||||
|
# /real/app
|
||||||
|
# /real/app/tests
|
||||||
|
# /real/app/tests/test_foo.py
|
||||||
|
|
||||||
|
# Links:
|
||||||
|
# /symlinktests -> /real/app/tests (running at symlinktests should fail)
|
||||||
|
# /symlink -> /real (running at /symlink should work)
|
||||||
|
|
||||||
real = testdir.tmpdir.mkdir("real")
|
real = testdir.tmpdir.mkdir("real")
|
||||||
realtests = real.mkdir("app").mkdir("tests")
|
realtests = real.mkdir("app").mkdir("tests")
|
||||||
testdir.tmpdir.join("symlinktests").mksymlinkto(realtests)
|
symlink_or_skip(realtests, testdir.tmpdir.join("symlinktests"))
|
||||||
testdir.tmpdir.join("symlink").mksymlinkto(real)
|
symlink_or_skip(real, testdir.tmpdir.join("symlink"))
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
**{
|
**{
|
||||||
"real/app/tests/test_foo.py": "def test1(fixture): pass",
|
"real/app/tests/test_foo.py": "def test1(fixture): pass",
|
||||||
|
@ -216,38 +226,20 @@ def test_conftest_symlink(testdir):
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Should fail because conftest cannot be found from the link structure.
|
||||||
result = testdir.runpytest("-vs", "symlinktests")
|
result = testdir.runpytest("-vs", "symlinktests")
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(["*fixture 'fixture' not found*"])
|
||||||
[
|
assert result.ret == ExitCode.TESTS_FAILED
|
||||||
"*conftest_loaded*",
|
|
||||||
"real/app/tests/test_foo.py::test1 fixture_used",
|
|
||||||
"PASSED",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
assert result.ret == ExitCode.OK
|
|
||||||
|
|
||||||
# Should not cause "ValueError: Plugin already registered" (#4174).
|
# Should not cause "ValueError: Plugin already registered" (#4174).
|
||||||
result = testdir.runpytest("-vs", "symlink")
|
result = testdir.runpytest("-vs", "symlink")
|
||||||
assert result.ret == ExitCode.OK
|
assert result.ret == ExitCode.OK
|
||||||
|
|
||||||
realtests.ensure("__init__.py")
|
|
||||||
result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1")
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
[
|
|
||||||
"*conftest_loaded*",
|
|
||||||
"real/app/tests/test_foo.py::test1 fixture_used",
|
|
||||||
"PASSED",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
assert result.ret == ExitCode.OK
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
not hasattr(py.path.local, "mksymlinkto"),
|
|
||||||
reason="symlink not available on this platform",
|
|
||||||
)
|
|
||||||
def test_conftest_symlink_files(testdir):
|
def test_conftest_symlink_files(testdir):
|
||||||
"""Check conftest.py loading when running in directory with symlinks."""
|
"""Symlinked conftest.py are found when pytest is executed in a directory with symlinked
|
||||||
|
files."""
|
||||||
real = testdir.tmpdir.mkdir("real")
|
real = testdir.tmpdir.mkdir("real")
|
||||||
source = {
|
source = {
|
||||||
"app/test_foo.py": "def test1(fixture): pass",
|
"app/test_foo.py": "def test1(fixture): pass",
|
||||||
|
@ -271,7 +263,7 @@ def test_conftest_symlink_files(testdir):
|
||||||
build = testdir.tmpdir.mkdir("build")
|
build = testdir.tmpdir.mkdir("build")
|
||||||
build.mkdir("app")
|
build.mkdir("app")
|
||||||
for f in source:
|
for f in source:
|
||||||
build.join(f).mksymlinkto(real.join(f))
|
symlink_or_skip(real.join(f), build.join(f))
|
||||||
build.chdir()
|
build.chdir()
|
||||||
result = testdir.runpytest("-vs", "app/test_foo.py")
|
result = testdir.runpytest("-vs", "app/test_foo.py")
|
||||||
result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"])
|
result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"])
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from string import ascii_lowercase
|
||||||
|
|
||||||
|
import py.path
|
||||||
|
|
||||||
|
from _pytest import pytester
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def subst_path_windows(filename):
|
||||||
|
for c in ascii_lowercase[7:]: # Create a subst drive from H-Z.
|
||||||
|
c += ":"
|
||||||
|
if not os.path.exists(c):
|
||||||
|
drive = c
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise AssertionError("Unable to find suitable drive letter for subst.")
|
||||||
|
|
||||||
|
directory = filename.dirpath()
|
||||||
|
basename = filename.basename
|
||||||
|
|
||||||
|
args = ["subst", drive, str(directory)]
|
||||||
|
subprocess.check_call(args)
|
||||||
|
assert os.path.exists(drive)
|
||||||
|
try:
|
||||||
|
filename = py.path.local(drive) / basename
|
||||||
|
yield filename
|
||||||
|
finally:
|
||||||
|
args = ["subst", "/D", drive]
|
||||||
|
subprocess.check_call(args)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def subst_path_linux(filename):
|
||||||
|
directory = filename.dirpath()
|
||||||
|
basename = filename.basename
|
||||||
|
|
||||||
|
target = directory / ".." / "sub2"
|
||||||
|
os.symlink(str(directory), str(target), target_is_directory=True)
|
||||||
|
try:
|
||||||
|
filename = target / basename
|
||||||
|
yield filename
|
||||||
|
finally:
|
||||||
|
# We don't need to unlink (it's all in the tempdir).
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_link_resolve(testdir: pytester.Testdir) -> None:
|
||||||
|
"""
|
||||||
|
See: https://github.com/pytest-dev/pytest/issues/5965
|
||||||
|
"""
|
||||||
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
p = sub1.join("test_foo.py")
|
||||||
|
p.write(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
def test_foo():
|
||||||
|
raise AssertionError()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
subst = subst_path_linux
|
||||||
|
if sys.platform == "win32":
|
||||||
|
subst = subst_path_windows
|
||||||
|
|
||||||
|
with subst(p) as subst_p:
|
||||||
|
result = testdir.runpytest(str(subst_p), "-v")
|
||||||
|
# i.e.: Make sure that the error is reported as a relative path, not as a
|
||||||
|
# resolved path.
|
||||||
|
# See: https://github.com/pytest-dev/pytest/issues/5965
|
||||||
|
stdout = result.stdout.str()
|
||||||
|
assert "sub1/test_foo.py" not in stdout
|
||||||
|
|
||||||
|
# i.e.: Expect drive on windows because we just have drive:filename, whereas
|
||||||
|
# we expect a relative path on Linux.
|
||||||
|
expect = (
|
||||||
|
"*{}*".format(subst_p) if sys.platform == "win32" else "*sub2/test_foo.py*"
|
||||||
|
)
|
||||||
|
result.stdout.fnmatch_lines([expect])
|
Loading…
Reference in New Issue