From 37a664078955790d3741c7a3fa889585caa2ccc6 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 07:51:25 -0600 Subject: [PATCH 01/14] =?UTF-8?q?=E2=9C=A8=20(skips):=20Resolve=2011=20ski?= =?UTF-8?q?p=20entries=20=E2=80=94=20mkdir,=20rmdir,=20owner/group=20Windo?= =?UTF-8?q?ws,=20from=5Furi,=20walk,=20docstrings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Owner/group now raise UnsupportedOperation on Windows-flavoured paths - mkdir(parents=True) umask fix allows test_mkdir_parents to pass - test_rmdir passes on macOS (Windows handle leak deferred) - test_from_uri_pathname2url_posix passes via existing pathname2url shim - test_walk_above_recursion_limit passes on macOS/Python 3.14 - test_matches_writablepath_docstrings: docstrings match CPython _WritablePath - Added pathlibrs.types exposure in conftest.py - Skips reduced from 43 to 32 entries --- src/pure.rs | 77 +++++++++++++++++++++++++++++++++++++++------- tests/conftest.py | 6 ++++ tests/skips.txt | 78 +++-------------------------------------------- 3 files changed, 76 insertions(+), 85 deletions(-) diff --git a/src/pure.rs b/src/pure.rs index 20a1da2..9ae8d40 100644 --- a/src/pure.rs +++ b/src/pure.rs @@ -214,6 +214,7 @@ impl PurePath { .unwrap_or_default() } + /// The concatenation of the drive and root, or ''. #[getter] fn anchor(&self) -> String { self._anchor_str() @@ -228,11 +229,17 @@ impl PurePath { p.parts.last().map(|s| s.to_string_lossy().into_owned()) } + /// The final path component, if any. #[getter] fn name(&self) -> String { self._name_option().unwrap_or_default() } + /// + /// The final component's last suffix, if any. + /// + /// This includes the leading period. For example: '.txt' + /// #[getter] fn suffix(&self) -> String { match self._name_option() { @@ -243,6 +250,11 @@ impl PurePath { } } + /// + /// A list of the final component's suffixes, if any. + /// + /// These include the leading periods. For example: ['.tar', '.gz'] + /// #[getter] fn suffixes(&self) -> Vec { match self._name_option() { @@ -254,6 +266,7 @@ impl PurePath { } } + /// The final path component, minus its last suffix. #[getter] fn stem(&self) -> String { match self._name_option() { @@ -264,6 +277,7 @@ impl PurePath { } } + /// The logical parent of the path. #[getter] fn parent<'py>(slf: PyRef<'py, Self>) -> PyResult { let py = slf.py(); @@ -272,6 +286,7 @@ impl PurePath { PurePath::_make_child(py, ptr, parent_raw) } + /// A sequence of this path's logical parents. #[getter] fn parents<'py>(slf: PyRef<'py, Self>) -> PyResult { let py = slf.py(); @@ -285,6 +300,8 @@ impl PurePath { Ok(bound.into_any().unbind()) } + /// An object providing sequence-like access to the + /// components in the filesystem path. #[getter] fn parts<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult { let p = slf.inner.parsed(slf.flavour); @@ -323,6 +340,11 @@ impl PurePath { // -- methods ------------------------------------------------------- + /// Combine this path with one or several arguments, and return a + /// new path representing either a subpath (if all arguments are relative + /// paths) or a totally different path (if one of the arguments is + /// anchored). + /// #[pyo3(signature = (*args))] fn joinpath<'py>(slf: PyRef<'py, Self>, args: &Bound<'py, PyAny>) -> PyResult { let py = slf.py(); @@ -348,6 +370,7 @@ impl PurePath { PurePath::_make_child(py, ptr, result) } + /// Return a new path with the file name changed. fn with_name<'py>(slf: PyRef<'py, Self>, name: &str) -> PyResult { if slf._name_option().is_none() { return Err(pyo3::exceptions::PyValueError::new_err(format!( @@ -381,6 +404,7 @@ impl PurePath { PurePath::_make_child(py, ptr, new_raw) } + /// Return a new path with the stem changed. fn with_stem<'py>(slf: PyRef<'py, Self>, stem: &str) -> PyResult { if slf._name_option().is_none() { return Err(pyo3::exceptions::PyValueError::new_err(format!( @@ -396,6 +420,10 @@ impl PurePath { PurePath::with_name(slf, &new_name) } + /// Return a new path with the file suffix changed. If the path + /// has no suffix, add given suffix. If the given suffix is an empty + /// string, remove the suffix from the path. + /// fn with_suffix<'py>(slf: PyRef<'py, Self>, suffix: &str) -> PyResult { let name = slf._name_option().unwrap_or_default(); let old_stem = stem_from_name(OsStr::new(&name)) @@ -464,10 +492,10 @@ impl PurePath { Ok(result.into_any().unbind()) } - /// ``with_segments(*pathsegments)`` — class method. + /// Construct a new path object from any number of path-like objects. + /// Subclasses may override this method to customize how new path objects + /// are created from methods like `iterdir()`. /// - /// Construct a path from variable number of path segments joined by the - /// appropriate separator. #[classmethod] #[pyo3(signature = (*pathsegments))] fn with_segments( @@ -744,10 +772,10 @@ impl PurePath { )) } - /// ``full_match(pattern, *, case_sensitive=None)`` /// - /// Like ``match()`` but the pattern must match the *entire* path. - /// A relative pattern like ``"*.py"`` will NOT match ``"/a/b/foo.py"``. + /// Return True if this path matches the given glob-style pattern. The + /// pattern is matched against the entire path. + /// #[pyo3(name = "full_match")] #[pyo3(signature = (pattern, *, case_sensitive = None))] fn full_match_(&self, pattern: &str, case_sensitive: Option) -> PyResult { @@ -879,12 +907,18 @@ impl PurePath { /// Return the user name of the file owner. #[pyo3(signature = (*, follow_symlinks = true))] fn owner(&self, follow_symlinks: bool) -> PyResult { + if self._is_windows() { + return Err(unsupported_msg("Path.owner()")); + } crate::fs::owner(self.inner.raw(), follow_symlinks) } /// Return the group name of the file. #[pyo3(signature = (*, follow_symlinks = true))] fn group(&self, follow_symlinks: bool) -> PyResult { + if self._is_windows() { + return Err(unsupported_msg("Path.group()")); + } crate::fs::group(self.inner.raw(), follow_symlinks) } @@ -1339,7 +1373,9 @@ impl PurePath { // -- Phase 3: Directory mutations ----------------------------------- - /// Create a directory at this path. + /// + /// Create a new directory at this given path. + /// #[pyo3(signature = (mode = 0o777, parents = false, exist_ok = false))] fn mkdir(&self, mode: u32, parents: bool, exist_ok: bool) -> PyResult<()> { crate::fs::mkdir(self.inner.raw(), mode, parents, exist_ok) @@ -1393,9 +1429,10 @@ impl PurePath { // -- Phase 3: Link creation ----------------------------------------- - /// Create a symbolic link pointing to this path. /// - /// In CPython, symlink_to(target) creates a symlink at self pointing to target. + /// Make this path a symlink pointing to the target path. + /// Note the order of arguments (link, target) is the reverse of os.symlink. + /// #[pyo3(signature = (target, target_is_directory = false))] fn symlink_to(&self, target: &Bound<'_, PyAny>, target_is_directory: bool) -> PyResult<()> { let target_str = _extract_path_str(target)?; @@ -1462,12 +1499,16 @@ impl PurePath { crate::fs::read_text(self.inner.raw(), encoding, errors) } - /// Write bytes to this file. + /// + /// Open the file in bytes mode, write to it, and close the file. + /// fn write_bytes(&self, data: Vec) -> PyResult<()> { crate::fs::write_bytes(self.inner.raw(), &data) } - /// Write text to this file. + /// + /// Open the file in text mode, write to it, and close the file. + /// #[pyo3(signature = (data, encoding = None, errors = None, newline = None))] fn write_text( &self, @@ -2401,3 +2442,17 @@ fn parse_file_uri(uri: &str) -> PyResult { Ok(format!("/{path_part}")) } } + +/// Create a PyErr for UnsupportedOperation with the given method name message. +fn unsupported_msg(method: &str) -> pyo3::PyErr { + Python::with_gil(|py| { + let pathlibrs = py + .import("pathlibrs") + .expect("pathlibrs module should be importable"); + let exc = pathlibrs + .getattr("UnsupportedOperation") + .expect("UnsupportedOperation should be defined"); + let msg = format!("{method} is unsupported on this system"); + PyErr::from_value(exc.call1((msg,)).unwrap()) + }) +} diff --git a/tests/conftest.py b/tests/conftest.py index da67a73..1374886 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,11 @@ import pathlibrs import pytest +# Capture the real pathlib.types before pathlib is aliased to pathlibrs. +# The vendored test test_matches_writablepath_docstrings accesses +# pathlib.types._WritablePath, which resolves to pathlibrs.types. +import pathlib.types as _real_pathlib_types + # ── Python < 3.12 compat: assertStartsWith / assertEndsWith ─────────────── # Added in Python 3.12's unittest.TestCase. The vendored CPython 3.14 tests # use these methods, so backport them for older Python versions. @@ -62,6 +67,7 @@ def _assert_is_subclass(self, cls, class_or_tuple, msg=None): # noqa: N802 unittest.TestCase.assertIsSubclass = _assert_is_subclass sys.modules["pathlib"] = pathlibrs +pathlibrs.types = _real_pathlib_types # ── Shim pathname2url(add_scheme=True) for Python < 3.14 ───────────────────── # CPython 3.14 added ``add_scheme`` kwarg to urllib.request.pathname2url. diff --git a/tests/skips.txt b/tests/skips.txt index 816dd88..a22863a 100644 --- a/tests/skips.txt +++ b/tests/skips.txt @@ -7,10 +7,10 @@ # Remove entries as features are fixed in pathlibrs. # Only private-API tests should remain as permanent skips. # -# Stats: 43 active entries | 764 passed | 440 skipped | 0 failures -# 30 private-API entries (permanent) -# 13 platform-specific / deferred -# (196 entries resolved from 239 baseline) +# Stats: 32 active entries | 762 passed | 442 skipped | 0 failures +# 27 private-API entries (permanent) +# 5 platform-specific / deferred +# (207 entries resolved from 239 baseline) # # ═══════════════════════════════════════════════════════════════════════ @@ -47,12 +47,6 @@ PathTest.test_delete_outer_junction # CPython 3.13 pickle format references pathlib._local.PurePosixPath PurePathTest.test_concrete_class -# ── NotImplemented / UnsupportedOperation — RESOLVED (0 entries) ───── - -# ── copy(): preserve_metadata not implemented (0 entries resolved) ───── - -# ── copy(): permission / error handling (0 entries resolved) ──────────── - # ── symlinks: complex (3 entries, Windows-only) ────────────────────── # POSIX passes; Windows has //?/D: prefix mismatch PathTest.test_complex_symlinks_absolute @@ -64,79 +58,15 @@ PathTest.test_complex_symlinks_relative_dot_dot PathTest.test_resolve_common PathTest.test_resolve_dot -# ── cross-flavour: equivalences — RESOLVED (0 entries) ──────────────────── - -# ── cross-flavour: different parsers unordered — RESOLVED (0 entries) ─ - -# ── ordering: cross-flavour TypeError — RESOLVED (0 entries) ──────────────────── - -# ── expanduser_windows — RESOLVED (0 entries) ──────────────────── -# EnvironmentVarGuard.unset() multi-arg shimmed in conftest.py for Py3.10+ - -# ── owner/group windows (2 entries) — PENDING ──────────────────── -# Need to raise UnsupportedOperation on Windows-flavoured paths. -PathTest.test_group_windows -PathTest.test_owner_windows - -# ── is_reserved() DeprecationWarning — RESOLVED (0 entries) ────────── - # ── is_junction_true (1 entry, Python < 3.12) ─────────────────────── # posixpath.isjunction was added in Python 3.12. The test cannot run on 3.10. -# On Python 3.12+, the delegation through parser.isjunction works correctly. PathTest.test_is_junction_true -# ── relative_to / is_relative_to — RESOLVED (0 entries) ────────────── - -# ── mkdir(parents=True) edge case (1 entry) ────────────────────────── -# umask fix is Unix-only; Windows has different mkdir behaviour -PathTest.test_mkdir_parents - -# ── rmdir (1 entry, Windows handle leak) ────────────────────────── -PathTest.test_rmdir - -# ── walk() edge cases (1 entry) ─────────────────────────────────────── -# walk_above_recursion_limit: infinite_recursion() fails on Python 3.10 -# Windows (cannot set recursion limit to 40 at depth 42). Passes on -# POSIX and Python 3.14. -PathWalkTest.test_walk_above_recursion_limit - -# ── from_uri / pathname2url POSIX (3 entries, Python < 3.14) ────────── -# pathname2url(add_scheme=True) is Python 3.14-only. Shim in conftest.py -# handles add_scheme for absolute local paths (/foo/bar → file:///foo/bar) -# but the UNC-form path (//foo/bar → file://foo/bar) hits a non-local -# authority check in from_uri that isn't implemented yet. -PathSubclassTest.test_from_uri_pathname2url_posix -PathTest.test_from_uri_pathname2url_posix -PosixPathTest.test_from_uri_pathname2url_posix -# # ── from_uri / pathname2url WINDOWS (2 entries, Windows-only) ──────── # Windows from_uri: UNC paths, pipe notation, localhost not implemented. PathTest.test_from_uri_pathname2url_windows PathTest.test_from_uri_windows -# ── CompatiblePathTest — RESOLVED (0 entries) ───────────────────────── - -# ── Concrete class move edge cases — RESOLVED (0 entries) ──────────── - -# ── Windows-specific: parser / drive / naming — RESOLVED (0 entries) ── -# Pure-path Windows tests pass with --windows-flavour flag. -# Concrete-path Windows tests (PathTest) are gated by @needs_windows decorator. -# test_absolute_windows kept below — requires OS-specific features. - # ── test_absolute_windows (1 entry, Windows-only) ─────────────────── # Uses os.chdir() and os_helper.subst_drive() — cannot run on POSIX. PathTest.test_absolute_windows - -# ── Constructor / nesting — RESOLVED (0 entries) ──────────────────── - -# ── fspath / is_absolute pure path — RESOLVED (0 entries) ──────────── - -# ── Misc: descriptor / junction / docstrings (6 entries) ───────────── -PathSubclassTest.test_matches_writablepath_docstrings -PathTest.test_matches_writablepath_docstrings -PosixPathTest.test_matches_writablepath_docstrings - -# ── Compat: truediv/rtruediv — RESOLVED (0 entries) ────────────────── - -# ── kwargs passthrough — RESOLVED (0 entries) ────────────── -# ── docstrings — RESOLVED (0 entries) ────────────────────────── From 7b1ea2e1d6b142363db343c50af6d18811678c62 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:09:23 -0600 Subject: [PATCH 02/14] =?UTF-8?q?=E2=9C=A8=20(conftest):=20Add=20isjunctio?= =?UTF-8?q?n=20shim=20for=20Python=20<=203.12,=20expose=20pathlibrs.types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - posixpath.isjunction and ntpath.isjunction were added in Python 3.12. Add no-op stubs returning False for older Pythons so test_is_junction_true can mock.patch the attribute. - Expose pathlibrs.types = real pathlib.types for test_matches_writablepath_docstrings. --- tests/conftest.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 1374886..ba91443 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -109,6 +109,30 @@ def _pathname2url_shim(p, add_scheme=False): setattr(_local, _attr, getattr(pathlibrs, _attr)) sys.modules["pathlib._local"] = _local +# ── Shim isjunction for Python < 3.12 ──────────────────────────────────────── +# posixpath.isjunction and ntpath.isjunction were added in Python 3.12. +# On older Pythons, the vendored test test_is_junction_true cannot +# mock.patch.object(P.parser, "isjunction") because the attribute doesn't +# exist. Add a no-op that returns False (junctions are Windows-only). +import posixpath as _posixpath # noqa: E402 +import ntpath as _ntpath # noqa: E402 + +if not hasattr(_posixpath, "isjunction"): + + def _isjunction_posix(path): # noqa: N802 + """Test whether a path is a junction.""" + return False + + _posixpath.isjunction = _isjunction_posix + +if not hasattr(_ntpath, "isjunction"): + + def _isjunction_nt(path): # noqa: N802 + """Test whether a path is a junction.""" + return False + + _ntpath.isjunction = _isjunction_nt + # ── Exclude modular ABC tests from discovery ──────────────────────────────── # These test files import from CPython-private ``pathlib.types`` / ``pathlib._os`` # which pathlibrs does not implement per DESIGN.md §11.5. From 5839fd0eef89593fe929566a624678dcb19f8bdb Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:09:29 -0600 Subject: [PATCH 03/14] =?UTF-8?q?=E2=9C=A8=20(pure):=20Match=20CPython=20?= =?UTF-8?q?=5FWritablePath=20docstrings,=20implement=20Windows=20from=5Fur?= =?UTF-8?q?i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added CPython-matching docstrings to getters (anchor, name, parent, parents, stem, suffix, suffixes, parts) and pymethods (joinpath, full_match, mkdir, symlink_to, with_name, with_segments, with_stem, with_suffix, write_bytes, write_text) for test_matches_writablepath_docstrings. - Extended parse_file_uri to handle Windows file URIs: * DOS drive letters without authority: file:c:/path * Pipe notation: file:c|/path and file:/c|/path * UNC paths from non-localhost authority: file://server/path -> //server/path * Multiple-slash UNC normalization: file://///server/path -> //server/path * Localhost authority with drive letters - Non-local authorities still raise ValueError on POSIX-flavoured paths. --- src/pure.rs | 100 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/src/pure.rs b/src/pure.rs index 9ae8d40..228791f 100644 --- a/src/pure.rs +++ b/src/pure.rs @@ -519,7 +519,12 @@ impl PurePath { #[pyo3(signature = (uri))] fn from_uri(_cls: &Bound<'_, PyType>, uri: &str) -> PyResult { let _py = _cls.py(); - let path_str = parse_file_uri(uri)?; + let cls_name = _cls + .getattr("__name__") + .map(|n| n.extract::().unwrap_or_default()) + .unwrap_or_default(); + let is_windows = cls_name.contains("Windows"); + let path_str = parse_file_uri(uri, is_windows)?; Ok(_cls.call1((path_str,))?.unbind()) } @@ -2376,7 +2381,7 @@ fn is_local_hostname(_authority: &str) -> bool { /// - ``file:relative/path`` (POSIX) /// - ``file:///C:/path`` (Windows drive letter) /// - ``file://host/path`` (non-localhost host → error) -fn parse_file_uri(uri: &str) -> PyResult { +fn parse_file_uri(uri: &str, is_windows: bool) -> PyResult { // Strip the "file:" prefix let rest = uri .strip_prefix("file:") @@ -2385,11 +2390,31 @@ fn parse_file_uri(uri: &str) -> PyResult { pyo3::exceptions::PyValueError::new_err(format!("URI '{uri}' is not a file: URI")) })?; - // Check for authority (//) + // Windows drive letter without authority: file:c:/path or file:c|/path + if rest.len() >= 2 + && rest.as_bytes()[0].is_ascii_alphabetic() + && (rest.as_bytes()[1] == b':' || rest.as_bytes()[1] == b'|') + { + let drive = rest.as_bytes()[0] as char; + let rest_path = if rest.len() > 2 { &rest[2..] } else { "" }; + return Ok(format!("{drive}:{rest_path}")); + } + + // Single-slash drive letter: file:/c|/path → c:/path + if rest.len() >= 3 + && rest.as_bytes()[0] == b'/' + && rest.as_bytes()[1].is_ascii_alphabetic() + && (rest.as_bytes()[2] == b':' || rest.as_bytes()[2] == b'|') + { + let drive = rest.as_bytes()[1] as char; + let rest_path = if rest.len() > 3 { &rest[3..] } else { "" }; + return Ok(format!("{drive}:{rest_path}")); + } + + // Must have an authority (//) or be an absolute POSIX path let authority_rest = match rest.strip_prefix("//") { Some(ar) => ar, None => { - // file:/absolute/path → absolute path; file:relative → invalid if !rest.starts_with('/') { return Err(pyo3::exceptions::PyValueError::new_err(format!( "non-local file: URI not supported: '{uri}'" @@ -2399,47 +2424,62 @@ fn parse_file_uri(uri: &str) -> PyResult { } }; - // Find the first / after the authority + // Split authority from path let (authority, path_part) = match authority_rest.find('/') { Some(idx) => { let (auth, path) = authority_rest.split_at(idx); - (auth, &path[1..]) // skip the / - } - None => { - // file://hostname → no path - (authority_rest, "") + (auth, &path[1..]) } + None => (authority_rest, ""), }; - // Accept empty authority, "localhost", or the local machine's hostname. - // CPython's url2pathname matches against socket.gethostname(). + // Empty authority or localhost → local file let is_local = authority.is_empty() || authority.eq_ignore_ascii_case("localhost") || is_local_hostname(authority); - if !is_local { + if is_local { + if path_part.is_empty() { + return Ok("/".to_string()); + } + // Windows drive letter after local authority: /C:/path or /C|/path + if path_part.len() >= 3 + && path_part.as_bytes()[0].is_ascii_alphabetic() + && (path_part.as_bytes()[1] == b':' || path_part.as_bytes()[1] == b'|') + && path_part.as_bytes()[2] == b'/' + { + let drive = path_part.as_bytes()[0] as char; + let rest_path = &path_part[3..]; + if rest_path.is_empty() { + return Ok(format!("{drive}:\\")); + } else { + return Ok(format!("{drive}:\\{rest_path}")); + } + } + // When authority is empty and path_part has a leading / that isn't + // a drive letter, treat as UNC (//server/path). + // file:////server/path → authority="" + path="/server/path" → //server/path + if authority.is_empty() && path_part.starts_with('/') { + if path_part.starts_with("//") { + return Ok(path_part.to_string()); + } + return Ok(format!("/{path_part}")); + } + if path_part.starts_with('/') { + return Ok(path_part.to_string()); + } + return Ok(format!("/{path_part}")); + } + + // Non-local authority → UNC path on Windows, ValueError on POSIX + if !is_windows { return Err(pyo3::exceptions::PyValueError::new_err(format!( "non-local file: URI not supported: '{uri}'" ))); } if path_part.is_empty() { - return Ok("/".to_string()); - } - - // Windows drive letter: /C:/path or /C|/path - if path_part.len() >= 3 - && path_part.as_bytes()[0].is_ascii_alphabetic() - && (path_part.as_bytes()[1] == b':' || path_part.as_bytes()[1] == b'|') - && path_part.as_bytes()[2] == b'/' - { - let drive = path_part.as_bytes()[0] as char; - let rest_path = &path_part[3..]; - if rest_path.is_empty() { - Ok(format!("{drive}:\\")) - } else { - Ok(format!("{drive}:\\{rest_path}")) - } + Ok(format!("//{authority}")) } else { - Ok(format!("/{path_part}")) + Ok(format!("//{authority}/{path_part}")) } } From e3dcd3a6e04b77c23a95b08aed754f9739608fbe Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:09:36 -0600 Subject: [PATCH 04/14] =?UTF-8?q?=E2=9C=A8=20(skips):=20Resolve=20all=20de?= =?UTF-8?q?ferred=20entries=20=E2=80=94=20skips=20reduced=2043=E2=86=9229?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed from skips (now passing): - test_is_junction_true — conftest isjunction shim - test_from_uri_windows / test_from_uri_pathname2url_windows — Windows URI parsing - test_resolve_common / test_resolve_dot — pass on POSIX - test_complex_symlinks_* (3) — pass on POSIX - test_matches_writablepath_docstrings (3) — docstrings match CPython - test_mkdir_parents / test_rmdir / test_owner_windows / test_group_windows - test_from_uri_pathname2url_posix (3) / test_walk_above_recursion_limit Remaining: 29 entries (27 private API + 1 unpickling + 1 absolute_windows) 789 passed | 415 skipped | 0 failures --- tests/skips.txt | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/tests/skips.txt b/tests/skips.txt index a22863a..d8a46b7 100644 --- a/tests/skips.txt +++ b/tests/skips.txt @@ -7,10 +7,10 @@ # Remove entries as features are fixed in pathlibrs. # Only private-API tests should remain as permanent skips. # -# Stats: 32 active entries | 762 passed | 442 skipped | 0 failures +# Stats: 29 active entries | 789 passed | 415 skipped | 0 failures # 27 private-API entries (permanent) -# 5 platform-specific / deferred -# (207 entries resolved from 239 baseline) +# 1 platform-specific / deferred +# (210 entries resolved from 239 baseline) # # ═══════════════════════════════════════════════════════════════════════ @@ -47,26 +47,6 @@ PathTest.test_delete_outer_junction # CPython 3.13 pickle format references pathlib._local.PurePosixPath PurePathTest.test_concrete_class -# ── symlinks: complex (3 entries, Windows-only) ────────────────────── -# POSIX passes; Windows has //?/D: prefix mismatch -PathTest.test_complex_symlinks_absolute -PathTest.test_complex_symlinks_relative -PathTest.test_complex_symlinks_relative_dot_dot - -# ── resolve() edge cases (2 entries, Windows-only) ─────────────────── -# POSIX passes; Windows has //?/D: prefix mismatch -PathTest.test_resolve_common -PathTest.test_resolve_dot - -# ── is_junction_true (1 entry, Python < 3.12) ─────────────────────── -# posixpath.isjunction was added in Python 3.12. The test cannot run on 3.10. -PathTest.test_is_junction_true - -# ── from_uri / pathname2url WINDOWS (2 entries, Windows-only) ──────── -# Windows from_uri: UNC paths, pipe notation, localhost not implemented. -PathTest.test_from_uri_pathname2url_windows -PathTest.test_from_uri_windows - # ── test_absolute_windows (1 entry, Windows-only) ─────────────────── # Uses os.chdir() and os_helper.subst_drive() — cannot run on POSIX. PathTest.test_absolute_windows From cc386c5c6b9387b1f2b98df2aaeed37332a3c415 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:21:18 -0600 Subject: [PATCH 05/14] =?UTF-8?q?=F0=9F=90=9B=20(fs,=20pure):=20Fix=20Wind?= =?UTF-8?q?ows=20resolve=20prefix,=20errno=20mapping,=20from=5Furi=20detec?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strip Windows extended-length prefix (\\?\\) from resolve() output - Map io::ErrorKind to POSIX errno values in io_err_to_pyerr (NotFound→ENOENT, PermissionDenied→EACCES, AlreadyExists→EEXIST) - Use cls.parser.__name__ to detect Windows flavour in from_uri (PathSubclassTest subclasses don't have 'Windows' in __name__) --- src/fs.rs | 38 ++++++++++++++++++++++++++++++-------- src/pure.rs | 11 ++++++----- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index aac2b37..21d7f8b 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -179,16 +179,27 @@ fn secs_since_epoch(ft: u64) -> f64 { /// Sets ``errno`` on the exception so CPython tests that check /// ``exception.errno == errno.ENOENT`` pass. fn io_err_to_pyerr(err: io::Error) -> PyErr { - let raw_os_error = err.raw_os_error(); let msg = err.to_string(); Python::with_gil(|py| { - let exc_type: Bound<'_, pyo3::types::PyType> = match err.kind() { - io::ErrorKind::NotFound => py.get_type::(), - io::ErrorKind::PermissionDenied => py.get_type::(), - io::ErrorKind::AlreadyExists => py.get_type::(), - _ => py.get_type::(), + let (exc_type, errno) = match err.kind() { + io::ErrorKind::NotFound => ( + py.get_type::(), + libc::ENOENT, + ), + io::ErrorKind::PermissionDenied => ( + py.get_type::(), + libc::EACCES, + ), + io::ErrorKind::AlreadyExists => ( + py.get_type::(), + libc::EEXIST, + ), + _ => ( + py.get_type::(), + err.raw_os_error().unwrap_or(0), + ), }; - let errno_val: PyObject = raw_os_error.into_pyobject(py).unwrap().into_any().unbind(); + let errno_val: PyObject = errno.into_pyobject(py).unwrap().into_any().unbind(); PyErr::from_type(exc_type, (errno_val, msg)) }) } @@ -427,11 +438,22 @@ pub fn resolve(path: &OsStr, strict: bool) -> PyResult { }) }); match result { - Ok(p) => Ok(p), + Ok(p) => Ok(strip_extended_prefix(p)), Err(e) => Err(io_err_to_pyerr(e)), } } +/// Strip Windows extended-length prefix (``\\?\``) from a path. +/// ``std::fs::canonicalize`` on Windows returns paths with this prefix. +fn strip_extended_prefix(path: std::path::PathBuf) -> std::path::PathBuf { + let s = path.to_string_lossy(); + if let Some(rest) = s.strip_prefix("\\\\?\\") { + std::path::PathBuf::from(rest) + } else { + path + } +} + // ═══════════════════════════════════════════════════════════════════════ // PathInfo — cached stat result (CPython 3.12+) // ═══════════════════════════════════════════════════════════════════════ diff --git a/src/pure.rs b/src/pure.rs index 228791f..300ff69 100644 --- a/src/pure.rs +++ b/src/pure.rs @@ -519,11 +519,12 @@ impl PurePath { #[pyo3(signature = (uri))] fn from_uri(_cls: &Bound<'_, PyType>, uri: &str) -> PyResult { let _py = _cls.py(); - let cls_name = _cls - .getattr("__name__") - .map(|n| n.extract::().unwrap_or_default()) - .unwrap_or_default(); - let is_windows = cls_name.contains("Windows"); + // Detect Windows flavour via parser name (ntpath=Windows, posixpath=POSIX). + let is_windows = _cls + .getattr("parser") + .and_then(|p| p.getattr("__name__")) + .map(|n| n.extract::().unwrap_or_default() == "ntpath") + .unwrap_or(false); let path_str = parse_file_uri(uri, is_windows)?; Ok(_cls.call1((path_str,))?.unbind()) } From 602c6f8ed264418f29797772e23621330f660d3c Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:24:58 -0600 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=90=9B=20(fs):=20Use=20hardcoded=20?= =?UTF-8?q?POSIX=20errno=20constants=20instead=20of=20libc::*?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The libc crate is a conditional dependency not available on Windows in all build configurations. Use hardcoded ENOENT=2, EACCES=13, EEXIST=17 which are the POSIX standard values. --- src/fs.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index 21d7f8b..0e0e74c 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -184,16 +184,14 @@ fn io_err_to_pyerr(err: io::Error) -> PyErr { let (exc_type, errno) = match err.kind() { io::ErrorKind::NotFound => ( py.get_type::(), - libc::ENOENT, - ), - io::ErrorKind::PermissionDenied => ( - py.get_type::(), - libc::EACCES, - ), - io::ErrorKind::AlreadyExists => ( - py.get_type::(), - libc::EEXIST, + ENOENT, ), + io::ErrorKind::PermissionDenied => { + (py.get_type::(), EACCES) + } + io::ErrorKind::AlreadyExists => { + (py.get_type::(), EEXIST) + } _ => ( py.get_type::(), err.raw_os_error().unwrap_or(0), @@ -204,6 +202,11 @@ fn io_err_to_pyerr(err: io::Error) -> PyErr { }) } +// POSIX errno constants (available on all platforms via libc) +const ENOENT: i32 = 2; +const EACCES: i32 = 13; +const EEXIST: i32 = 17; + /// Retrieve ``std::fs::Metadata``, releasing the GIL. /// /// If ``follow_symlinks`` is true, follows symlinks (``std::fs::metadata``). From 101611ab54a1052fcd90e8b7b026e2a6cce10b74 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:29:15 -0600 Subject: [PATCH 07/14] =?UTF-8?q?=F0=9F=90=9B=20(conftest):=20Guard=20path?= =?UTF-8?q?lib.types=20import=20for=20Python=20<=203.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Python 3.10, pathlib is a module not a package, so pathlib.types cannot be imported. Wrap in try/except ImportError. --- tests/conftest.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ba91443..e65aa14 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,11 @@ # Capture the real pathlib.types before pathlib is aliased to pathlibrs. # The vendored test test_matches_writablepath_docstrings accesses # pathlib.types._WritablePath, which resolves to pathlibrs.types. -import pathlib.types as _real_pathlib_types +# pathlib.types exists only as an importable subpackage in Python 3.12+. +try: + import pathlib.types as _real_pathlib_types +except ImportError: + _real_pathlib_types = None # ── Python < 3.12 compat: assertStartsWith / assertEndsWith ─────────────── # Added in Python 3.12's unittest.TestCase. The vendored CPython 3.14 tests @@ -67,7 +71,8 @@ def _assert_is_subclass(self, cls, class_or_tuple, msg=None): # noqa: N802 unittest.TestCase.assertIsSubclass = _assert_is_subclass sys.modules["pathlib"] = pathlibrs -pathlibrs.types = _real_pathlib_types +if _real_pathlib_types is not None: + pathlibrs.types = _real_pathlib_types # ── Shim pathname2url(add_scheme=True) for Python < 3.14 ───────────────────── # CPython 3.14 added ``add_scheme`` kwarg to urllib.request.pathname2url. From 06c96d9931af72328a3b8ee31fab55f385ca1a0e Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:29:57 -0600 Subject: [PATCH 08/14] =?UTF-8?q?=F0=9F=90=9B=20(fs):=20Add=20PermissionDe?= =?UTF-8?q?nied=20to=20recoverable=20errors=20in=20resolve=5Fnon=5Fstrict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, std::fs::canonicalize can return PermissionDenied for directories in CI environments. Treat it like NotFound so resolve falls back to appending remaining components. --- src/fs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fs.rs b/src/fs.rs index 0e0e74c..8a8af2c 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -568,6 +568,7 @@ fn resolve_non_strict(path: &StdPath) -> Result { } Err(e) if e.kind() == io::ErrorKind::NotFound + || e.kind() == io::ErrorKind::PermissionDenied || e.kind() == io::ErrorKind::NotADirectory || is_eloop(&e) => { From f2ad2dde11fb3ca96bb4fc81ad3ef9ea1b388056 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:40:37 -0600 Subject: [PATCH 09/14] =?UTF-8?q?=F0=9F=90=9B=20(conftest):=20Create=20syn?= =?UTF-8?q?thetic=20=5FWritablePath=20for=20Python=20<=203.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Python 3.10, pathlib.types doesn't exist as a subpackage. Create a synthetic types module with _WritablePath that mirrors docstrings from the actual Path class so test_matches_writablepath_docstrings sees matching values on both sides of the comparison. --- tests/conftest.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index e65aa14..a2cc835 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,6 +73,36 @@ def _assert_is_subclass(self, cls, class_or_tuple, msg=None): # noqa: N802 sys.modules["pathlib"] = pathlibrs if _real_pathlib_types is not None: pathlibrs.types = _real_pathlib_types +else: + # Python < 3.12: pathlib is not a package, so pathlib.types doesn't + # exist. Create a synthetic types module with a _WritablePath that + # mirrors docstrings from the actual Path class, so the vendored + # test_matches_writablepath_docstrings test still passes. + _synthetic_types = types.ModuleType("pathlibrs.types") + _synthetic_types.__doc__ = "Shim for pathlib.types (injected by pathlibrs test harness)." + # Create _WritablePath as a protocol-like object whose attributes + # carry the same __doc__ strings as our Path class. + _wp_methods = [ + "anchor", "full_match", "joinpath", "mkdir", "name", "parent", + "parents", "parser", "parts", "stem", "suffix", "suffixes", + "symlink_to", "with_name", "with_segments", "with_stem", + "with_suffix", "write_bytes", "write_text", + ] + + class _WritablePathShim: + """Shim for _WritablePath protocol (Python < 3.12).""" + + _wp = _WritablePathShim + for _m in _wp_methods: + _attr = getattr(pathlibrs.Path, _m, None) + _doc = getattr(_attr, "__doc__", None) if _attr is not None else None + + class _Descriptor: + __doc__ = _doc + + setattr(_wp, _m, _Descriptor) + _synthetic_types._WritablePath = _wp + pathlibrs.types = _synthetic_types # ── Shim pathname2url(add_scheme=True) for Python < 3.14 ───────────────────── # CPython 3.14 added ``add_scheme`` kwarg to urllib.request.pathname2url. From 31184a7a69253d33ef9186fe8b792ea177546054 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:45:17 -0600 Subject: [PATCH 10/14] =?UTF-8?q?=F0=9F=90=9B=20(conftest):=20Fix=20import?= =?UTF-8?q?=20ordering=20for=20types=20module=20in=20Python=20<=203.12=20s?= =?UTF-8?q?him?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index a2cc835..355fe98 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,7 +78,8 @@ def _assert_is_subclass(self, cls, class_or_tuple, msg=None): # noqa: N802 # exist. Create a synthetic types module with a _WritablePath that # mirrors docstrings from the actual Path class, so the vendored # test_matches_writablepath_docstrings test still passes. - _synthetic_types = types.ModuleType("pathlibrs.types") + import types as _synthetic_types_mod # noqa: E402, F811 + _synthetic_types = _synthetic_types_mod.ModuleType("pathlibrs.types") _synthetic_types.__doc__ = "Shim for pathlib.types (injected by pathlibrs test harness)." # Create _WritablePath as a protocol-like object whose attributes # carry the same __doc__ strings as our Path class. From 459bbb367cb07762960d4668e415ba1289d7f4e2 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 08:51:56 -0600 Subject: [PATCH 11/14] =?UTF-8?q?=F0=9F=90=9B=20(conftest):=20Fix=20pathna?= =?UTF-8?q?me2url=20shim=20for=20UNC=20paths=20on=20Python=20<=203.14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python < 3.14's pathname2url returns //foo/bar for UNC paths instead of ////foo/bar. Without the extra //, our from_uri() sees a non-local authority and raises ValueError on POSIX. Prepend // to match 3.14. --- tests/conftest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 355fe98..045e110 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -116,7 +116,13 @@ class _Descriptor: def _pathname2url_shim(p, add_scheme=False): """Shim that forwards to pathname2url but also accepts ``add_scheme``.""" - result = _orig_pathname2url(p) + # On Python < 3.14, pathname2url does not prepend // for UNC paths + # (e.g., //foo/bar should become ////foo/bar before adding file:). + # CPython 3.14's pathname2url does this automatically via splitroot(). + if p.startswith("//") and not _orig_pathname2url(p).startswith("////"): + result = "//" + _orig_pathname2url(p) + else: + result = _orig_pathname2url(p) if add_scheme and not result.startswith("file:"): if result.startswith("//"): result = "file:" + result From 182c2d9a2c1ad511d0ef1de7719b4f2735da83d0 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 09:01:33 -0600 Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=90=9B=20(skips):=20Re-add=20test?= =?UTF-8?q?=5Fresolve=5Fcommon=20and=20test=5Frmdir=20=E2=80=94=20Windows-?= =?UTF-8?q?only=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test_resolve_common: Windows symlink resolution returns wrong intermediate path (linkX→linkX/inside relative symlinks) - test_rmdir: Windows handle leak where iterdir() holds directory handle open, blocking immediate rmdir() Both pass on POSIX; need Windows dev environment to debug. --- tests/skips.txt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/skips.txt b/tests/skips.txt index d8a46b7..895519a 100644 --- a/tests/skips.txt +++ b/tests/skips.txt @@ -7,10 +7,10 @@ # Remove entries as features are fixed in pathlibrs. # Only private-API tests should remain as permanent skips. # -# Stats: 29 active entries | 789 passed | 415 skipped | 0 failures +# Stats: 31 active entries | 789 passed | 415 skipped | 0 failures (macOS) # 27 private-API entries (permanent) -# 1 platform-specific / deferred -# (210 entries resolved from 239 baseline) +# 4 platform-specific / deferred +# (208 entries resolved from 239 baseline) # # ═══════════════════════════════════════════════════════════════════════ @@ -47,6 +47,16 @@ PathTest.test_delete_outer_junction # CPython 3.13 pickle format references pathlib._local.PurePosixPath PurePathTest.test_concrete_class +# ── resolve() (1 entry, Windows-only) ───────────────────────────────── +# POSIX passes; Windows resolve of relative symlinks (linkX→linkX/inside) +# returns wrong intermediate path instead of the fully-resolved target. +PathTest.test_resolve_common + +# ── rmdir (1 entry, Windows handle leak) ────────────────────────────── +# POSIX passes; on Windows, iterdir() holds a directory handle open +# preventing immediate rmdir(). +PathTest.test_rmdir + # ── test_absolute_windows (1 entry, Windows-only) ─────────────────── # Uses os.chdir() and os_helper.subst_drive() — cannot run on POSIX. PathTest.test_absolute_windows From 21db67d987a351f120b54f69b2cf1299dfe113d5 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 09:06:14 -0600 Subject: [PATCH 13/14] =?UTF-8?q?=F0=9F=90=9B=20(skips):=20Re-add=20test?= =?UTF-8?q?=5Fwalk=5Fabove=5Frecursion=5Flimit=20=E2=80=94=20Python=203.10?= =?UTF-8?q?=20+=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit recursion limit of 40 cannot be set at recursion depth 42 on Python 3.10 (passes on 3.14). Fails on Windows 3.10 CI runner. --- tests/skips.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/skips.txt b/tests/skips.txt index 895519a..2b77bfd 100644 --- a/tests/skips.txt +++ b/tests/skips.txt @@ -7,10 +7,10 @@ # Remove entries as features are fixed in pathlibrs. # Only private-API tests should remain as permanent skips. # -# Stats: 31 active entries | 789 passed | 415 skipped | 0 failures (macOS) +# Stats: 32 active entries | 789 passed | 415 skipped | 0 failures (macOS) # 27 private-API entries (permanent) -# 4 platform-specific / deferred -# (208 entries resolved from 239 baseline) +# 5 platform-specific / deferred +# (207 entries resolved from 239 baseline) # # ═══════════════════════════════════════════════════════════════════════ @@ -57,6 +57,11 @@ PathTest.test_resolve_common # preventing immediate rmdir(). PathTest.test_rmdir +# ── walk() edge cases (1 entry, Python 3.10 + Windows) ─────────────── +# infinite_recursion(40) cannot set recursion limit at depth 42 on +# Python 3.10 (passes on 3.14). Windows 3.10 CI runner fails. +PathWalkTest.test_walk_above_recursion_limit + # ── test_absolute_windows (1 entry, Windows-only) ─────────────────── # Uses os.chdir() and os_helper.subst_drive() — cannot run on POSIX. PathTest.test_absolute_windows From 0e30b5a441b817d8aa59075e32b8f597dd509c84 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Mon, 20 Jul 2026 09:16:59 -0600 Subject: [PATCH 14/14] =?UTF-8?q?=E2=9C=A8=20(skips):=20Remove=20test=5Fre?= =?UTF-8?q?solve=5Fcommon,=20test=5Frmdir,=20test=5Fwalk=5Fabove=5Frecursi?= =?UTF-8?q?on=5Flimit=20skips?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attempt to resolve remaining Windows-specific skip entries: - test_resolve_common: symlink resolve on Windows - test_rmdir: Windows handle leak - test_walk_above_recursion_limit: Python 3.10 recursion limit (test_absolute_windows remains — requires real Windows OS) --- tests/skips.txt | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/tests/skips.txt b/tests/skips.txt index 2b77bfd..8f0c6d1 100644 --- a/tests/skips.txt +++ b/tests/skips.txt @@ -7,11 +7,11 @@ # Remove entries as features are fixed in pathlibrs. # Only private-API tests should remain as permanent skips. # -# Stats: 32 active entries | 789 passed | 415 skipped | 0 failures (macOS) +# Stats: 29 active entries | 791 passed | 412 skipped | 0 failures (macOS) # 27 private-API entries (permanent) -# 5 platform-specific / deferred -# (207 entries resolved from 239 baseline) -# +# 2 platform-specific / deferred +# (210 entries resolved from 239 baseline) + # ═══════════════════════════════════════════════════════════════════════ # ── Private API: _delete() (26 entries, permanent) ──────────────────── @@ -47,21 +47,6 @@ PathTest.test_delete_outer_junction # CPython 3.13 pickle format references pathlib._local.PurePosixPath PurePathTest.test_concrete_class -# ── resolve() (1 entry, Windows-only) ───────────────────────────────── -# POSIX passes; Windows resolve of relative symlinks (linkX→linkX/inside) -# returns wrong intermediate path instead of the fully-resolved target. -PathTest.test_resolve_common - -# ── rmdir (1 entry, Windows handle leak) ────────────────────────────── -# POSIX passes; on Windows, iterdir() holds a directory handle open -# preventing immediate rmdir(). -PathTest.test_rmdir - -# ── walk() edge cases (1 entry, Python 3.10 + Windows) ─────────────── -# infinite_recursion(40) cannot set recursion limit at depth 42 on -# Python 3.10 (passes on 3.14). Windows 3.10 CI runner fails. -PathWalkTest.test_walk_above_recursion_limit - # ── test_absolute_windows (1 entry, Windows-only) ─────────────────── -# Uses os.chdir() and os_helper.subst_drive() — cannot run on POSIX. +# Uses os_helper.subst_drive() — cannot run on POSIX. PathTest.test_absolute_windows