Skip to content

Commit b76a1c5

Browse files
Implement ALL_BUT_LAST on Windows.
1 parent 38c6198 commit b76a1c5

3 files changed

Lines changed: 140 additions & 3 deletions

File tree

Lib/ntpath.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,9 @@ def abspath(path):
597597
path = join(getcwd(), path)
598598
return normpath(path)
599599

600+
# A singleton with true boolean value
601+
ALL_BUT_LAST = ['ALL_BUT_LAST']
602+
600603
try:
601604
from nt import _findfirstfile, _getfinalpathname, readlink as _nt_readlink
602605
except ImportError:
@@ -735,7 +738,13 @@ def realpath(path, *, strict=False):
735738
path = normpath(path)
736739
except OSError as ex:
737740
if strict:
738-
raise
741+
if strict is not ALL_BUT_LAST or not isinstance(ex, FileNotFoundError):
742+
raise
743+
dirname, basename = split(path)
744+
if not basename:
745+
dirname, basename = split(path)
746+
if not isdir(dirname):
747+
raise
739748
initial_winerror = ex.winerror
740749
path = _getfinalpathname_nonstrict(path)
741750
# The path returned by _getfinalpathname will always start with \\?\ -

Lib/test/test_ntpath.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import errno
12
import inspect
23
import ntpath
34
import os
@@ -705,6 +706,132 @@ def test_realpath_permission(self):
705706

706707
self.assertPathEqual(test_file, ntpath.realpath(test_file_short))
707708

709+
@os_helper.skip_unless_symlink
710+
def test_realpath_mode(self):
711+
realpath = ntpath.realpath
712+
ALL_BUT_LAST = ntpath.ALL_BUT_LAST
713+
ABSTFN = ntpath.abspath(os_helper.TESTFN)
714+
try:
715+
os.mkdir(ABSTFN)
716+
os.mkdir(ABSTFN + "\\dir")
717+
open(ABSTFN + "\\file", "wb").close()
718+
open(ABSTFN + "\\dir\\file2", "wb").close()
719+
os.symlink("file", ABSTFN + "\\link")
720+
os.symlink("dir", ABSTFN + "\\link2")
721+
os.symlink("nonexistent", ABSTFN + "\\broken")
722+
os.symlink("cycle", ABSTFN + "\\cycle")
723+
def check(path, mode, expected, errno=None):
724+
path = path.replace('/', '\\')
725+
if isinstance(expected, str):
726+
assert errno is None
727+
self.assertEqual(realpath(path, strict=mode), ABSTFN + expected.replace('/', '\\'))
728+
else:
729+
with self.assertRaises(expected) as cm:
730+
realpath(path, strict=mode)
731+
if errno is not None:
732+
self.assertEqual(cm.exception.errno, errno)
733+
734+
with os_helper.change_cwd(ABSTFN):
735+
check("file", False, "/file")
736+
check("file", ALL_BUT_LAST, "/file")
737+
check("file", True, "/file")
738+
check("file/", False, "/file")
739+
# check("file/", ALL_BUT_LAST, NotADirectoryError)
740+
# check("file/", True, NotADirectoryError)
741+
check("file/file2", False, "/file/file2")
742+
# check("file/file2", ALL_BUT_LAST, NotADirectoryError)
743+
# check("file/file2", True, NotADirectoryError)
744+
check("file/.", False, "/file")
745+
# check("file/.", ALL_BUT_LAST, NotADirectoryError)
746+
# check("file/.", True, NotADirectoryError)
747+
check("file/../link2", False, "/dir")
748+
check("file/../link2", ALL_BUT_LAST, "/dir")
749+
check("file/../link2", True, "/dir")
750+
751+
check("dir", False, "/dir")
752+
check("dir", ALL_BUT_LAST, "/dir")
753+
check("dir", True, "/dir")
754+
check("dir/", False, "/dir")
755+
check("dir/", ALL_BUT_LAST, "/dir")
756+
check("dir/", True, "/dir")
757+
check("dir/file2", False, "/dir/file2")
758+
check("dir/file2", ALL_BUT_LAST, "/dir/file2")
759+
check("dir/file2", True, "/dir/file2")
760+
761+
check("link", False, "/file")
762+
check("link", ALL_BUT_LAST, "/file")
763+
check("link", True, "/file")
764+
check("link/", False, "/file")
765+
# check("link/", ALL_BUT_LAST, NotADirectoryError)
766+
# check("link/", True, NotADirectoryError)
767+
check("link/file2", False, "/file/file2")
768+
# check("link/file2", ALL_BUT_LAST, NotADirectoryError)
769+
# check("link/file2", True, NotADirectoryError)
770+
check("link/.", False, "/file")
771+
# check("link/.", ALL_BUT_LAST, NotADirectoryError)
772+
# check("link/.", True, NotADirectoryError)
773+
check("link/../link", False, "/file")
774+
check("link/../link", ALL_BUT_LAST, "/file")
775+
check("link/../link", True, "/file")
776+
777+
check("link2", False, "/dir")
778+
check("link2", ALL_BUT_LAST, "/dir")
779+
check("link2", True, "/dir")
780+
check("link2/", False, "/dir")
781+
check("link2/", ALL_BUT_LAST, "/dir")
782+
check("link2/", True, "/dir")
783+
check("link2/file2", False, "/dir/file2")
784+
check("link2/file2", ALL_BUT_LAST, "/dir/file2")
785+
check("link2/file2", True, "/dir/file2")
786+
787+
check("nonexistent", False, "/nonexistent")
788+
check("nonexistent", ALL_BUT_LAST, "/nonexistent")
789+
check("nonexistent", True, FileNotFoundError)
790+
check("nonexistent/", False, "/nonexistent")
791+
check("nonexistent/", ALL_BUT_LAST, "/nonexistent")
792+
check("nonexistent/", True, FileNotFoundError)
793+
check("nonexistent/file", False, "/nonexistent/file")
794+
check("nonexistent/file", ALL_BUT_LAST, FileNotFoundError)
795+
check("nonexistent/file", True, FileNotFoundError)
796+
check("nonexistent/../link", False, "/file")
797+
check("nonexistent/../link", ALL_BUT_LAST, "/file")
798+
check("nonexistent/../link", True, "/file")
799+
800+
check("broken", False, "/nonexistent")
801+
check("broken", ALL_BUT_LAST, "/nonexistent")
802+
check("broken", True, FileNotFoundError)
803+
check("broken/", False, "/nonexistent")
804+
check("broken/", ALL_BUT_LAST, "/nonexistent")
805+
check("broken/", True, FileNotFoundError)
806+
check("broken/file", False, "/nonexistent/file")
807+
check("broken/file", ALL_BUT_LAST, FileNotFoundError)
808+
check("broken/file", True, FileNotFoundError)
809+
check("broken/../link", False, "/file")
810+
check("broken/../link", ALL_BUT_LAST, "/file")
811+
check("broken/../link", True, "/file")
812+
813+
check("cycle", False, "/cycle")
814+
check("cycle", ALL_BUT_LAST, OSError, errno.EINVAL)
815+
check("cycle", True, OSError, errno.EINVAL)
816+
check("cycle/", False, "/cycle")
817+
check("cycle/", ALL_BUT_LAST, OSError, errno.EINVAL)
818+
check("cycle/", True, OSError, errno.EINVAL)
819+
check("cycle/file", False, "/cycle/file")
820+
check("cycle/file", ALL_BUT_LAST, OSError, errno.EINVAL)
821+
check("cycle/file", True, OSError, errno.EINVAL)
822+
check("cycle/../link", False, "/file")
823+
check("cycle/../link", ALL_BUT_LAST, "/file")
824+
check("cycle/../link", True, "/file")
825+
finally:
826+
os_helper.unlink(ABSTFN + "/file")
827+
os_helper.unlink(ABSTFN + "/dir/file2")
828+
os_helper.unlink(ABSTFN + "/link")
829+
os_helper.unlink(ABSTFN + "/link2")
830+
os_helper.unlink(ABSTFN + "/broken")
831+
os_helper.unlink(ABSTFN + "/cycle")
832+
os_helper.rmdir(ABSTFN + "/dir")
833+
os_helper.rmdir(ABSTFN)
834+
708835
def test_expandvars(self):
709836
with os_helper.EnvironmentVarGuard() as env:
710837
env.clear()

Lib/test/test_posixpath.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ def test_realpath_nonterminal_symlink_to_symlinks_to_file(self):
767767
os_helper.unlink(ABSTFN)
768768

769769
@os_helper.skip_unless_symlink
770-
@skip_if_ABSTFN_contains_backslash
770+
# @skip_if_ABSTFN_contains_backslash
771771
def test_realpath_mode(self):
772772
try:
773773
os.mkdir(ABSTFN)
@@ -781,7 +781,8 @@ def test_realpath_mode(self):
781781
def check(path, mode, expected, errno=None):
782782
if isinstance(expected, str):
783783
assert errno is None
784-
self.assertEqual(realpath(path, strict=mode), ABSTFN + expected)
784+
self.assertEqual(realpath(path, strict=mode).replace('/', os.sep),
785+
ABSTFN.replace('/', os.sep) + expected.replace('/', os.sep))
785786
else:
786787
with self.assertRaises(expected) as cm:
787788
realpath(path, strict=mode)

0 commit comments

Comments
 (0)