Fix collection of short paths on Windows (#11936)
Passing a short path in the command line was causing the matchparts check to fail, because ``Path(short_path) != Path(long_path)``. Using ``os.path.samefile`` as fallback ensures the comparsion works on Windows when comparing short/long paths. Fix #11895
This commit is contained in:
parent
010ce2ab0f
commit
8d9b95dcdb
|
@ -0,0 +1 @@
|
||||||
|
Fix collection on Windows where initial paths contain the short version of a path (for example ``c:\PROGRA~1\tests``).
|
|
@ -906,6 +906,10 @@ class Session(nodes.Collector):
|
||||||
# Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
|
# Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
|
||||||
if isinstance(matchparts[0], Path):
|
if isinstance(matchparts[0], Path):
|
||||||
is_match = node.path == matchparts[0]
|
is_match = node.path == matchparts[0]
|
||||||
|
if sys.platform == "win32" and not is_match:
|
||||||
|
# In case the file paths do not match, fallback to samefile() to
|
||||||
|
# account for short-paths on Windows (#11895).
|
||||||
|
is_match = os.path.samefile(node.path, matchparts[0])
|
||||||
# Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
|
# Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
|
||||||
else:
|
else:
|
||||||
# TODO: Remove parametrized workaround once collection structure contains
|
# TODO: Remove parametrized workaround once collection structure contains
|
||||||
|
|
|
@ -4,9 +4,11 @@ from pathlib import Path
|
||||||
import pprint
|
import pprint
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from _pytest.assertion.util import running_on_ci
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.main import _in_venv
|
from _pytest.main import _in_venv
|
||||||
|
@ -1759,3 +1761,29 @@ def test_does_not_crash_on_recursive_symlink(pytester: Pytester) -> None:
|
||||||
|
|
||||||
assert result.ret == ExitCode.OK
|
assert result.ret == ExitCode.OK
|
||||||
assert result.parseoutcomes() == {"passed": 1}
|
assert result.parseoutcomes() == {"passed": 1}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")
|
||||||
|
def test_collect_short_file_windows(pytester: Pytester) -> None:
|
||||||
|
"""Reproducer for #11895: short paths not colleced on Windows."""
|
||||||
|
short_path = tempfile.mkdtemp()
|
||||||
|
if "~" not in short_path: # pragma: no cover
|
||||||
|
if running_on_ci():
|
||||||
|
# On CI, we are expecting that under the current GitHub actions configuration,
|
||||||
|
# tempfile.mkdtemp() is producing short paths, so we want to fail to prevent
|
||||||
|
# this from silently changing without us noticing.
|
||||||
|
pytest.fail(
|
||||||
|
f"tempfile.mkdtemp() failed to produce a short path on CI: {short_path}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# We want to skip failing this test locally in this situation because
|
||||||
|
# depending on the local configuration tempfile.mkdtemp() might not produce a short path:
|
||||||
|
# For example, user might have configured %TEMP% exactly to avoid generating short paths.
|
||||||
|
pytest.skip(
|
||||||
|
f"tempfile.mkdtemp() failed to produce a short path: {short_path}, skipping"
|
||||||
|
)
|
||||||
|
|
||||||
|
test_file = Path(short_path).joinpath("test_collect_short_file_windows.py")
|
||||||
|
test_file.write_text("def test(): pass", encoding="UTF-8")
|
||||||
|
result = pytester.runpytest(short_path)
|
||||||
|
assert result.parseoutcomes() == {"passed": 1}
|
||||||
|
|
Loading…
Reference in New Issue