summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-12-06 21:39:45 (GMT)
committerGitHub <noreply@github.com>2024-12-06 21:39:45 (GMT)
commit31c9f3ced293492b38e784c17c4befe425da5dab (patch)
treeb1c83ae065746025ec183c067c6dae52bb04de13 /Lib
parent0fc4063747c96223575f6f5a0562eddf2ed0ed62 (diff)
downloadcpython-31c9f3ced293492b38e784c17c4befe425da5dab.zip
cpython-31c9f3ced293492b38e784c17c4befe425da5dab.tar.gz
cpython-31c9f3ced293492b38e784c17c4befe425da5dab.tar.bz2
GH-127381: pathlib ABCs: remove `PathBase.resolve()` and `absolute()` (#127707)
Remove our implementation of POSIX path resolution in `PathBase.resolve()`. This functionality is rather fragile and isn't necessary in most cases. It depends on `PathBase.stat()`, which we're looking to remove. Also remove `PathBase.absolute()`. Many legitimate virtual filesystems lack the notion of a 'current directory', so it's wrong to include in the basic interface.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/pathlib/_abc.py64
-rw-r--r--Lib/test/test_pathlib/test_pathlib.py586
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py680
3 files changed, 599 insertions, 731 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index 11a11ec..820970f 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -13,7 +13,6 @@ resemble pathlib's PurePath and Path respectively.
import functools
import operator
-import posixpath
from errno import EINVAL
from glob import _GlobberBase, _no_recurse_symlinks
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
@@ -115,11 +114,6 @@ class PurePathBase:
# The `_raw_paths` slot stores unjoined string paths. This is set in
# the `__init__()` method.
'_raw_paths',
-
- # The '_resolving' slot stores a boolean indicating whether the path
- # is being processed by `PathBase.resolve()`. This prevents duplicate
- # work from occurring when `resolve()` calls `stat()` or `readlink()`.
- '_resolving',
)
parser = ParserBase()
_globber = PathGlobber
@@ -130,7 +124,6 @@ class PurePathBase:
raise TypeError(
f"argument should be a str, not {type(arg).__name__!r}")
self._raw_paths = list(args)
- self._resolving = False
def with_segments(self, *pathsegments):
"""Construct a new path object from any number of path-like objects.
@@ -339,9 +332,7 @@ class PurePathBase:
path = str(self)
parent = self.parser.split(path)[0]
if path != parent:
- parent = self.with_segments(parent)
- parent._resolving = self._resolving
- return parent
+ return self.with_segments(parent)
return self
@property
@@ -424,9 +415,6 @@ class PathBase(PurePathBase):
"""
__slots__ = ()
- # Maximum number of symlinks to follow in resolve()
- _max_symlinks = 40
-
@classmethod
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported"
@@ -720,20 +708,6 @@ class PathBase(PurePathBase):
yield path, dirnames, filenames
paths += [path.joinpath(d) for d in reversed(dirnames)]
- def absolute(self):
- """Return an absolute version of this path
- No normalization or symlink resolution is performed.
-
- Use resolve() to resolve symlinks and remove '..' segments.
- """
- if self.is_absolute():
- return self
- elif self.parser is not posixpath:
- raise UnsupportedOperation(self._unsupported_msg('absolute()'))
- else:
- # Treat the root directory as the current working directory.
- return self.with_segments('/', *self._raw_paths)
-
def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
(as returned by os.path.expanduser)
@@ -745,42 +719,6 @@ class PathBase(PurePathBase):
Return the path to which the symbolic link points.
"""
raise UnsupportedOperation(self._unsupported_msg('readlink()'))
- readlink._supported = False
-
- def resolve(self, strict=False):
- """
- Make the path absolute, resolving all symlinks on the way and also
- normalizing it.
- """
- if self._resolving:
- return self
- elif self.parser is not posixpath:
- raise UnsupportedOperation(self._unsupported_msg('resolve()'))
-
- def raise_error(*args):
- raise OSError("Unsupported operation.")
-
- getcwd = raise_error
- if strict or getattr(self.readlink, '_supported', True):
- def lstat(path_str):
- path = self.with_segments(path_str)
- path._resolving = True
- return path.stat(follow_symlinks=False)
-
- def readlink(path_str):
- path = self.with_segments(path_str)
- path._resolving = True
- return str(path.readlink())
- else:
- # If the user has *not* overridden the `readlink()` method, then
- # symlinks are unsupported and (in non-strict mode) we can improve
- # performance by not calling `path.lstat()`.
- lstat = readlink = raise_error
-
- return self.with_segments(posixpath._realpath(
- str(self.absolute()), strict, self.parser.sep,
- getcwd=getcwd, lstat=lstat, readlink=readlink,
- maxlinks=self._max_symlinks))
def symlink_to(self, target, target_is_directory=False):
"""
diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py
index 2c48eee..8c9049f 100644
--- a/Lib/test/test_pathlib/test_pathlib.py
+++ b/Lib/test/test_pathlib/test_pathlib.py
@@ -1,3 +1,4 @@
+import collections
import contextlib
import io
import os
@@ -21,7 +22,7 @@ from test.support import swap_attr
from test.support import os_helper
from test.support.os_helper import TESTFN, FakePath
from test.test_pathlib import test_pathlib_abc
-from test.test_pathlib.test_pathlib_abc import needs_posix, needs_windows, needs_symlinks
+from test.test_pathlib.test_pathlib_abc import needs_posix, needs_windows
try:
import fcntl
@@ -55,6 +56,13 @@ def patch_replace(old_test):
self.cls.replace = old_replace
return new_test
+
+_tests_needing_symlinks = set()
+def needs_symlinks(fn):
+ """Decorator that marks a test as requiring a path class that supports symlinks."""
+ _tests_needing_symlinks.add(fn.__name__)
+ return fn
+
#
# Tests for the pure classes.
#
@@ -533,6 +541,9 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
can_symlink = os_helper.can_symlink()
def setUp(self):
+ name = self.id().split('.')[-1]
+ if name in _tests_needing_symlinks and not self.can_symlink:
+ self.skipTest('requires symlinks')
super().setUp()
os.chmod(self.parser.join(self.base, 'dirE'), 0)
@@ -693,6 +704,34 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
if hasattr(source_st, 'st_flags'):
self.assertEqual(source_st.st_flags, target_st.st_flags)
+ @needs_symlinks
+ def test_copy_file_to_existing_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'dirB' / 'fileB'
+ target = base / 'linkA'
+ real_target = base / 'fileA'
+ result = source.copy(target)
+ self.assertEqual(result, target)
+ self.assertTrue(target.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertTrue(real_target.exists())
+ self.assertFalse(real_target.is_symlink())
+ self.assertEqual(source.read_text(), real_target.read_text())
+
+ @needs_symlinks
+ def test_copy_file_to_existing_symlink_follow_symlinks_false(self):
+ base = self.cls(self.base)
+ source = base / 'dirB' / 'fileB'
+ target = base / 'linkA'
+ real_target = base / 'fileA'
+ result = source.copy(target, follow_symlinks=False)
+ self.assertEqual(result, target)
+ self.assertTrue(target.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertTrue(real_target.exists())
+ self.assertFalse(real_target.is_symlink())
+ self.assertEqual(source.read_text(), real_target.read_text())
+
@os_helper.skip_unless_xattr
def test_copy_file_preserve_metadata_xattrs(self):
base = self.cls(self.base)
@@ -703,6 +742,118 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertEqual(os.getxattr(target, b'user.foo'), b'42')
@needs_symlinks
+ def test_copy_symlink_follow_symlinks_true(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ target = base / 'copyA'
+ result = source.copy(target)
+ self.assertEqual(result, target)
+ self.assertTrue(target.exists())
+ self.assertFalse(target.is_symlink())
+ self.assertEqual(source.read_text(), target.read_text())
+
+ @needs_symlinks
+ def test_copy_symlink_follow_symlinks_false(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ target = base / 'copyA'
+ result = source.copy(target, follow_symlinks=False)
+ self.assertEqual(result, target)
+ self.assertTrue(target.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source.readlink(), target.readlink())
+
+ @needs_symlinks
+ def test_copy_symlink_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ self.assertRaises(OSError, source.copy, source)
+
+ @needs_symlinks
+ def test_copy_symlink_to_existing_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'copySource'
+ target = base / 'copyTarget'
+ source.symlink_to(base / 'fileA')
+ target.symlink_to(base / 'dirC')
+ self.assertRaises(OSError, source.copy, target)
+ self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
+
+ @needs_symlinks
+ def test_copy_symlink_to_existing_directory_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'copySource'
+ target = base / 'copyTarget'
+ source.symlink_to(base / 'fileA')
+ target.symlink_to(base / 'dirC')
+ self.assertRaises(OSError, source.copy, target)
+ self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
+
+ @needs_symlinks
+ def test_copy_directory_symlink_follow_symlinks_false(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ target = base / 'copyA'
+ result = source.copy(target, follow_symlinks=False)
+ self.assertEqual(result, target)
+ self.assertTrue(target.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source.readlink(), target.readlink())
+
+ @needs_symlinks
+ def test_copy_directory_symlink_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ self.assertRaises(OSError, source.copy, source)
+ self.assertRaises(OSError, source.copy, source, follow_symlinks=False)
+
+ @needs_symlinks
+ def test_copy_directory_symlink_into_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ target = base / 'linkB' / 'copyB'
+ self.assertRaises(OSError, source.copy, target)
+ self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
+ self.assertFalse(target.exists())
+
+ @needs_symlinks
+ def test_copy_directory_symlink_to_existing_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'copySource'
+ target = base / 'copyTarget'
+ source.symlink_to(base / 'dirC')
+ target.symlink_to(base / 'fileA')
+ self.assertRaises(FileExistsError, source.copy, target)
+ self.assertRaises(FileExistsError, source.copy, target, follow_symlinks=False)
+
+ @needs_symlinks
+ def test_copy_directory_symlink_to_existing_directory_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'copySource'
+ target = base / 'copyTarget'
+ source.symlink_to(base / 'dirC' / 'dirD')
+ target.symlink_to(base / 'dirC')
+ self.assertRaises(FileExistsError, source.copy, target)
+ self.assertRaises(FileExistsError, source.copy, target, follow_symlinks=False)
+
+ @needs_symlinks
+ def test_copy_dangling_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'source'
+ target = base / 'target'
+
+ source.mkdir()
+ source.joinpath('link').symlink_to('nonexistent')
+
+ self.assertRaises(FileNotFoundError, source.copy, target)
+
+ target2 = base / 'target2'
+ result = source.copy(target2, follow_symlinks=False)
+ self.assertEqual(result, target2)
+ self.assertTrue(target2.joinpath('link').is_symlink())
+ self.assertEqual(target2.joinpath('link').readlink(), self.cls('nonexistent'))
+
+ @needs_symlinks
def test_copy_link_preserve_metadata(self):
base = self.cls(self.base)
source = base / 'linkA'
@@ -801,6 +952,54 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
target_file = target.joinpath('dirD', 'fileD')
self.assertEqual(os.getxattr(target_file, b'user.foo'), b'42')
+ @needs_symlinks
+ def test_move_file_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ source_readlink = source.readlink()
+ target = base / 'linkA_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source_readlink, target.readlink())
+
+ @needs_symlinks
+ def test_move_file_symlink_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkA'
+ self.assertRaises(OSError, source.move, source)
+
+ @needs_symlinks
+ def test_move_dir_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ source_readlink = source.readlink()
+ target = base / 'linkB_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source_readlink, target.readlink())
+
+ @needs_symlinks
+ def test_move_dir_symlink_to_itself(self):
+ base = self.cls(self.base)
+ source = base / 'linkB'
+ self.assertRaises(OSError, source.move, source)
+
+ @needs_symlinks
+ def test_move_dangling_symlink(self):
+ base = self.cls(self.base)
+ source = base / 'brokenLink'
+ source_readlink = source.readlink()
+ target = base / 'brokenLink_moved'
+ result = source.move(target)
+ self.assertEqual(result, target)
+ self.assertFalse(source.exists())
+ self.assertTrue(target.is_symlink())
+ self.assertEqual(source_readlink, target.readlink())
+
@patch_replace
def test_move_file_other_fs(self):
self.test_move_file()
@@ -858,9 +1057,41 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
def test_move_into_empty_name_other_os(self):
self.test_move_into_empty_name()
+ @needs_symlinks
+ def test_complex_symlinks_absolute(self):
+ self._check_complex_symlinks(self.base)
+
+ @needs_symlinks
+ def test_complex_symlinks_relative(self):
+ self._check_complex_symlinks('.')
+
+ @needs_symlinks
+ def test_complex_symlinks_relative_dot_dot(self):
+ self._check_complex_symlinks(self.parser.join('dirA', '..'))
+
def _check_complex_symlinks(self, link0_target):
- super()._check_complex_symlinks(link0_target)
+ # Test solving a non-looping chain of symlinks (issue #19887).
+ parser = self.parser
P = self.cls(self.base)
+ P.joinpath('link1').symlink_to(parser.join('link0', 'link0'), target_is_directory=True)
+ P.joinpath('link2').symlink_to(parser.join('link1', 'link1'), target_is_directory=True)
+ P.joinpath('link3').symlink_to(parser.join('link2', 'link2'), target_is_directory=True)
+ P.joinpath('link0').symlink_to(link0_target, target_is_directory=True)
+
+ # Resolve absolute paths.
+ p = (P / 'link0').resolve()
+ self.assertEqual(p, P)
+ self.assertEqualNormCase(str(p), self.base)
+ p = (P / 'link1').resolve()
+ self.assertEqual(p, P)
+ self.assertEqualNormCase(str(p), self.base)
+ p = (P / 'link2').resolve()
+ self.assertEqual(p, P)
+ self.assertEqualNormCase(str(p), self.base)
+ p = (P / 'link3').resolve()
+ self.assertEqual(p, P)
+ self.assertEqualNormCase(str(p), self.base)
+
# Resolve relative paths.
old_path = os.getcwd()
os.chdir(self.base)
@@ -880,6 +1111,118 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
finally:
os.chdir(old_path)
+ def _check_resolve(self, p, expected, strict=True):
+ q = p.resolve(strict)
+ self.assertEqual(q, expected)
+
+ # This can be used to check both relative and absolute resolutions.
+ _check_resolve_relative = _check_resolve_absolute = _check_resolve
+
+ @needs_symlinks
+ def test_resolve_common(self):
+ P = self.cls
+ p = P(self.base, 'foo')
+ with self.assertRaises(OSError) as cm:
+ p.resolve(strict=True)
+ self.assertEqual(cm.exception.errno, errno.ENOENT)
+ # Non-strict
+ parser = self.parser
+ self.assertEqualNormCase(str(p.resolve(strict=False)),
+ parser.join(self.base, 'foo'))
+ p = P(self.base, 'foo', 'in', 'spam')
+ self.assertEqualNormCase(str(p.resolve(strict=False)),
+ parser.join(self.base, 'foo', 'in', 'spam'))
+ p = P(self.base, '..', 'foo', 'in', 'spam')
+ self.assertEqualNormCase(str(p.resolve(strict=False)),
+ parser.join(parser.dirname(self.base), 'foo', 'in', 'spam'))
+ # These are all relative symlinks.
+ p = P(self.base, 'dirB', 'fileB')
+ self._check_resolve_relative(p, p)
+ p = P(self.base, 'linkA')
+ self._check_resolve_relative(p, P(self.base, 'fileA'))
+ p = P(self.base, 'dirA', 'linkC', 'fileB')
+ self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB'))
+ p = P(self.base, 'dirB', 'linkD', 'fileB')
+ self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB'))
+ # Non-strict
+ p = P(self.base, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam')
+ self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB', 'foo', 'in',
+ 'spam'), False)
+ p = P(self.base, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')
+ if self.cls.parser is not posixpath:
+ # In Windows, if linkY points to dirB, 'dirA\linkY\..'
+ # resolves to 'dirA' without resolving linkY first.
+ self._check_resolve_relative(p, P(self.base, 'dirA', 'foo', 'in',
+ 'spam'), False)
+ else:
+ # In Posix, if linkY points to dirB, 'dirA/linkY/..'
+ # resolves to 'dirB/..' first before resolving to parent of dirB.
+ self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False)
+ # Now create absolute symlinks.
+ d = self.tempdir()
+ P(self.base, 'dirA', 'linkX').symlink_to(d)
+ P(self.base, str(d), 'linkY').symlink_to(self.parser.join(self.base, 'dirB'))
+ p = P(self.base, 'dirA', 'linkX', 'linkY', 'fileB')
+ self._check_resolve_absolute(p, P(self.base, 'dirB', 'fileB'))
+ # Non-strict
+ p = P(self.base, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam')
+ self._check_resolve_relative(p, P(self.base, 'dirB', 'foo', 'in', 'spam'),
+ False)
+ p = P(self.base, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')
+ if self.cls.parser is not posixpath:
+ # In Windows, if linkY points to dirB, 'dirA\linkY\..'
+ # resolves to 'dirA' without resolving linkY first.
+ self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False)
+ else:
+ # In Posix, if linkY points to dirB, 'dirA/linkY/..'
+ # resolves to 'dirB/..' first before resolving to parent of dirB.
+ self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False)
+
+ @needs_symlinks
+ def test_resolve_dot(self):
+ # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/
+ parser = self.parser
+ p = self.cls(self.base)
+ p.joinpath('0').symlink_to('.', target_is_directory=True)
+ p.joinpath('1').symlink_to(parser.join('0', '0'), target_is_directory=True)
+ p.joinpath('2').symlink_to(parser.join('1', '1'), target_is_directory=True)
+ q = p / '2'
+ self.assertEqual(q.resolve(strict=True), p)
+ r = q / '3' / '4'
+ self.assertRaises(FileNotFoundError, r.resolve, strict=True)
+ # Non-strict
+ self.assertEqual(r.resolve(strict=False), p / '3' / '4')
+
+ def _check_symlink_loop(self, *args):
+ path = self.cls(*args)
+ with self.assertRaises(OSError) as cm:
+ path.resolve(strict=True)
+ self.assertEqual(cm.exception.errno, errno.ELOOP)
+
+ @needs_posix
+ @needs_symlinks
+ def test_resolve_loop(self):
+ # Loops with relative symlinks.
+ self.cls(self.base, 'linkX').symlink_to('linkX/inside')
+ self._check_symlink_loop(self.base, 'linkX')
+ self.cls(self.base, 'linkY').symlink_to('linkY')
+ self._check_symlink_loop(self.base, 'linkY')
+ self.cls(self.base, 'linkZ').symlink_to('linkZ/../linkZ')
+ self._check_symlink_loop(self.base, 'linkZ')
+ # Non-strict
+ p = self.cls(self.base, 'linkZ', 'foo')
+ self.assertEqual(p.resolve(strict=False), p)
+ # Loops with absolute symlinks.
+ self.cls(self.base, 'linkU').symlink_to(self.parser.join(self.base, 'linkU/inside'))
+ self._check_symlink_loop(self.base, 'linkU')
+ self.cls(self.base, 'linkV').symlink_to(self.parser.join(self.base, 'linkV'))
+ self._check_symlink_loop(self.base, 'linkV')
+ self.cls(self.base, 'linkW').symlink_to(self.parser.join(self.base, 'linkW/../linkW'))
+ self._check_symlink_loop(self.base, 'linkW')
+ # Non-strict
+ q = self.cls(self.base, 'linkW', 'foo')
+ self.assertEqual(q.resolve(strict=False), q)
+
def test_resolve_nonexist_relative_issue38671(self):
p = self.cls('non', 'exist')
@@ -890,6 +1233,24 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
finally:
os.chdir(old_cwd)
+ @needs_symlinks
+ def test_readlink(self):
+ P = self.cls(self.base)
+ self.assertEqual((P / 'linkA').readlink(), self.cls('fileA'))
+ self.assertEqual((P / 'brokenLink').readlink(),
+ self.cls('non-existing'))
+ self.assertEqual((P / 'linkB').readlink(), self.cls('dirB'))
+ self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB'))
+ with self.assertRaises(OSError):
+ (P / 'fileA').readlink()
+
+ @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present")
+ def test_readlink_unsupported(self):
+ P = self.cls(self.base)
+ p = P / 'fileA'
+ with self.assertRaises(pathlib.UnsupportedOperation):
+ q.readlink(p)
+
@os_helper.skip_unless_working_chmod
def test_chmod(self):
p = self.cls(self.base) / 'fileA'
@@ -991,6 +1352,41 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertEqual(expected_gid, gid_2)
self.assertEqual(expected_name, link.group(follow_symlinks=False))
+ @needs_symlinks
+ def test_delete_symlink(self):
+ tmp = self.cls(self.base, 'delete')
+ tmp.mkdir()
+ dir_ = tmp / 'dir'
+ dir_.mkdir()
+ link = tmp / 'link'
+ link.symlink_to(dir_)
+ link._delete()
+ self.assertTrue(dir_.exists())
+ self.assertFalse(link.exists(follow_symlinks=False))
+
+ @needs_symlinks
+ def test_delete_inner_symlink(self):
+ tmp = self.cls(self.base, 'delete')
+ tmp.mkdir()
+ dir1 = tmp / 'dir1'
+ dir2 = dir1 / 'dir2'
+ dir3 = tmp / 'dir3'
+ for d in dir1, dir2, dir3:
+ d.mkdir()
+ file1 = tmp / 'file1'
+ file1.write_text('foo')
+ link1 = dir1 / 'link1'
+ link1.symlink_to(dir2)
+ link2 = dir1 / 'link2'
+ link2.symlink_to(dir3)
+ link3 = dir1 / 'link3'
+ link3.symlink_to(file1)
+ # make sure symlinks are removed but not followed
+ dir1._delete()
+ self.assertFalse(dir1.exists())
+ self.assertTrue(dir3.exists())
+ self.assertTrue(file1.exists())
+
@unittest.skipIf(sys.platform[:6] == 'cygwin',
"This test can't be run on Cygwin (issue #1071513).")
@os_helper.skip_if_dac_override
@@ -1355,6 +1751,12 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
q.symlink_to(p)
@needs_symlinks
+ def test_stat_no_follow_symlinks(self):
+ p = self.cls(self.base) / 'linkA'
+ st = p.stat()
+ self.assertNotEqual(st, p.stat(follow_symlinks=False))
+
+ @needs_symlinks
def test_lstat(self):
p = self.cls(self.base)/ 'linkA'
st = p.stat()
@@ -1433,6 +1835,15 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
with self.assertRaises(TypeError):
self.cls(foo="bar")
+ @needs_symlinks
+ def test_iterdir_symlink(self):
+ # __iter__ on a symlink to a directory.
+ P = self.cls
+ p = P(self.base, 'linkB')
+ paths = set(p.iterdir())
+ expected = { P(self.base, 'linkB', q) for q in ['fileB', 'linkD'] }
+ self.assertEqual(paths, expected)
+
def test_glob_empty_pattern(self):
p = self.cls('')
with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'):
@@ -1493,6 +1904,25 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertEqual(
set(P('.').glob('**/*/*')), {P("dirD/fileD")})
+ # See https://github.com/WebAssembly/wasi-filesystem/issues/26
+ @unittest.skipIf(is_wasi, "WASI resolution of '..' parts doesn't match POSIX")
+ def test_glob_dotdot(self):
+ # ".." is not special in globs.
+ P = self.cls
+ p = P(self.base)
+ self.assertEqual(set(p.glob("..")), { P(self.base, "..") })
+ self.assertEqual(set(p.glob("../..")), { P(self.base, "..", "..") })
+ self.assertEqual(set(p.glob("dirA/..")), { P(self.base, "dirA", "..") })
+ self.assertEqual(set(p.glob("dirA/../file*")), { P(self.base, "dirA/../fileA") })
+ self.assertEqual(set(p.glob("dirA/../file*/..")), set())
+ self.assertEqual(set(p.glob("../xyzzy")), set())
+ if self.cls.parser is posixpath:
+ self.assertEqual(set(p.glob("xyzzy/..")), set())
+ else:
+ # ".." segments are normalized first on Windows, so this path is stat()able.
+ self.assertEqual(set(p.glob("xyzzy/..")), { P(self.base, "xyzzy", "..") })
+ self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)})
+
def test_glob_inaccessible(self):
P = self.cls
p = P(self.base, "mydir1", "mydir2")
@@ -1508,6 +1938,124 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
self.assertEqual(expect, set(p.rglob(P(pattern))))
self.assertEqual(expect, set(p.rglob(FakePath(pattern))))
+ @needs_symlinks
+ @unittest.skipIf(is_emscripten, "Hangs")
+ def test_glob_recurse_symlinks_common(self):
+ def _check(path, glob, expected):
+ actual = {path for path in path.glob(glob, recurse_symlinks=True)
+ if path.parts.count("linkD") <= 1} # exclude symlink loop.
+ self.assertEqual(actual, { P(self.base, q) for q in expected })
+ P = self.cls
+ p = P(self.base)
+ _check(p, "fileB", [])
+ _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
+ _check(p, "*A", ["dirA", "fileA", "linkA"])
+ _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"])
+ _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
+ _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
+ _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."])
+ _check(p, "dir*/**", [
+ "dirA/", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB",
+ "dirB/", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB",
+ "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
+ "dirE/"])
+ _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
+ "dirC/", "dirC/dirD/", "dirE/"])
+ _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
+ "dirB/linkD/..", "dirA/linkC/linkD/..",
+ "dirC/..", "dirC/dirD/..", "dirE/.."])
+ _check(p, "dir*/*/**", [
+ "dirA/linkC/", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB",
+ "dirB/linkD/", "dirB/linkD/fileB",
+ "dirC/dirD/", "dirC/dirD/fileD"])
+ _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
+ _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..",
+ "dirB/linkD/..", "dirC/dirD/.."])
+ _check(p, "dir*/**/fileC", ["dirC/fileC"])
+ _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
+ _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"])
+ _check(p, "*/dirD/**/", ["dirC/dirD/"])
+
+ @needs_symlinks
+ @unittest.skipIf(is_emscripten, "Hangs")
+ def test_rglob_recurse_symlinks_common(self):
+ def _check(path, glob, expected):
+ actual = {path for path in path.rglob(glob, recurse_symlinks=True)
+ if path.parts.count("linkD") <= 1} # exclude symlink loop.
+ self.assertEqual(actual, { P(self.base, q) for q in expected })
+ P = self.cls
+ p = P(self.base)
+ _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB",
+ "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB"])
+ _check(p, "*/fileA", [])
+ _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB",
+ "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB"])
+ _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB",
+ "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB",
+ "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"])
+ _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
+ "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"])
+ _check(p, "", ["", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
+ "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"])
+
+ p = P(self.base, "dirC")
+ _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
+ "dirC/dirD", "dirC/dirD/fileD"])
+ _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
+ _check(p, "*/*", ["dirC/dirD/fileD"])
+ _check(p, "*/", ["dirC/dirD/"])
+ _check(p, "", ["dirC/", "dirC/dirD/"])
+ # gh-91616, a re module regression
+ _check(p, "*.txt", ["dirC/novel.txt"])
+ _check(p, "*.*", ["dirC/novel.txt"])
+
+ @needs_symlinks
+ def test_rglob_symlink_loop(self):
+ # Don't get fooled by symlink loops (Issue #26012).
+ P = self.cls
+ p = P(self.base)
+ given = set(p.rglob('*', recurse_symlinks=False))
+ expect = {'brokenLink',
+ 'dirA', 'dirA/linkC',
+ 'dirB', 'dirB/fileB', 'dirB/linkD',
+ 'dirC', 'dirC/dirD', 'dirC/dirD/fileD',
+ 'dirC/fileC', 'dirC/novel.txt',
+ 'dirE',
+ 'fileA',
+ 'linkA',
+ 'linkB',
+ 'brokenLinkLoop',
+ }
+ self.assertEqual(given, {p / x for x in expect})
+
+ @needs_symlinks
+ def test_glob_permissions(self):
+ # See bpo-38894
+ P = self.cls
+ base = P(self.base) / 'permissions'
+ base.mkdir()
+
+ for i in range(100):
+ link = base / f"link{i}"
+ if i % 2:
+ link.symlink_to(P(self.base, "dirE", "nonexistent"))
+ else:
+ link.symlink_to(P(self.base, "dirC"), target_is_directory=True)
+
+ self.assertEqual(len(set(base.glob("*"))), 100)
+ self.assertEqual(len(set(base.glob("*/"))), 50)
+ self.assertEqual(len(set(base.glob("*/fileC"))), 50)
+ self.assertEqual(len(set(base.glob("*/file*"))), 50)
+
+ @needs_symlinks
+ def test_glob_long_symlink(self):
+ # See gh-87695
+ base = self.cls(self.base) / 'long_symlink'
+ base.mkdir()
+ bad_link = base / 'bad_link'
+ bad_link.symlink_to("bad" * 200)
+ self.assertEqual(sorted(base.glob('**/*')), [bad_link])
+
@needs_posix
def test_absolute_posix(self):
P = self.cls
@@ -1822,6 +2370,9 @@ class PathWalkTest(test_pathlib_abc.DummyPathWalkTest):
can_symlink = PathTest.can_symlink
def setUp(self):
+ name = self.id().split('.')[-1]
+ if name in _tests_needing_symlinks and not self.can_symlink:
+ self.skipTest('requires symlinks')
super().setUp()
sub21_path= self.sub2_path / "SUB21"
tmp5_path = sub21_path / "tmp3"
@@ -1903,6 +2454,37 @@ class PathWalkTest(test_pathlib_abc.DummyPathWalkTest):
list(base.walk())
list(base.walk(top_down=False))
+ @needs_symlinks
+ def test_walk_follow_symlinks(self):
+ walk_it = self.walk_path.walk(follow_symlinks=True)
+ for root, dirs, files in walk_it:
+ if root == self.link_path:
+ self.assertEqual(dirs, [])
+ self.assertEqual(files, ["tmp4"])
+ break
+ else:
+ self.fail("Didn't follow symlink with follow_symlinks=True")
+
+ @needs_symlinks
+ def test_walk_symlink_location(self):
+ # Tests whether symlinks end up in filenames or dirnames depending
+ # on the `follow_symlinks` argument.
+ walk_it = self.walk_path.walk(follow_symlinks=False)
+ for root, dirs, files in walk_it:
+ if root == self.sub2_path:
+ self.assertIn("link", files)
+ break
+ else:
+ self.fail("symlink not found")
+
+ walk_it = self.walk_path.walk(follow_symlinks=True)
+ for root, dirs, files in walk_it:
+ if root == self.sub2_path:
+ self.assertIn("link", dirs)
+ break
+ else:
+ self.fail("symlink not found")
+
@unittest.skipIf(os.name == 'nt', 'test requires a POSIX-compatible system')
class PosixPathTest(PathTest, PurePosixPathTest):
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
index 00153e3..bf9ae6c 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -8,13 +8,11 @@ import unittest
from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
import posixpath
-from test.support import is_wasi, is_emscripten
from test.support.os_helper import TESTFN
_tests_needing_posix = set()
_tests_needing_windows = set()
-_tests_needing_symlinks = set()
def needs_posix(fn):
@@ -27,11 +25,6 @@ def needs_windows(fn):
_tests_needing_windows.add(fn.__name__)
return fn
-def needs_symlinks(fn):
- """Decorator that marks a test as requiring a path class that supports symlinks."""
- _tests_needing_symlinks.add(fn.__name__)
- return fn
-
class UnsupportedOperationTest(unittest.TestCase):
def test_is_notimplemented(self):
@@ -1369,7 +1362,6 @@ class PathBaseTest(PurePathBaseTest):
self.assertRaises(e, p.glob, '*')
self.assertRaises(e, p.rglob, '*')
self.assertRaises(e, lambda: list(p.walk()))
- self.assertRaises(e, p.absolute)
self.assertRaises(e, p.expanduser)
self.assertRaises(e, p.readlink)
self.assertRaises(e, p.symlink_to, 'foo')
@@ -1425,7 +1417,6 @@ class DummyPath(PathBase):
_files = {}
_directories = {}
- _symlinks = {}
def __eq__(self, other):
if not isinstance(other, DummyPath):
@@ -1439,16 +1430,11 @@ class DummyPath(PathBase):
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
def stat(self, *, follow_symlinks=True):
- if follow_symlinks or self.name in ('', '.', '..'):
- path = str(self.resolve(strict=True))
- else:
- path = str(self.parent.resolve(strict=True) / self.name)
+ path = str(self).rstrip('/')
if path in self._files:
st_mode = stat.S_IFREG
elif path in self._directories:
st_mode = stat.S_IFDIR
- elif path in self._symlinks:
- st_mode = stat.S_IFLNK
else:
raise FileNotFoundError(errno.ENOENT, "Not found", str(self))
return DummyPathStatResult(st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0)
@@ -1457,10 +1443,7 @@ class DummyPath(PathBase):
errors=None, newline=None):
if buffering != -1 and not (buffering == 0 and 'b' in mode):
raise NotImplementedError
- path_obj = self.resolve()
- path = str(path_obj)
- name = path_obj.name
- parent = str(path_obj.parent)
+ path = str(self)
if path in self._directories:
raise IsADirectoryError(errno.EISDIR, "Is a directory", path)
@@ -1471,6 +1454,7 @@ class DummyPath(PathBase):
raise FileNotFoundError(errno.ENOENT, "File not found", path)
stream = io.BytesIO(self._files[path])
elif mode == 'w':
+ parent, name = posixpath.split(path)
if parent not in self._directories:
raise FileNotFoundError(errno.ENOENT, "File not found", parent)
stream = DummyPathIO(self._files, path)
@@ -1483,7 +1467,7 @@ class DummyPath(PathBase):
return stream
def iterdir(self):
- path = str(self.resolve())
+ path = str(self).rstrip('/')
if path in self._files:
raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path)
elif path in self._directories:
@@ -1492,9 +1476,9 @@ class DummyPath(PathBase):
raise FileNotFoundError(errno.ENOENT, "File not found", path)
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
- path = str(self.parent.resolve() / self.name)
- parent = str(self.parent.resolve())
- if path in self._directories or path in self._symlinks:
+ path = str(self)
+ parent = str(self.parent)
+ if path in self._directories:
if exist_ok:
return
else:
@@ -1510,33 +1494,28 @@ class DummyPath(PathBase):
self.mkdir(mode, parents=False, exist_ok=exist_ok)
def unlink(self, missing_ok=False):
- path_obj = self.parent.resolve(strict=True) / self.name
- path = str(path_obj)
- name = path_obj.name
- parent = str(path_obj.parent)
+ path = str(self)
+ name = self.name
+ parent = str(self.parent)
if path in self._directories:
raise IsADirectoryError(errno.EISDIR, "Is a directory", path)
elif path in self._files:
self._directories[parent].remove(name)
del self._files[path]
- elif path in self._symlinks:
- self._directories[parent].remove(name)
- del self._symlinks[path]
elif not missing_ok:
raise FileNotFoundError(errno.ENOENT, "File not found", path)
def rmdir(self):
- path_obj = self.parent.resolve(strict=True) / self.name
- path = str(path_obj)
- if path in self._files or path in self._symlinks:
+ path = str(self)
+ if path in self._files:
raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path)
elif path not in self._directories:
raise FileNotFoundError(errno.ENOENT, "File not found", path)
elif self._directories[path]:
raise OSError(errno.ENOTEMPTY, "Directory not empty", path)
else:
- name = path_obj.name
- parent = str(path_obj.parent)
+ name = self.name
+ parent = str(self.parent)
self._directories[parent].remove(name)
del self._directories[path]
@@ -1569,9 +1548,6 @@ class DummyPathTest(DummyPurePathTest):
def setUp(self):
super().setUp()
- name = self.id().split('.')[-1]
- if name in _tests_needing_symlinks and not self.can_symlink:
- self.skipTest('requires symlinks')
parser = self.cls.parser
p = self.cls(self.base)
p.mkdir(parents=True)
@@ -1604,7 +1580,6 @@ class DummyPathTest(DummyPurePathTest):
cls = self.cls
cls._files.clear()
cls._directories.clear()
- cls._symlinks.clear()
def tempdir(self):
path = self.cls(self.base).with_name('tmp-dirD')
@@ -1730,101 +1705,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertTrue(target.exists())
self.assertEqual(source.read_text(), target.read_text())
- @needs_symlinks
- def test_copy_symlink_follow_symlinks_true(self):
- base = self.cls(self.base)
- source = base / 'linkA'
- target = base / 'copyA'
- result = source.copy(target)
- self.assertEqual(result, target)
- self.assertTrue(target.exists())
- self.assertFalse(target.is_symlink())
- self.assertEqual(source.read_text(), target.read_text())
-
- @needs_symlinks
- def test_copy_symlink_follow_symlinks_false(self):
- base = self.cls(self.base)
- source = base / 'linkA'
- target = base / 'copyA'
- result = source.copy(target, follow_symlinks=False)
- self.assertEqual(result, target)
- self.assertTrue(target.exists())
- self.assertTrue(target.is_symlink())
- self.assertEqual(source.readlink(), target.readlink())
-
- @needs_symlinks
- def test_copy_symlink_to_itself(self):
- base = self.cls(self.base)
- source = base / 'linkA'
- self.assertRaises(OSError, source.copy, source)
-
- @needs_symlinks
- def test_copy_symlink_to_existing_symlink(self):
- base = self.cls(self.base)
- source = base / 'copySource'
- target = base / 'copyTarget'
- source.symlink_to(base / 'fileA')
- target.symlink_to(base / 'dirC')
- self.assertRaises(OSError, source.copy, target)
- self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
-
- @needs_symlinks
- def test_copy_symlink_to_existing_directory_symlink(self):
- base = self.cls(self.base)
- source = base / 'copySource'
- target = base / 'copyTarget'
- source.symlink_to(base / 'fileA')
- target.symlink_to(base / 'dirC')
- self.assertRaises(OSError, source.copy, target)
- self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
-
- @needs_symlinks
- def test_copy_directory_symlink_follow_symlinks_false(self):
- base = self.cls(self.base)
- source = base / 'linkB'
- target = base / 'copyA'
- result = source.copy(target, follow_symlinks=False)
- self.assertEqual(result, target)
- self.assertTrue(target.exists())
- self.assertTrue(target.is_symlink())
- self.assertEqual(source.readlink(), target.readlink())
-
- @needs_symlinks
- def test_copy_directory_symlink_to_itself(self):
- base = self.cls(self.base)
- source = base / 'linkB'
- self.assertRaises(OSError, source.copy, source)
- self.assertRaises(OSError, source.copy, source, follow_symlinks=False)
-
- @needs_symlinks
- def test_copy_directory_symlink_into_itself(self):
- base = self.cls(self.base)
- source = base / 'linkB'
- target = base / 'linkB' / 'copyB'
- self.assertRaises(OSError, source.copy, target)
- self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
- self.assertFalse(target.exists())
-
- @needs_symlinks
- def test_copy_directory_symlink_to_existing_symlink(self):
- base = self.cls(self.base)
- source = base / 'copySource'
- target = base / 'copyTarget'
- source.symlink_to(base / 'dirC')
- target.symlink_to(base / 'fileA')
- self.assertRaises(FileExistsError, source.copy, target)
- self.assertRaises(FileExistsError, source.copy, target, follow_symlinks=False)
-
- @needs_symlinks
- def test_copy_directory_symlink_to_existing_directory_symlink(self):
- base = self.cls(self.base)
- source = base / 'copySource'
- target = base / 'copyTarget'
- source.symlink_to(base / 'dirC' / 'dirD')
- target.symlink_to(base / 'dirC')
- self.assertRaises(FileExistsError, source.copy, target)
- self.assertRaises(FileExistsError, source.copy, target, follow_symlinks=False)
-
def test_copy_file_to_existing_file(self):
base = self.cls(self.base)
source = base / 'fileA'
@@ -1840,34 +1720,6 @@ class DummyPathTest(DummyPurePathTest):
target = base / 'dirA'
self.assertRaises(OSError, source.copy, target)
- @needs_symlinks
- def test_copy_file_to_existing_symlink(self):
- base = self.cls(self.base)
- source = base / 'dirB' / 'fileB'
- target = base / 'linkA'
- real_target = base / 'fileA'
- result = source.copy(target)
- self.assertEqual(result, target)
- self.assertTrue(target.exists())
- self.assertTrue(target.is_symlink())
- self.assertTrue(real_target.exists())
- self.assertFalse(real_target.is_symlink())
- self.assertEqual(source.read_text(), real_target.read_text())
-
- @needs_symlinks
- def test_copy_file_to_existing_symlink_follow_symlinks_false(self):
- base = self.cls(self.base)
- source = base / 'dirB' / 'fileB'
- target = base / 'linkA'
- real_target = base / 'fileA'
- result = source.copy(target, follow_symlinks=False)
- self.assertEqual(result, target)
- self.assertTrue(target.exists())
- self.assertTrue(target.is_symlink())
- self.assertTrue(real_target.exists())
- self.assertFalse(real_target.is_symlink())
- self.assertEqual(source.read_text(), real_target.read_text())
-
def test_copy_file_empty(self):
base = self.cls(self.base)
source = base / 'empty'
@@ -1985,23 +1837,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
self.assertFalse(target.exists())
- @needs_symlinks
- def test_copy_dangling_symlink(self):
- base = self.cls(self.base)
- source = base / 'source'
- target = base / 'target'
-
- source.mkdir()
- source.joinpath('link').symlink_to('nonexistent')
-
- self.assertRaises(FileNotFoundError, source.copy, target)
-
- target2 = base / 'target2'
- result = source.copy(target2, follow_symlinks=False)
- self.assertEqual(result, target2)
- self.assertTrue(target2.joinpath('link').is_symlink())
- self.assertEqual(target2.joinpath('link').readlink(), self.cls('nonexistent'))
-
def test_copy_into(self):
base = self.cls(self.base)
source = base / 'fileA'
@@ -2087,54 +1922,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertTrue(source.exists())
self.assertFalse(target.exists())
- @needs_symlinks
- def test_move_file_symlink(self):
- base = self.cls(self.base)
- source = base / 'linkA'
- source_readlink = source.readlink()
- target = base / 'linkA_moved'
- result = source.move(target)
- self.assertEqual(result, target)
- self.assertFalse(source.exists())
- self.assertTrue(target.is_symlink())
- self.assertEqual(source_readlink, target.readlink())
-
- @needs_symlinks
- def test_move_file_symlink_to_itself(self):
- base = self.cls(self.base)
- source = base / 'linkA'
- self.assertRaises(OSError, source.move, source)
-
- @needs_symlinks
- def test_move_dir_symlink(self):
- base = self.cls(self.base)
- source = base / 'linkB'
- source_readlink = source.readlink()
- target = base / 'linkB_moved'
- result = source.move(target)
- self.assertEqual(result, target)
- self.assertFalse(source.exists())
- self.assertTrue(target.is_symlink())
- self.assertEqual(source_readlink, target.readlink())
-
- @needs_symlinks
- def test_move_dir_symlink_to_itself(self):
- base = self.cls(self.base)
- source = base / 'linkB'
- self.assertRaises(OSError, source.move, source)
-
- @needs_symlinks
- def test_move_dangling_symlink(self):
- base = self.cls(self.base)
- source = base / 'brokenLink'
- source_readlink = source.readlink()
- target = base / 'brokenLink_moved'
- result = source.move(target)
- self.assertEqual(result, target)
- self.assertFalse(source.exists())
- self.assertTrue(target.is_symlink())
- self.assertEqual(source_readlink, target.readlink())
-
def test_move_into(self):
base = self.cls(self.base)
source = base / 'fileA'
@@ -2161,15 +1948,6 @@ class DummyPathTest(DummyPurePathTest):
expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
self.assertEqual(paths, { P(self.base, q) for q in expected })
- @needs_symlinks
- def test_iterdir_symlink(self):
- # __iter__ on a symlink to a directory.
- P = self.cls
- p = P(self.base, 'linkB')
- paths = set(p.iterdir())
- expected = { P(self.base, 'linkB', q) for q in ['fileB', 'linkD'] }
- self.assertEqual(paths, expected)
-
def test_iterdir_nodir(self):
# __iter__ on something that is not a directory.
p = self.cls(self.base, 'fileA')
@@ -2196,7 +1974,6 @@ class DummyPathTest(DummyPurePathTest):
if entry.name != 'brokenLinkLoop':
self.assertEqual(entry.is_dir(), child.is_dir())
-
def test_glob_common(self):
def _check(glob, expected):
self.assertEqual(set(glob), { P(self.base, q) for q in expected })
@@ -2250,8 +2027,6 @@ class DummyPathTest(DummyPurePathTest):
P = self.cls
p = P(self.base)
self.assertEqual(list(p.glob("")), [p])
- self.assertEqual(list(p.glob(".")), [p / "."])
- self.assertEqual(list(p.glob("./")), [p / "./"])
def test_glob_case_sensitive(self):
P = self.cls
@@ -2265,44 +2040,6 @@ class DummyPathTest(DummyPurePathTest):
_check(path, "dirb/file*", True, [])
_check(path, "dirb/file*", False, ["dirB/fileB"])
- @needs_symlinks
- @unittest.skipIf(is_emscripten, "Hangs")
- def test_glob_recurse_symlinks_common(self):
- def _check(path, glob, expected):
- actual = {path for path in path.glob(glob, recurse_symlinks=True)
- if path.parts.count("linkD") <= 1} # exclude symlink loop.
- self.assertEqual(actual, { P(self.base, q) for q in expected })
- P = self.cls
- p = P(self.base)
- _check(p, "fileB", [])
- _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
- _check(p, "*A", ["dirA", "fileA", "linkA"])
- _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"])
- _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
- _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
- _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."])
- _check(p, "dir*/**", [
- "dirA/", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB",
- "dirB/", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB",
- "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
- "dirE/"])
- _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
- "dirC/", "dirC/dirD/", "dirE/"])
- _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
- "dirB/linkD/..", "dirA/linkC/linkD/..",
- "dirC/..", "dirC/dirD/..", "dirE/.."])
- _check(p, "dir*/*/**", [
- "dirA/linkC/", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB",
- "dirB/linkD/", "dirB/linkD/fileB",
- "dirC/dirD/", "dirC/dirD/fileD"])
- _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
- _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..",
- "dirB/linkD/..", "dirC/dirD/.."])
- _check(p, "dir*/**/fileC", ["dirC/fileC"])
- _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
- _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"])
- _check(p, "*/dirD/**/", ["dirC/dirD/"])
-
def test_rglob_recurse_symlinks_false(self):
def _check(path, glob, expected):
actual = set(path.rglob(glob, recurse_symlinks=False))
@@ -2361,252 +2098,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertEqual(set(p.rglob("FILEd")), { P(self.base, "dirC/dirD/fileD") })
self.assertEqual(set(p.rglob("*\\")), { P(self.base, "dirC/dirD/") })
- @needs_symlinks
- @unittest.skipIf(is_emscripten, "Hangs")
- def test_rglob_recurse_symlinks_common(self):
- def _check(path, glob, expected):
- actual = {path for path in path.rglob(glob, recurse_symlinks=True)
- if path.parts.count("linkD") <= 1} # exclude symlink loop.
- self.assertEqual(actual, { P(self.base, q) for q in expected })
- P = self.cls
- p = P(self.base)
- _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB",
- "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB"])
- _check(p, "*/fileA", [])
- _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB",
- "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB"])
- _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB",
- "dirA/linkC/linkD/fileB", "dirB/linkD/fileB", "linkB/linkD/fileB",
- "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"])
- _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
- "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"])
- _check(p, "", ["", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
- "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"])
-
- p = P(self.base, "dirC")
- _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
- "dirC/dirD", "dirC/dirD/fileD"])
- _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
- _check(p, "*/*", ["dirC/dirD/fileD"])
- _check(p, "*/", ["dirC/dirD/"])
- _check(p, "", ["dirC/", "dirC/dirD/"])
- # gh-91616, a re module regression
- _check(p, "*.txt", ["dirC/novel.txt"])
- _check(p, "*.*", ["dirC/novel.txt"])
-
- @needs_symlinks
- def test_rglob_symlink_loop(self):
- # Don't get fooled by symlink loops (Issue #26012).
- P = self.cls
- p = P(self.base)
- given = set(p.rglob('*', recurse_symlinks=False))
- expect = {'brokenLink',
- 'dirA', 'dirA/linkC',
- 'dirB', 'dirB/fileB', 'dirB/linkD',
- 'dirC', 'dirC/dirD', 'dirC/dirD/fileD',
- 'dirC/fileC', 'dirC/novel.txt',
- 'dirE',
- 'fileA',
- 'linkA',
- 'linkB',
- 'brokenLinkLoop',
- }
- self.assertEqual(given, {p / x for x in expect})
-
- # See https://github.com/WebAssembly/wasi-filesystem/issues/26
- @unittest.skipIf(is_wasi, "WASI resolution of '..' parts doesn't match POSIX")
- def test_glob_dotdot(self):
- # ".." is not special in globs.
- P = self.cls
- p = P(self.base)
- self.assertEqual(set(p.glob("..")), { P(self.base, "..") })
- self.assertEqual(set(p.glob("../..")), { P(self.base, "..", "..") })
- self.assertEqual(set(p.glob("dirA/..")), { P(self.base, "dirA", "..") })
- self.assertEqual(set(p.glob("dirA/../file*")), { P(self.base, "dirA/../fileA") })
- self.assertEqual(set(p.glob("dirA/../file*/..")), set())
- self.assertEqual(set(p.glob("../xyzzy")), set())
- if self.cls.parser is posixpath:
- self.assertEqual(set(p.glob("xyzzy/..")), set())
- else:
- # ".." segments are normalized first on Windows, so this path is stat()able.
- self.assertEqual(set(p.glob("xyzzy/..")), { P(self.base, "xyzzy", "..") })
- self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)})
-
- @needs_symlinks
- def test_glob_permissions(self):
- # See bpo-38894
- P = self.cls
- base = P(self.base) / 'permissions'
- base.mkdir()
-
- for i in range(100):
- link = base / f"link{i}"
- if i % 2:
- link.symlink_to(P(self.base, "dirE", "nonexistent"))
- else:
- link.symlink_to(P(self.base, "dirC"), target_is_directory=True)
-
- self.assertEqual(len(set(base.glob("*"))), 100)
- self.assertEqual(len(set(base.glob("*/"))), 50)
- self.assertEqual(len(set(base.glob("*/fileC"))), 50)
- self.assertEqual(len(set(base.glob("*/file*"))), 50)
-
- @needs_symlinks
- def test_glob_long_symlink(self):
- # See gh-87695
- base = self.cls(self.base) / 'long_symlink'
- base.mkdir()
- bad_link = base / 'bad_link'
- bad_link.symlink_to("bad" * 200)
- self.assertEqual(sorted(base.glob('**/*')), [bad_link])
-
- @needs_posix
- def test_absolute_posix(self):
- P = self.cls
- # The default implementation uses '/' as the current directory
- self.assertEqual(str(P('').absolute()), '/')
- self.assertEqual(str(P('a').absolute()), '/a')
- self.assertEqual(str(P('a/b').absolute()), '/a/b')
-
- self.assertEqual(str(P('/').absolute()), '/')
- self.assertEqual(str(P('/a').absolute()), '/a')
- self.assertEqual(str(P('/a/b').absolute()), '/a/b')
-
- # '//'-prefixed absolute path (supported by POSIX).
- self.assertEqual(str(P('//').absolute()), '//')
- self.assertEqual(str(P('//a').absolute()), '//a')
- self.assertEqual(str(P('//a/b').absolute()), '//a/b')
-
- @needs_symlinks
- def test_readlink(self):
- P = self.cls(self.base)
- self.assertEqual((P / 'linkA').readlink(), self.cls('fileA'))
- self.assertEqual((P / 'brokenLink').readlink(),
- self.cls('non-existing'))
- self.assertEqual((P / 'linkB').readlink(), self.cls('dirB'))
- self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB'))
- with self.assertRaises(OSError):
- (P / 'fileA').readlink()
-
- @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present")
- def test_readlink_unsupported(self):
- P = self.cls(self.base)
- p = P / 'fileA'
- with self.assertRaises(UnsupportedOperation):
- q.readlink(p)
-
- def _check_resolve(self, p, expected, strict=True):
- q = p.resolve(strict)
- self.assertEqual(q, expected)
-
- # This can be used to check both relative and absolute resolutions.
- _check_resolve_relative = _check_resolve_absolute = _check_resolve
-
- @needs_symlinks
- def test_resolve_common(self):
- P = self.cls
- p = P(self.base, 'foo')
- with self.assertRaises(OSError) as cm:
- p.resolve(strict=True)
- self.assertEqual(cm.exception.errno, errno.ENOENT)
- # Non-strict
- parser = self.parser
- self.assertEqualNormCase(str(p.resolve(strict=False)),
- parser.join(self.base, 'foo'))
- p = P(self.base, 'foo', 'in', 'spam')
- self.assertEqualNormCase(str(p.resolve(strict=False)),
- parser.join(self.base, 'foo', 'in', 'spam'))
- p = P(self.base, '..', 'foo', 'in', 'spam')
- self.assertEqualNormCase(str(p.resolve(strict=False)),
- parser.join(parser.dirname(self.base), 'foo', 'in', 'spam'))
- # These are all relative symlinks.
- p = P(self.base, 'dirB', 'fileB')
- self._check_resolve_relative(p, p)
- p = P(self.base, 'linkA')
- self._check_resolve_relative(p, P(self.base, 'fileA'))
- p = P(self.base, 'dirA', 'linkC', 'fileB')
- self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB'))
- p = P(self.base, 'dirB', 'linkD', 'fileB')
- self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB'))
- # Non-strict
- p = P(self.base, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam')
- self._check_resolve_relative(p, P(self.base, 'dirB', 'fileB', 'foo', 'in',
- 'spam'), False)
- p = P(self.base, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')
- if self.cls.parser is not posixpath:
- # In Windows, if linkY points to dirB, 'dirA\linkY\..'
- # resolves to 'dirA' without resolving linkY first.
- self._check_resolve_relative(p, P(self.base, 'dirA', 'foo', 'in',
- 'spam'), False)
- else:
- # In Posix, if linkY points to dirB, 'dirA/linkY/..'
- # resolves to 'dirB/..' first before resolving to parent of dirB.
- self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False)
- # Now create absolute symlinks.
- d = self.tempdir()
- P(self.base, 'dirA', 'linkX').symlink_to(d)
- P(self.base, str(d), 'linkY').symlink_to(self.parser.join(self.base, 'dirB'))
- p = P(self.base, 'dirA', 'linkX', 'linkY', 'fileB')
- self._check_resolve_absolute(p, P(self.base, 'dirB', 'fileB'))
- # Non-strict
- p = P(self.base, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam')
- self._check_resolve_relative(p, P(self.base, 'dirB', 'foo', 'in', 'spam'),
- False)
- p = P(self.base, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')
- if self.cls.parser is not posixpath:
- # In Windows, if linkY points to dirB, 'dirA\linkY\..'
- # resolves to 'dirA' without resolving linkY first.
- self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False)
- else:
- # In Posix, if linkY points to dirB, 'dirA/linkY/..'
- # resolves to 'dirB/..' first before resolving to parent of dirB.
- self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False)
-
- @needs_symlinks
- def test_resolve_dot(self):
- # See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/
- parser = self.parser
- p = self.cls(self.base)
- p.joinpath('0').symlink_to('.', target_is_directory=True)
- p.joinpath('1').symlink_to(parser.join('0', '0'), target_is_directory=True)
- p.joinpath('2').symlink_to(parser.join('1', '1'), target_is_directory=True)
- q = p / '2'
- self.assertEqual(q.resolve(strict=True), p)
- r = q / '3' / '4'
- self.assertRaises(FileNotFoundError, r.resolve, strict=True)
- # Non-strict
- self.assertEqual(r.resolve(strict=False), p / '3' / '4')
-
- def _check_symlink_loop(self, *args):
- path = self.cls(*args)
- with self.assertRaises(OSError) as cm:
- path.resolve(strict=True)
- self.assertEqual(cm.exception.errno, errno.ELOOP)
-
- @needs_posix
- @needs_symlinks
- def test_resolve_loop(self):
- # Loops with relative symlinks.
- self.cls(self.base, 'linkX').symlink_to('linkX/inside')
- self._check_symlink_loop(self.base, 'linkX')
- self.cls(self.base, 'linkY').symlink_to('linkY')
- self._check_symlink_loop(self.base, 'linkY')
- self.cls(self.base, 'linkZ').symlink_to('linkZ/../linkZ')
- self._check_symlink_loop(self.base, 'linkZ')
- # Non-strict
- p = self.cls(self.base, 'linkZ', 'foo')
- self.assertEqual(p.resolve(strict=False), p)
- # Loops with absolute symlinks.
- self.cls(self.base, 'linkU').symlink_to(self.parser.join(self.base, 'linkU/inside'))
- self._check_symlink_loop(self.base, 'linkU')
- self.cls(self.base, 'linkV').symlink_to(self.parser.join(self.base, 'linkV'))
- self._check_symlink_loop(self.base, 'linkV')
- self.cls(self.base, 'linkW').symlink_to(self.parser.join(self.base, 'linkW/../linkW'))
- self._check_symlink_loop(self.base, 'linkW')
- # Non-strict
- q = self.cls(self.base, 'linkW', 'foo')
- self.assertEqual(q.resolve(strict=False), q)
-
def test_stat(self):
statA = self.cls(self.base).joinpath('fileA').stat()
statB = self.cls(self.base).joinpath('dirB', 'fileB').stat()
@@ -2627,12 +2118,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertEqual(statA.st_dev, statC.st_dev)
# other attributes not used by pathlib.
- @needs_symlinks
- def test_stat_no_follow_symlinks(self):
- p = self.cls(self.base) / 'linkA'
- st = p.stat()
- self.assertNotEqual(st, p.stat(follow_symlinks=False))
-
def test_stat_no_follow_symlinks_nosymlink(self):
p = self.cls(self.base) / 'fileA'
st = p.stat()
@@ -2760,41 +2245,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertIs((P / 'fileA\udfff').is_char_device(), False)
self.assertIs((P / 'fileA\x00').is_char_device(), False)
- def _check_complex_symlinks(self, link0_target):
- # Test solving a non-looping chain of symlinks (issue #19887).
- parser = self.parser
- P = self.cls(self.base)
- P.joinpath('link1').symlink_to(parser.join('link0', 'link0'), target_is_directory=True)
- P.joinpath('link2').symlink_to(parser.join('link1', 'link1'), target_is_directory=True)
- P.joinpath('link3').symlink_to(parser.join('link2', 'link2'), target_is_directory=True)
- P.joinpath('link0').symlink_to(link0_target, target_is_directory=True)
-
- # Resolve absolute paths.
- p = (P / 'link0').resolve()
- self.assertEqual(p, P)
- self.assertEqualNormCase(str(p), self.base)
- p = (P / 'link1').resolve()
- self.assertEqual(p, P)
- self.assertEqualNormCase(str(p), self.base)
- p = (P / 'link2').resolve()
- self.assertEqual(p, P)
- self.assertEqualNormCase(str(p), self.base)
- p = (P / 'link3').resolve()
- self.assertEqual(p, P)
- self.assertEqualNormCase(str(p), self.base)
-
- @needs_symlinks
- def test_complex_symlinks_absolute(self):
- self._check_complex_symlinks(self.base)
-
- @needs_symlinks
- def test_complex_symlinks_relative(self):
- self._check_complex_symlinks('.')
-
- @needs_symlinks
- def test_complex_symlinks_relative_dot_dot(self):
- self._check_complex_symlinks(self.parser.join('dirA', '..'))
-
def test_unlink(self):
p = self.cls(self.base) / 'fileA'
p.unlink()
@@ -2838,41 +2288,6 @@ class DummyPathTest(DummyPurePathTest):
self.assertRaises(FileNotFoundError, base.joinpath('dirC', 'fileC').stat)
self.assertRaises(FileNotFoundError, base.joinpath('dirC', 'novel.txt').stat)
- @needs_symlinks
- def test_delete_symlink(self):
- tmp = self.cls(self.base, 'delete')
- tmp.mkdir()
- dir_ = tmp / 'dir'
- dir_.mkdir()
- link = tmp / 'link'
- link.symlink_to(dir_)
- link._delete()
- self.assertTrue(dir_.exists())
- self.assertFalse(link.exists(follow_symlinks=False))
-
- @needs_symlinks
- def test_delete_inner_symlink(self):
- tmp = self.cls(self.base, 'delete')
- tmp.mkdir()
- dir1 = tmp / 'dir1'
- dir2 = dir1 / 'dir2'
- dir3 = tmp / 'dir3'
- for d in dir1, dir2, dir3:
- d.mkdir()
- file1 = tmp / 'file1'
- file1.write_text('foo')
- link1 = dir1 / 'link1'
- link1.symlink_to(dir2)
- link2 = dir1 / 'link2'
- link2.symlink_to(dir3)
- link3 = dir1 / 'link3'
- link3.symlink_to(file1)
- # make sure symlinks are removed but not followed
- dir1._delete()
- self.assertFalse(dir1.exists())
- self.assertTrue(dir3.exists())
- self.assertTrue(file1.exists())
-
def test_delete_missing(self):
tmp = self.cls(self.base, 'delete')
tmp.mkdir()
@@ -2887,9 +2302,6 @@ class DummyPathWalkTest(unittest.TestCase):
can_symlink = False
def setUp(self):
- name = self.id().split('.')[-1]
- if name in _tests_needing_symlinks and not self.can_symlink:
- self.skipTest('requires symlinks')
# Build:
# TESTFN/
# TEST1/ a file kid and two directory kids
@@ -3002,70 +2414,6 @@ class DummyPathWalkTest(unittest.TestCase):
raise AssertionError(f"Unexpected path: {path}")
self.assertTrue(seen_testfn)
- @needs_symlinks
- def test_walk_follow_symlinks(self):
- walk_it = self.walk_path.walk(follow_symlinks=True)
- for root, dirs, files in walk_it:
- if root == self.link_path:
- self.assertEqual(dirs, [])
- self.assertEqual(files, ["tmp4"])
- break
- else:
- self.fail("Didn't follow symlink with follow_symlinks=True")
-
- @needs_symlinks
- def test_walk_symlink_location(self):
- # Tests whether symlinks end up in filenames or dirnames depending
- # on the `follow_symlinks` argument.
- walk_it = self.walk_path.walk(follow_symlinks=False)
- for root, dirs, files in walk_it:
- if root == self.sub2_path:
- self.assertIn("link", files)
- break
- else:
- self.fail("symlink not found")
-
- walk_it = self.walk_path.walk(follow_symlinks=True)
- for root, dirs, files in walk_it:
- if root == self.sub2_path:
- self.assertIn("link", dirs)
- break
- else:
- self.fail("symlink not found")
-
-
-class DummyPathWithSymlinks(DummyPath):
- __slots__ = ()
-
- # Reduce symlink traversal limit to make tests run faster.
- _max_symlinks = 20
-
- def readlink(self):
- path = str(self.parent.resolve() / self.name)
- if path in self._symlinks:
- return self.with_segments(self._symlinks[path][0])
- elif path in self._files or path in self._directories:
- raise OSError(errno.EINVAL, "Not a symlink", path)
- else:
- raise FileNotFoundError(errno.ENOENT, "File not found", path)
-
- def symlink_to(self, target, target_is_directory=False):
- path = str(self.parent.resolve() / self.name)
- parent = str(self.parent.resolve())
- if path in self._symlinks:
- raise FileExistsError(errno.EEXIST, "File exists", path)
- self._directories[parent].add(self.name)
- self._symlinks[path] = str(target), target_is_directory
-
-
-class DummyPathWithSymlinksTest(DummyPathTest):
- cls = DummyPathWithSymlinks
- can_symlink = True
-
-
-class DummyPathWithSymlinksWalkTest(DummyPathWalkTest):
- cls = DummyPathWithSymlinks
- can_symlink = True
if __name__ == "__main__":