diff options
author | Barney Gale <barney.gale@gmail.com> | 2023-02-17 14:08:14 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-17 14:08:14 (GMT) |
commit | 072011b3c38f871cdc3ab62630ea2234d09456d1 (patch) | |
tree | 5305bd780eb338a27ce67428077773bf0565a5f5 /Lib | |
parent | d401b20630965c0e1d2a5a0d60d5fc51aa5a8d80 (diff) | |
download | cpython-072011b3c38f871cdc3ab62630ea2234d09456d1.zip cpython-072011b3c38f871cdc3ab62630ea2234d09456d1.tar.gz cpython-072011b3c38f871cdc3ab62630ea2234d09456d1.tar.bz2 |
gh-100809: Fix handling of drive-relative paths in pathlib.Path.absolute() (GH-100812)
Resolving the drive independently uses the OS API, which ensures it starts from the current directory on that drive.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/pathlib.py | 7 | ||||
-rw-r--r-- | Lib/test/support/os_helper.py | 35 | ||||
-rw-r--r-- | Lib/test/test_pathlib.py | 20 |
3 files changed, 61 insertions, 1 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py index d7994a3..dde5735 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -816,7 +816,12 @@ class Path(PurePath): """ if self.is_absolute(): return self - return self._from_parts([os.getcwd()] + self._parts) + elif self._drv: + # There is a CWD on each drive-letter drive. + cwd = self._flavour.abspath(self._drv) + else: + cwd = os.getcwd() + return self._from_parts([cwd] + self._parts) def resolve(self, strict=False): """ diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 2d4356a..821a4b1 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -4,6 +4,7 @@ import errno import os import re import stat +import string import sys import time import unittest @@ -716,3 +717,37 @@ class EnvironmentVarGuard(collections.abc.MutableMapping): else: self._environ[k] = v os.environ = self._environ + + +try: + import ctypes + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + + ERROR_FILE_NOT_FOUND = 2 + DDD_REMOVE_DEFINITION = 2 + DDD_EXACT_MATCH_ON_REMOVE = 4 + DDD_NO_BROADCAST_SYSTEM = 8 +except (ImportError, AttributeError): + def subst_drive(path): + raise unittest.SkipTest('ctypes or kernel32 is not available') +else: + @contextlib.contextmanager + def subst_drive(path): + """Temporarily yield a substitute drive for a given path.""" + for c in reversed(string.ascii_uppercase): + drive = f'{c}:' + if (not kernel32.QueryDosDeviceW(drive, None, 0) and + ctypes.get_last_error() == ERROR_FILE_NOT_FOUND): + break + else: + raise unittest.SkipTest('no available logical drive') + if not kernel32.DefineDosDeviceW( + DDD_NO_BROADCAST_SYSTEM, drive, path): + raise ctypes.WinError(ctypes.get_last_error()) + try: + yield drive + finally: + if not kernel32.DefineDosDeviceW( + DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, + drive, path): + raise ctypes.WinError(ctypes.get_last_error()) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index b868379..4de91d5 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2973,6 +2973,26 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase): self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(share, 'a', 'b', 'c')) + drive = os.path.splitdrive(BASE)[0] + with os_helper.change_cwd(BASE): + # Relative path with root + self.assertEqual(str(P('\\').absolute()), drive + '\\') + self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo') + + # Relative path on current drive + self.assertEqual(str(P(drive).absolute()), BASE) + self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(BASE, 'foo')) + + with os_helper.subst_drive(BASE) as other_drive: + # Set the working directory on the substitute drive + saved_cwd = os.getcwd() + other_cwd = f'{other_drive}\\dirA' + os.chdir(other_cwd) + os.chdir(saved_cwd) + + # Relative path on another drive + self.assertEqual(str(P(other_drive).absolute()), other_cwd) + self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo') def test_glob(self): P = self.cls |