Skip to content

Commit

Permalink
pythonGH-89812: Make symlink support configurable in pathlib tests.
Browse files Browse the repository at this point in the history
Adjust the pathlib tests to add a new `PathTest.can_symlink` class
attribute, which allows us to enable or disable symlink support in tests.
A (near-)future commit will add an `AbstractPath` class; its tests will
hard-code the value to `True` or `False` depending on a stub subclass's
capabilities.
  • Loading branch information
barneygale committed Jun 24, 2023
1 parent 4a6c84f commit 35e7b04
Showing 1 changed file with 50 additions and 34 deletions.
84 changes: 50 additions & 34 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,7 @@ class PathTest(unittest.TestCase):
"""Tests for the FS-accessing functionalities of the Path classes."""

cls = pathlib.Path
can_symlink = os_helper.can_symlink()

# (BASE)
# |
Expand Down Expand Up @@ -1616,7 +1617,7 @@ def cleanup():
with open(join('dirC', 'dirD', 'fileD'), 'wb') as f:
f.write(b"this is file D\n")
os.chmod(join('dirE'), 0)
if os_helper.can_symlink():
if self.can_symlink:
# Relative symlinks.
os.symlink('fileA', join('linkA'))
os.symlink('non-existing', join('brokenLink'))
Expand Down Expand Up @@ -1680,7 +1681,7 @@ def test_exists(self):
self.assertIs(True, (p / 'dirA').exists())
self.assertIs(True, (p / 'fileA').exists())
self.assertIs(False, (p / 'fileA' / 'bah').exists())
if os_helper.can_symlink():
if self.can_symlink:
self.assertIs(True, (p / 'linkA').exists())
self.assertIs(True, (p / 'linkB').exists())
self.assertIs(True, (p / 'linkB' / 'fileB').exists())
Expand Down Expand Up @@ -1747,12 +1748,13 @@ def test_iterdir(self):
it = p.iterdir()
paths = set(it)
expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA']
if os_helper.can_symlink():
if self.can_symlink:
expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
self.assertEqual(paths, { P(BASE, q) for q in expected })

@os_helper.skip_unless_symlink
def test_iterdir_symlink(self):
if not self.can_symlink:
self.skipTest("symlinks required")
# __iter__ on a symlink to a directory.
P = self.cls
p = P(BASE, 'linkB')
Expand Down Expand Up @@ -1780,23 +1782,23 @@ def _check(glob, expected):
_check(it, ["fileA"])
_check(p.glob("fileB"), [])
_check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"])
if not os_helper.can_symlink():
if not self.can_symlink:
_check(p.glob("*A"), ['dirA', 'fileA'])
else:
_check(p.glob("*A"), ['dirA', 'fileA', 'linkA'])
if not os_helper.can_symlink():
if not self.can_symlink:
_check(p.glob("*B/*"), ['dirB/fileB'])
else:
_check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD',
'linkB/fileB', 'linkB/linkD'])
if not os_helper.can_symlink():
if not self.can_symlink:
_check(p.glob("*/fileB"), ['dirB/fileB'])
else:
_check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
if os_helper.can_symlink():
if self.can_symlink:
_check(p.glob("brokenLink"), ['brokenLink'])

if not os_helper.can_symlink():
if not self.can_symlink:
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE"])
else:
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE", "linkB"])
Expand All @@ -1818,8 +1820,9 @@ def _check(path, pattern, case_sensitive, expected):
_check(path, "dirb/file*", True, [])
_check(path, "dirb/file*", False, ["dirB/fileB"])

@os_helper.skip_unless_symlink
def test_glob_follow_symlinks_common(self):
if not self.can_symlink:
self.skipTest("symlinks required")
def _check(path, glob, expected):
actual = {path for path in path.glob(glob, follow_symlinks=True)
if "linkD" not in path.parent.parts} # exclude symlink loop.
Expand All @@ -1843,8 +1846,9 @@ def _check(path, glob, expected):
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD"])
_check(p, "*/dirD/**/", ["dirC/dirD"])

@os_helper.skip_unless_symlink
def test_glob_no_follow_symlinks_common(self):
if not self.can_symlink:
self.skipTest("symlinks required")
def _check(path, glob, expected):
actual = {path for path in path.glob(glob, follow_symlinks=False)}
self.assertEqual(actual, { P(BASE, q) for q in expected })
Expand Down Expand Up @@ -1876,14 +1880,14 @@ def _check(glob, expected):
_check(p.rglob("fileB"), ["dirB/fileB"])
_check(p.rglob("**/fileB"), ["dirB/fileB"])
_check(p.rglob("*/fileA"), [])
if not os_helper.can_symlink():
if not self.can_symlink:
_check(p.rglob("*/fileB"), ["dirB/fileB"])
else:
_check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB",
"linkB/fileB", "dirA/linkC/fileB"])
_check(p.rglob("file*"), ["fileA", "dirB/fileB",
"dirC/fileC", "dirC/dirD/fileD"])
if not os_helper.can_symlink():
if not self.can_symlink:
_check(p.rglob("*/"), [
"dirA", "dirB", "dirC", "dirC/dirD", "dirE",
])
Expand All @@ -1908,8 +1912,9 @@ def _check(glob, expected):
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
_check(p.rglob("*.*"), ["dirC/novel.txt"])

@os_helper.skip_unless_symlink
def test_rglob_follow_symlinks_common(self):
if not self.can_symlink:
self.skipTest("symlinks required")
def _check(path, glob, expected):
actual = {path for path in path.rglob(glob, follow_symlinks=True)
if 'linkD' not in path.parent.parts} # exclude symlink loop.
Expand Down Expand Up @@ -1937,8 +1942,9 @@ def _check(path, glob, expected):
_check(p, "*.txt", ["dirC/novel.txt"])
_check(p, "*.*", ["dirC/novel.txt"])

@os_helper.skip_unless_symlink
def test_rglob_no_follow_symlinks_common(self):
if not self.can_symlink:
self.skipTest("symlinks required")
def _check(path, glob, expected):
actual = {path for path in path.rglob(glob, follow_symlinks=False)}
self.assertEqual(actual, { P(BASE, q) for q in expected })
Expand All @@ -1962,9 +1968,10 @@ def _check(path, glob, expected):
_check(p, "*.txt", ["dirC/novel.txt"])
_check(p, "*.*", ["dirC/novel.txt"])

@os_helper.skip_unless_symlink
def test_rglob_symlink_loop(self):
# Don't get fooled by symlink loops (Issue #26012).
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls
p = P(BASE)
given = set(p.rglob('*'))
Expand Down Expand Up @@ -2011,9 +2018,10 @@ def test_glob_dotdot(self):
self.assertEqual(set(p.glob("xyzzy/..")), set())
self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)})

@os_helper.skip_unless_symlink
def test_glob_permissions(self):
# See bpo-38894
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls
base = P(BASE) / 'permissions'
base.mkdir()
Expand All @@ -2031,9 +2039,10 @@ def test_glob_permissions(self):
self.assertEqual(len(set(base.glob("*/fileC"))), 50)
self.assertEqual(len(set(base.glob("*/file*"))), 50)

@os_helper.skip_unless_symlink
def test_glob_long_symlink(self):
# See gh-87695
if not self.can_symlink:
self.skipTest("symlinks required")
base = self.cls(BASE) / 'long_symlink'
base.mkdir()
bad_link = base / 'bad_link'
Expand All @@ -2051,8 +2060,9 @@ def test_glob_above_recursion_limit(self):
with set_recursion_limit(recursion_limit):
list(base.glob('**'))

@os_helper.skip_unless_symlink
def test_readlink(self):
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls(BASE)
self.assertEqual((P / 'linkA').readlink(), self.cls('fileA'))
self.assertEqual((P / 'brokenLink').readlink(),
Expand All @@ -2075,8 +2085,9 @@ def _check_resolve(self, p, expected, strict=True):
# This can be used to check both relative and absolute resolutions.
_check_resolve_relative = _check_resolve_absolute = _check_resolve

@os_helper.skip_unless_symlink
def test_resolve_common(self):
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls
p = P(BASE, 'foo')
with self.assertRaises(OSError) as cm:
Expand Down Expand Up @@ -2136,9 +2147,10 @@ def test_resolve_common(self):
# resolves to 'dirB/..' first before resolving to parent of dirB.
self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False)

@os_helper.skip_unless_symlink
def test_resolve_dot(self):
# See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/
if not self.can_symlink:
self.skipTest("symlinks required")
p = self.cls(BASE)
self.dirlink('.', join('0'))
self.dirlink(os.path.join('0', '0'), join('1'))
Expand All @@ -2160,8 +2172,9 @@ def test_stat(self):
self.addCleanup(p.chmod, st.st_mode)
self.assertNotEqual(p.stat(), st)

@os_helper.skip_unless_symlink
def test_stat_no_follow_symlinks(self):
if not self.can_symlink:
self.skipTest("symlinks required")
p = self.cls(BASE) / 'linkA'
st = p.stat()
self.assertNotEqual(st, p.stat(follow_symlinks=False))
Expand All @@ -2171,8 +2184,9 @@ def test_stat_no_follow_symlinks_nosymlink(self):
st = p.stat()
self.assertEqual(st, p.stat(follow_symlinks=False))

@os_helper.skip_unless_symlink
def test_lstat(self):
if not self.can_symlink:
self.skipTest("symlinks required")
p = self.cls(BASE)/ 'linkA'
st = p.stat()
self.assertNotEqual(st, p.lstat())
Expand All @@ -2188,7 +2202,7 @@ def test_is_dir(self):
self.assertFalse((P / 'fileA').is_dir())
self.assertFalse((P / 'non-existing').is_dir())
self.assertFalse((P / 'fileA' / 'bah').is_dir())
if os_helper.can_symlink():
if self.can_symlink:
self.assertFalse((P / 'linkA').is_dir())
self.assertTrue((P / 'linkB').is_dir())
self.assertFalse((P/ 'brokenLink').is_dir(), False)
Expand All @@ -2201,7 +2215,7 @@ def test_is_file(self):
self.assertFalse((P / 'dirA').is_file())
self.assertFalse((P / 'non-existing').is_file())
self.assertFalse((P / 'fileA' / 'bah').is_file())
if os_helper.can_symlink():
if self.can_symlink:
self.assertTrue((P / 'linkA').is_file())
self.assertFalse((P / 'linkB').is_file())
self.assertFalse((P/ 'brokenLink').is_file())
Expand All @@ -2219,7 +2233,7 @@ def test_is_mount(self):
self.assertFalse((P / 'non-existing').is_mount())
self.assertFalse((P / 'fileA' / 'bah').is_mount())
self.assertTrue(R.is_mount())
if os_helper.can_symlink():
if self.can_symlink:
self.assertFalse((P / 'linkA').is_mount())
self.assertIs((R / '\udfff').is_mount(), False)

Expand All @@ -2229,13 +2243,13 @@ def test_is_symlink(self):
self.assertFalse((P / 'dirA').is_symlink())
self.assertFalse((P / 'non-existing').is_symlink())
self.assertFalse((P / 'fileA' / 'bah').is_symlink())
if os_helper.can_symlink():
if self.can_symlink:
self.assertTrue((P / 'linkA').is_symlink())
self.assertTrue((P / 'linkB').is_symlink())
self.assertTrue((P/ 'brokenLink').is_symlink())
self.assertIs((P / 'fileA\udfff').is_file(), False)
self.assertIs((P / 'fileA\x00').is_file(), False)
if os_helper.can_symlink():
if self.can_symlink:
self.assertIs((P / 'linkA\udfff').is_file(), False)
self.assertIs((P / 'linkA\x00').is_file(), False)

Expand Down Expand Up @@ -2292,6 +2306,9 @@ def test_parts_interning(self):
self.assertIs(p.parts[2], q.parts[3])

def _check_complex_symlinks(self, link0_target):
if not self.can_symlink:
self.skipTest("symlinks required")

# Test solving a non-looping chain of symlinks (issue #19887).
P = self.cls(BASE)
self.dirlink(os.path.join('link0', 'link0'), join('link1'))
Expand Down Expand Up @@ -2332,15 +2349,12 @@ def _check_complex_symlinks(self, link0_target):
finally:
os.chdir(old_path)

@os_helper.skip_unless_symlink
def test_complex_symlinks_absolute(self):
self._check_complex_symlinks(BASE)

@os_helper.skip_unless_symlink
def test_complex_symlinks_relative(self):
self._check_complex_symlinks('.')

@os_helper.skip_unless_symlink
def test_complex_symlinks_relative_dot_dot(self):
self._check_complex_symlinks(os.path.join('dirA', '..'))

Expand Down Expand Up @@ -2444,7 +2458,7 @@ def with_segments(self, *pathsegments):
self.assertEqual(42, p.with_segments('~').expanduser().session_id)
self.assertEqual(42, (p / 'fileA').rename(p / 'fileB').session_id)
self.assertEqual(42, (p / 'fileB').replace(p / 'fileA').session_id)
if os_helper.can_symlink():
if self.can_symlink:
self.assertEqual(42, (p / 'linkA').readlink().session_id)
for path in p.iterdir():
self.assertEqual(42, path.session_id)
Expand Down Expand Up @@ -2765,8 +2779,9 @@ def my_mkdir(path, mode=0o777):
self.assertNotIn(str(p12), concurrently_created)
self.assertTrue(p.exists())

@os_helper.skip_unless_symlink
def test_symlink_to(self):
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls(BASE)
target = P / 'fileA'
# Symlinking a path target.
Expand Down Expand Up @@ -3149,8 +3164,9 @@ def test_touch_mode(self):
st = os.stat(join('masked_new_file'))
self.assertEqual(stat.S_IMODE(st.st_mode), 0o750)

@os_helper.skip_unless_symlink
def test_resolve_loop(self):
if not self.can_symlink:
self.skipTest("symlinks required")
# Loops with relative symlinks.
os.symlink('linkX/inside', join('linkX'))
self._check_symlink_loop(BASE, 'linkX')
Expand Down

0 comments on commit 35e7b04

Please sign in to comment.