mirror of
https://github.com/python/cpython.git
synced 2025-07-24 11:44:31 +00:00
bpo-37834: Normalise handling of reparse points on Windows (GH-15231)
bpo-37834: Normalise handling of reparse points on Windows * ntpath.realpath() and nt.stat() will traverse all supported reparse points (previously was mixed) * nt.lstat() will let the OS traverse reparse points that are not name surrogates (previously would not traverse any reparse point) * nt.[l]stat() will only set S_IFLNK for symlinks (previous behaviour) * nt.readlink() will read destinations for symlinks and junction points only bpo-1311: os.path.exists('nul') now returns True on Windows * nt.stat('nul').st_mode is now S_IFCHR (previously was an error)
This commit is contained in:
parent
bcc446f525
commit
df2d4a6f3d
16 changed files with 477 additions and 240 deletions
|
@ -8,6 +8,7 @@ import codecs
|
|||
import contextlib
|
||||
import decimal
|
||||
import errno
|
||||
import fnmatch
|
||||
import fractions
|
||||
import itertools
|
||||
import locale
|
||||
|
@ -2253,6 +2254,20 @@ class ReadlinkTests(unittest.TestCase):
|
|||
filelinkb = os.fsencode(filelink)
|
||||
filelinkb_target = os.fsencode(filelink_target)
|
||||
|
||||
def assertPathEqual(self, left, right):
|
||||
left = os.path.normcase(left)
|
||||
right = os.path.normcase(right)
|
||||
if sys.platform == 'win32':
|
||||
# Bad practice to blindly strip the prefix as it may be required to
|
||||
# correctly refer to the file, but we're only comparing paths here.
|
||||
has_prefix = lambda p: p.startswith(
|
||||
b'\\\\?\\' if isinstance(p, bytes) else '\\\\?\\')
|
||||
if has_prefix(left):
|
||||
left = left[4:]
|
||||
if has_prefix(right):
|
||||
right = right[4:]
|
||||
self.assertEqual(left, right)
|
||||
|
||||
def setUp(self):
|
||||
self.assertTrue(os.path.exists(self.filelink_target))
|
||||
self.assertTrue(os.path.exists(self.filelinkb_target))
|
||||
|
@ -2274,14 +2289,14 @@ class ReadlinkTests(unittest.TestCase):
|
|||
os.symlink(self.filelink_target, self.filelink)
|
||||
self.addCleanup(support.unlink, self.filelink)
|
||||
filelink = FakePath(self.filelink)
|
||||
self.assertEqual(os.readlink(filelink), self.filelink_target)
|
||||
self.assertPathEqual(os.readlink(filelink), self.filelink_target)
|
||||
|
||||
@support.skip_unless_symlink
|
||||
def test_pathlike_bytes(self):
|
||||
os.symlink(self.filelinkb_target, self.filelinkb)
|
||||
self.addCleanup(support.unlink, self.filelinkb)
|
||||
path = os.readlink(FakePath(self.filelinkb))
|
||||
self.assertEqual(path, self.filelinkb_target)
|
||||
self.assertPathEqual(path, self.filelinkb_target)
|
||||
self.assertIsInstance(path, bytes)
|
||||
|
||||
@support.skip_unless_symlink
|
||||
|
@ -2289,7 +2304,7 @@ class ReadlinkTests(unittest.TestCase):
|
|||
os.symlink(self.filelinkb_target, self.filelinkb)
|
||||
self.addCleanup(support.unlink, self.filelinkb)
|
||||
path = os.readlink(self.filelinkb)
|
||||
self.assertEqual(path, self.filelinkb_target)
|
||||
self.assertPathEqual(path, self.filelinkb_target)
|
||||
self.assertIsInstance(path, bytes)
|
||||
|
||||
|
||||
|
@ -2348,16 +2363,12 @@ class Win32SymlinkTests(unittest.TestCase):
|
|||
# was created with target_is_dir==True.
|
||||
os.remove(self.missing_link)
|
||||
|
||||
@unittest.skip("currently fails; consider for improvement")
|
||||
def test_isdir_on_directory_link_to_missing_target(self):
|
||||
self._create_missing_dir_link()
|
||||
# consider having isdir return true for directory links
|
||||
self.assertTrue(os.path.isdir(self.missing_link))
|
||||
self.assertFalse(os.path.isdir(self.missing_link))
|
||||
|
||||
@unittest.skip("currently fails; consider for improvement")
|
||||
def test_rmdir_on_directory_link_to_missing_target(self):
|
||||
self._create_missing_dir_link()
|
||||
# consider allowing rmdir to remove directory links
|
||||
os.rmdir(self.missing_link)
|
||||
|
||||
def check_stat(self, link, target):
|
||||
|
@ -2453,6 +2464,24 @@ class Win32SymlinkTests(unittest.TestCase):
|
|||
except OSError:
|
||||
pass
|
||||
|
||||
def test_appexeclink(self):
|
||||
root = os.path.expandvars(r'%LOCALAPPDATA%\Microsoft\WindowsApps')
|
||||
aliases = [os.path.join(root, a)
|
||||
for a in fnmatch.filter(os.listdir(root), '*.exe')]
|
||||
|
||||
for alias in aliases:
|
||||
if support.verbose:
|
||||
print()
|
||||
print("Testing with", alias)
|
||||
st = os.lstat(alias)
|
||||
self.assertEqual(st, os.stat(alias))
|
||||
self.assertFalse(stat.S_ISLNK(st.st_mode))
|
||||
self.assertEqual(st.st_reparse_tag, stat.IO_REPARSE_TAG_APPEXECLINK)
|
||||
# testing the first one we see is sufficient
|
||||
break
|
||||
else:
|
||||
self.skipTest("test requires an app execution alias")
|
||||
|
||||
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
|
||||
class Win32JunctionTests(unittest.TestCase):
|
||||
junction = 'junctiontest'
|
||||
|
@ -2460,25 +2489,29 @@ class Win32JunctionTests(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
assert os.path.exists(self.junction_target)
|
||||
assert not os.path.exists(self.junction)
|
||||
assert not os.path.lexists(self.junction)
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists(self.junction):
|
||||
# os.rmdir delegates to Windows' RemoveDirectoryW,
|
||||
# which removes junction points safely.
|
||||
os.rmdir(self.junction)
|
||||
if os.path.lexists(self.junction):
|
||||
os.unlink(self.junction)
|
||||
|
||||
def test_create_junction(self):
|
||||
_winapi.CreateJunction(self.junction_target, self.junction)
|
||||
self.assertTrue(os.path.lexists(self.junction))
|
||||
self.assertTrue(os.path.exists(self.junction))
|
||||
self.assertTrue(os.path.isdir(self.junction))
|
||||
self.assertNotEqual(os.stat(self.junction), os.lstat(self.junction))
|
||||
self.assertEqual(os.stat(self.junction), os.stat(self.junction_target))
|
||||
|
||||
# Junctions are not recognized as links.
|
||||
# bpo-37834: Junctions are not recognized as links.
|
||||
self.assertFalse(os.path.islink(self.junction))
|
||||
self.assertEqual(os.path.normcase("\\\\?\\" + self.junction_target),
|
||||
os.path.normcase(os.readlink(self.junction)))
|
||||
|
||||
def test_unlink_removes_junction(self):
|
||||
_winapi.CreateJunction(self.junction_target, self.junction)
|
||||
self.assertTrue(os.path.exists(self.junction))
|
||||
self.assertTrue(os.path.lexists(self.junction))
|
||||
|
||||
os.unlink(self.junction)
|
||||
self.assertFalse(os.path.exists(self.junction))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue