Skip to content

pathlib tests: annotate tests needing symlinks with decorator #114625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@
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

try:
import grp, pwd
except ImportError:
grp = pwd = None


only_nt = unittest.skipIf(os.name != 'nt',
'test requires a Windows-compatible system')
only_posix = unittest.skipIf(os.name == 'nt',
'test requires a POSIX-compatible system')

root_in_posix = False
if hasattr(os, 'geteuid'):
root_in_posix = (os.geteuid() == 0)
Expand Down Expand Up @@ -1304,7 +1300,7 @@ def test_chmod(self):
self.assertEqual(p.stat().st_mode, new_mode)

# On Windows, os.chmod does not follow symlinks (issue #15411)
@only_posix
@needs_posix
@os_helper.skip_unless_working_chmod
def test_chmod_follow_symlinks_true(self):
p = self.cls(self.base) / 'linkA'
Expand Down Expand Up @@ -1573,7 +1569,7 @@ def test_mkdir_exist_ok_root(self):
self.cls('/').resolve().mkdir(exist_ok=True)
self.cls('/').resolve().mkdir(parents=True, exist_ok=True)

@only_nt # XXX: not sure how to test this on POSIX.
@needs_windows # XXX: not sure how to test this on POSIX.
def test_mkdir_with_unknown_drive(self):
for d in 'ZYXWVUTSRQPONMLKJIHGFEDCBA':
p = self.cls(d + ':\\')
Expand Down Expand Up @@ -1638,9 +1634,8 @@ def my_mkdir(path, mode=0o777):
self.assertNotIn(str(p12), concurrently_created)
self.assertTrue(p.exists())

@needs_symlinks
def test_symlink_to(self):
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls(self.base)
target = P / 'fileA'
# Symlinking a path target.
Expand Down Expand Up @@ -1884,7 +1879,7 @@ def test_rglob_pathlike(self):
self.assertEqual(expect, set(p.rglob(FakePath(pattern))))


@only_posix
@unittest.skipIf(os.name == 'nt', 'test requires a POSIX-compatible system')
class PosixPathTest(PathTest, PurePosixPathTest):
cls = pathlib.PosixPath

Expand Down Expand Up @@ -2060,7 +2055,7 @@ def test_from_uri_pathname2url(self):
self.assertEqual(P.from_uri('file:' + pathname2url('//foo/bar')), P('//foo/bar'))


@only_nt
@unittest.skipIf(os.name != 'nt', 'test requires a Windows-compatible system')
class WindowsPathTest(PathTest, PureWindowsPathTest):
cls = pathlib.WindowsPath

Expand Down
86 changes: 49 additions & 37 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@
from test.support.os_helper import TESTFN


_tests_needing_posix = set()
_tests_needing_windows = set()
_tests_needing_symlinks = set()


def needs_posix(fn):
"""Decorator that marks a test as requiring a POSIX-flavoured path class."""
_tests_needing_posix.add(fn.__name__)
return fn

def needs_windows(fn):
"""Decorator that marks a test as requiring a Windows-flavoured path class."""
_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):
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
Expand Down Expand Up @@ -115,6 +136,11 @@ class DummyPurePathTest(unittest.TestCase):
base = f'/this/path/kills/fascists/{TESTFN}'

def setUp(self):
name = self.id().split('.')[-1]
if name in _tests_needing_posix and self.cls.pathmod is not posixpath:
self.skipTest('requires POSIX-flavoured path class')
if name in _tests_needing_windows and self.cls.pathmod is posixpath:
self.skipTest('requires Windows-flavoured path class')
p = self.cls('a')
self.pathmod = p.pathmod
self.sep = self.pathmod.sep
Expand Down Expand Up @@ -888,6 +914,9 @@ 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')
pathmod = self.cls.pathmod
p = self.cls(self.base)
p.mkdir(parents=True)
Expand Down Expand Up @@ -1045,9 +1074,8 @@ def test_iterdir(self):
expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
self.assertEqual(paths, { P(self.base, q) for q in expected })

@needs_symlinks
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(self.base, 'linkB')
Expand Down Expand Up @@ -1116,9 +1144,8 @@ def _check(path, pattern, case_sensitive, expected):
_check(path, "dirb/file*", True, [])
_check(path, "dirb/file*", False, ["dirB/fileB"])

@needs_symlinks
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 path.parts.count("linkD") <= 1} # exclude symlink loop.
Expand All @@ -1144,9 +1171,8 @@ def _check(path, glob, expected):
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
_check(p, "*/dirD/**/", ["dirC/dirD/"])

@needs_symlinks
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(self.base, q) for q in expected })
Expand Down Expand Up @@ -1210,9 +1236,8 @@ def _check(glob, expected):
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
_check(p.rglob("*.*"), ["dirC/novel.txt"])

@needs_symlinks
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 path.parts.count("linkD") <= 1} # exclude symlink loop.
Expand Down Expand Up @@ -1243,9 +1268,8 @@ def _check(path, glob, expected):
_check(p, "*.txt", ["dirC/novel.txt"])
_check(p, "*.*", ["dirC/novel.txt"])

@needs_symlinks
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(self.base, q) for q in expected })
Expand All @@ -1269,10 +1293,9 @@ def _check(path, glob, expected):
_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).
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls
p = P(self.base)
given = set(p.rglob('*'))
Expand Down Expand Up @@ -1302,10 +1325,9 @@ def test_glob_dotdot(self):
self.assertEqual(set(p.glob("xyzzy/..")), set())
self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)})

@needs_symlinks
def test_glob_permissions(self):
# See bpo-38894
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls
base = P(self.base) / 'permissions'
base.mkdir()
Expand All @@ -1322,19 +1344,17 @@ def test_glob_permissions(self):
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
if not self.can_symlink:
self.skipTest("symlinks required")
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_symlinks
def test_readlink(self):
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls(self.base)
self.assertEqual((P / 'linkA').readlink(), self.cls('fileA'))
self.assertEqual((P / 'brokenLink').readlink(),
Expand All @@ -1358,9 +1378,8 @@ 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

@needs_symlinks
def test_resolve_common(self):
if not self.can_symlink:
self.skipTest("symlinks required")
P = self.cls
p = P(self.base, 'foo')
with self.assertRaises(OSError) as cm:
Expand Down Expand Up @@ -1419,10 +1438,9 @@ def test_resolve_common(self):
# 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/
if not self.can_symlink:
self.skipTest("symlinks required")
pathmod = self.pathmod
p = self.cls(self.base)
p.joinpath('0').symlink_to('.', target_is_directory=True)
Expand All @@ -1441,11 +1459,9 @@ def _check_symlink_loop(self, *args):
path.resolve(strict=True)
self.assertEqual(cm.exception.errno, errno.ELOOP)

@needs_posix
@needs_symlinks
def test_resolve_loop(self):
if not self.can_symlink:
self.skipTest("symlinks required")
if self.cls.pathmod is not posixpath:
self.skipTest("symlink loops work differently with concrete Windows paths")
# Loops with relative symlinks.
self.cls(self.base, 'linkX').symlink_to('linkX/inside')
self._check_symlink_loop(self.base, 'linkX')
Expand Down Expand Up @@ -1487,9 +1503,8 @@ def test_stat(self):
self.assertEqual(statA.st_dev, statC.st_dev)
# other attributes not used by pathlib.

@needs_symlinks
def test_stat_no_follow_symlinks(self):
if not self.can_symlink:
self.skipTest("symlinks required")
p = self.cls(self.base) / 'linkA'
st = p.stat()
self.assertNotEqual(st, p.stat(follow_symlinks=False))
Expand All @@ -1499,9 +1514,8 @@ def test_stat_no_follow_symlinks_nosymlink(self):
st = p.stat()
self.assertEqual(st, p.stat(follow_symlinks=False))

@needs_symlinks
def test_lstat(self):
if not self.can_symlink:
self.skipTest("symlinks required")
p = self.cls(self.base)/ 'linkA'
st = p.stat()
self.assertNotEqual(st, p.lstat())
Expand Down Expand Up @@ -1634,9 +1648,6 @@ def test_is_char_device_false(self):
self.assertIs((P / 'fileA\x00').is_char_device(), False)

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).
pathmod = self.pathmod
P = self.cls(self.base)
Expand Down Expand Up @@ -1682,12 +1693,15 @@ def _check_complex_symlinks(self, link0_target):
finally:
os.chdir(old_path)

@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.pathmod.join('dirA', '..'))

Expand Down Expand Up @@ -1803,9 +1817,8 @@ def test_walk_bottom_up(self):
raise AssertionError(f"Unexpected path: {path}")
self.assertTrue(seen_testfn)

@needs_symlinks
def test_walk_follow_symlinks(self):
if not self.can_symlink:
self.skipTest("symlinks required")
self.setUpWalk()
walk_it = self.walk_path.walk(follow_symlinks=True)
for root, dirs, files in walk_it:
Expand All @@ -1816,9 +1829,8 @@ def test_walk_follow_symlinks(self):
else:
self.fail("Didn't follow symlink with follow_symlinks=True")

@needs_symlinks
def test_walk_symlink_location(self):
if not self.can_symlink:
self.skipTest("symlinks required")
self.setUpWalk()
# Tests whether symlinks end up in filenames or dirnames depending
# on the `follow_symlinks` argument.
Expand Down