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:
Bruno Oliveira 2024-02-23 07:51:15 -03:00 committed by GitHub
parent 010ce2ab0f
commit 8d9b95dcdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 33 additions and 0 deletions

View File

@ -0,0 +1 @@
Fix collection on Windows where initial paths contain the short version of a path (for example ``c:\PROGRA~1\tests``).

View File

@ -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

View File

@ -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}