Skip to content

Commit

Permalink
Revert "Expand to long paths when resolving collection arguments"
Browse files Browse the repository at this point in the history
This reverts commit 2e8f957.
  • Loading branch information
nicoddemus committed Feb 17, 2024
1 parent 2e8f957 commit abb43a6
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 78 deletions.
35 changes: 1 addition & 34 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# mypy: allow-untyped-defs
"""Python version and platform compatibility code."""
"""Python version compatibility code."""
from __future__ import annotations

import dataclasses
Expand Down Expand Up @@ -301,39 +301,6 @@ def get_user_id() -> int | None:
return uid if uid != ERROR else None


if sys.platform == "win32":
from ctypes import create_unicode_buffer
from ctypes import windll

def ensure_long_path(p: Path) -> Path:
"""
Returns the given path in its long form in Windows.
Short-paths follow the DOS restriction of 8 characters + 3 chars for file extension,
and are still supported by Windows.
"""
# If the path does not exist, we cannot discover its long path.
if not p.exists():
return p
short_path = os.fspath(p)
# Use a buffer twice the size of the original path size to (reasonably) ensure we will be able
# to hold the long path.
buffer_size = len(short_path) * 2
buffer = create_unicode_buffer(buffer_size)
windll.kernel32.GetLongPathNameW(short_path, buffer, buffer_size)
long_path_str = buffer.value
# If we could not convert it, probably better to hard-crash this now rather
# than later.
assert long_path_str, f"Failed to convert short path to long path form:\n(size: {len(short_path)}):{short_path}"
return Path(buffer.value)

else:

def ensure_long_path(p: Path) -> Path:
"""No-op in other platforms."""
return p


# Perform exhaustiveness checking.
#
# Consider this example:
Expand Down
7 changes: 4 additions & 3 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

from _pytest import nodes
import _pytest._code
from _pytest.compat import ensure_long_path
from _pytest.config import Config
from _pytest.config import directory_arg
from _pytest.config import ExitCode
Expand Down Expand Up @@ -902,6 +901,10 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
# Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
if isinstance(matchparts[0], Path):
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])

Check warning on line 907 in src/_pytest/main.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/main.py#L907

Added line #L907 was not covered by tests
# Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
else:
# TODO: Remove parametrized workaround once collection structure contains
Expand Down Expand Up @@ -1009,6 +1012,4 @@ def resolve_collection_argument(
else "directory argument cannot contain :: selection parts: {arg}"
)
raise UsageError(msg.format(arg=arg))
# Ensure we expand short paths to long paths on Windows (#11895).
fspath = ensure_long_path(fspath)
return fspath, parts
65 changes: 24 additions & 41 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from typing import List

from _pytest.assertion.util import running_on_ci
from _pytest.compat import ensure_long_path
from _pytest.config import ExitCode
from _pytest.fixtures import FixtureRequest
from _pytest.main import _in_venv
Expand Down Expand Up @@ -1764,43 +1763,27 @@ def test_foo(): assert True
assert result.parseoutcomes() == {"passed": 1}


class TestCollectionShortPaths:
@pytest.fixture
def short_path(self) -> Path:
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"
)
return Path(short_path)

@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")
def test_ensure_long_path_win(self, short_path: Path) -> None:
long_path = ensure_long_path(short_path)
assert len(os.fspath(long_path)) > len(os.fspath(short_path))

@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")
def test_collect_short_file_windows(
self, pytester: Pytester, short_path: Path
) -> None:
"""Reproducer for #11895: short paths not collected on Windows."""
test_file = 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}

def test_ensure_long_path_general(self, tmp_path: Path) -> None:
"""Sanity check: a normal path to ensure_long_path works on all platforms."""
assert ensure_long_path(tmp_path) == tmp_path
assert ensure_long_path(tmp_path / "non-existent") == tmp_path / "non-existent"
@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()

Check warning on line 1769 in testing/test_collection.py

View check run for this annotation

Codecov / codecov/patch

testing/test_collection.py#L1769

Added line #L1769 was not covered by tests
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}

Check warning on line 1789 in testing/test_collection.py

View check run for this annotation

Codecov / codecov/patch

testing/test_collection.py#L1786-L1789

Added lines #L1786 - L1789 were not covered by tests

0 comments on commit abb43a6

Please sign in to comment.