Skip to content

Commit 3d27ab9

Browse files
RonnyPfannschmidtCursor AIAnthropic Claude Opus 4
authored
add: nodes.norm_sep helper for nodeid path separator normalization (#14120)
Consolidates scattered inline implementations into a single consistent helper. Co-authored-by: Cursor AI <[email protected]> Co-authored-by: Anthropic Claude Opus 4 <[email protected]>
1 parent 6d0aff1 commit 3d27ab9

File tree

4 files changed

+51
-8
lines changed

4 files changed

+51
-8
lines changed

src/_pytest/fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,8 +1635,8 @@ def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> N
16351635
nodeid = ""
16361636
if nodeid == ".":
16371637
nodeid = ""
1638-
if os.sep != nodes.SEP:
1639-
nodeid = nodeid.replace(os.sep, nodes.SEP)
1638+
elif nodeid:
1639+
nodeid = nodes.norm_sep(nodeid)
16401640
else:
16411641
nodeid = None
16421642

src/_pytest/nodes.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@
5151

5252
SEP = "/"
5353

54+
55+
def norm_sep(path: str | os.PathLike[str]) -> str:
56+
"""Normalize path separators to forward slashes for nodeid compatibility.
57+
58+
Replaces backslashes with forward slashes. This handles both Windows native
59+
paths and cross-platform data (e.g., Windows paths in serialized test reports
60+
when running on Linux).
61+
62+
:param path: A path string or PathLike object.
63+
:returns: String with all backslashes replaced by forward slashes.
64+
"""
65+
return os.fspath(path).replace("\\", SEP)
66+
67+
5468
tracebackcutdir = Path(_pytest.__file__).parent
5569

5670

@@ -589,7 +603,7 @@ def __init__(
589603
pass
590604
else:
591605
name = str(rel)
592-
name = name.replace(os.sep, SEP)
606+
name = norm_sep(name)
593607
self.path = path
594608

595609
if session is None:
@@ -602,8 +616,8 @@ def __init__(
602616
except ValueError:
603617
nodeid = _check_initialpaths_for_relpath(session._initialpaths, path)
604618

605-
if nodeid and os.sep != SEP:
606-
nodeid = nodeid.replace(os.sep, SEP)
619+
if nodeid:
620+
nodeid = norm_sep(nodeid)
607621

608622
super().__init__(
609623
name=name,

src/_pytest/terminal.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,9 +1034,7 @@ def mkrel(nodeid: str) -> str:
10341034
# fspath comes from testid which has a "/"-normalized path.
10351035
if fspath:
10361036
res = mkrel(nodeid)
1037-
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
1038-
"\\", nodes.SEP
1039-
):
1037+
if self.verbosity >= 2 and nodeid.split("::")[0] != nodes.norm_sep(fspath):
10401038
res += " <- " + bestrelpath(self.startpath, Path(fspath))
10411039
else:
10421040
res = "[location]"

testing/test_nodes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,37 @@ def test():
9898
items[0].warn(Exception("ok")) # type: ignore[arg-type]
9999

100100

101+
class TestNormSep:
102+
"""Tests for the norm_sep helper function."""
103+
104+
def test_forward_slashes_unchanged(self) -> None:
105+
"""Forward slashes pass through unchanged."""
106+
assert nodes.norm_sep("a/b/c") == "a/b/c"
107+
108+
def test_backslashes_converted(self) -> None:
109+
"""Backslashes are converted to forward slashes."""
110+
assert nodes.norm_sep("a\\b\\c") == "a/b/c"
111+
112+
def test_mixed_separators(self) -> None:
113+
"""Mixed separators are all normalized to forward slashes."""
114+
assert nodes.norm_sep("a\\b/c\\d") == "a/b/c/d"
115+
116+
def test_pathlike_input(self, tmp_path: Path) -> None:
117+
"""PathLike objects are converted to string with normalized separators."""
118+
# Create a path and verify it's normalized
119+
result = nodes.norm_sep(tmp_path / "subdir" / "file.py")
120+
assert "\\" not in result
121+
assert "subdir/file.py" in result
122+
123+
def test_empty_string(self) -> None:
124+
"""Empty string returns empty string."""
125+
assert nodes.norm_sep("") == ""
126+
127+
def test_windows_absolute_path(self) -> None:
128+
"""Windows absolute paths have backslashes converted."""
129+
assert nodes.norm_sep("C:\\Users\\test\\project") == "C:/Users/test/project"
130+
131+
101132
def test__check_initialpaths_for_relpath() -> None:
102133
"""Ensure that it handles dirs, and does not always use dirname."""
103134
cwd = Path.cwd()

0 commit comments

Comments
 (0)