diff --git a/src/fs.rs b/src/fs.rs index aac2b37..8a8af2c 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -179,20 +179,34 @@ 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::(), + 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), + ), }; - 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)) }) } +// 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``). @@ -427,11 +441,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+) // ═══════════════════════════════════════════════════════════════════════ @@ -543,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) => { diff --git a/src/pure.rs b/src/pure.rs index 20a1da2..300ff69 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( @@ -491,7 +519,13 @@ 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)?; + // 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()) } @@ -744,10 +778,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 +913,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 +1379,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 +1435,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 +1505,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, @@ -2335,7 +2382,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:") @@ -2344,11 +2391,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}'" @@ -2358,46 +2425,75 @@ 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}")) } } + +/// 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..045e110 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,15 @@ 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. +# 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 # use these methods, so backport them for older Python versions. @@ -62,6 +71,39 @@ def _assert_is_subclass(self, cls, class_or_tuple, msg=None): # noqa: N802 unittest.TestCase.assertIsSubclass = _assert_is_subclass 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. + 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. + _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. @@ -74,7 +116,13 @@ def _assert_is_subclass(self, cls, class_or_tuple, msg=None): # noqa: N802 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 @@ -103,6 +151,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. diff --git a/tests/skips.txt b/tests/skips.txt index 816dd88..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: 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: 29 active entries | 791 passed | 412 skipped | 0 failures (macOS) +# 27 private-API entries (permanent) +# 2 platform-specific / deferred +# (210 entries resolved from 239 baseline) + # ═══════════════════════════════════════════════════════════════════════ # ── Private API: _delete() (26 entries, permanent) ──────────────────── @@ -47,96 +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 -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 - -# ── 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. +# Uses 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) ──────────────────────────