Skip to content

Commit 7a9727e

Browse files
authored
pathlib tests: annotate tests needing symlinks with decorator (#114625)
Add `@needs_symlinks` decorator for tests that require symlink support in the path class. Also add `@needs_windows` and `@needs_posix` decorators for tests that require a specific a specific path flavour. These aren't much used yet, but will be later.
1 parent b5c7c84 commit 7a9727e

File tree

2 files changed

+55
-48
lines changed

2 files changed

+55
-48
lines changed

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,14 @@
1919
from test.support import os_helper
2020
from test.support.os_helper import TESTFN, FakePath
2121
from test.test_pathlib import test_pathlib_abc
22+
from test.test_pathlib.test_pathlib_abc import needs_posix, needs_windows, needs_symlinks
2223

2324
try:
2425
import grp, pwd
2526
except ImportError:
2627
grp = pwd = None
2728

2829

29-
only_nt = unittest.skipIf(os.name != 'nt',
30-
'test requires a Windows-compatible system')
31-
only_posix = unittest.skipIf(os.name == 'nt',
32-
'test requires a POSIX-compatible system')
33-
3430
root_in_posix = False
3531
if hasattr(os, 'geteuid'):
3632
root_in_posix = (os.geteuid() == 0)
@@ -1268,7 +1264,7 @@ def test_chmod(self):
12681264
self.assertEqual(p.stat().st_mode, new_mode)
12691265

12701266
# On Windows, os.chmod does not follow symlinks (issue #15411)
1271-
@only_posix
1267+
@needs_posix
12721268
@os_helper.skip_unless_working_chmod
12731269
def test_chmod_follow_symlinks_true(self):
12741270
p = self.cls(self.base) / 'linkA'
@@ -1537,7 +1533,7 @@ def test_mkdir_exist_ok_root(self):
15371533
self.cls('/').resolve().mkdir(exist_ok=True)
15381534
self.cls('/').resolve().mkdir(parents=True, exist_ok=True)
15391535

1540-
@only_nt # XXX: not sure how to test this on POSIX.
1536+
@needs_windows # XXX: not sure how to test this on POSIX.
15411537
def test_mkdir_with_unknown_drive(self):
15421538
for d in 'ZYXWVUTSRQPONMLKJIHGFEDCBA':
15431539
p = self.cls(d + ':\\')
@@ -1602,9 +1598,8 @@ def my_mkdir(path, mode=0o777):
16021598
self.assertNotIn(str(p12), concurrently_created)
16031599
self.assertTrue(p.exists())
16041600

1601+
@needs_symlinks
16051602
def test_symlink_to(self):
1606-
if not self.can_symlink:
1607-
self.skipTest("symlinks required")
16081603
P = self.cls(self.base)
16091604
target = P / 'fileA'
16101605
# Symlinking a path target.
@@ -1848,7 +1843,7 @@ def test_rglob_pathlike(self):
18481843
self.assertEqual(expect, set(p.rglob(FakePath(pattern))))
18491844

18501845

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

@@ -2024,7 +2019,7 @@ def test_from_uri_pathname2url(self):
20242019
self.assertEqual(P.from_uri('file:' + pathname2url('//foo/bar')), P('//foo/bar'))
20252020

20262021

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

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@
1111
from test.support.os_helper import TESTFN
1212

1313

14+
_tests_needing_posix = set()
15+
_tests_needing_windows = set()
16+
_tests_needing_symlinks = set()
17+
18+
19+
def needs_posix(fn):
20+
"""Decorator that marks a test as requiring a POSIX-flavoured path class."""
21+
_tests_needing_posix.add(fn.__name__)
22+
return fn
23+
24+
def needs_windows(fn):
25+
"""Decorator that marks a test as requiring a Windows-flavoured path class."""
26+
_tests_needing_windows.add(fn.__name__)
27+
return fn
28+
29+
def needs_symlinks(fn):
30+
"""Decorator that marks a test as requiring a path class that supports symlinks."""
31+
_tests_needing_symlinks.add(fn.__name__)
32+
return fn
33+
34+
1435
class UnsupportedOperationTest(unittest.TestCase):
1536
def test_is_notimplemented(self):
1637
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
@@ -115,6 +136,11 @@ class DummyPurePathTest(unittest.TestCase):
115136
base = f'/this/path/kills/fascists/{TESTFN}'
116137

117138
def setUp(self):
139+
name = self.id().split('.')[-1]
140+
if name in _tests_needing_posix and self.cls.pathmod is not posixpath:
141+
self.skipTest('requires POSIX-flavoured path class')
142+
if name in _tests_needing_windows and self.cls.pathmod is posixpath:
143+
self.skipTest('requires Windows-flavoured path class')
118144
p = self.cls('a')
119145
self.pathmod = p.pathmod
120146
self.sep = self.pathmod.sep
@@ -888,6 +914,9 @@ class DummyPathTest(DummyPurePathTest):
888914

889915
def setUp(self):
890916
super().setUp()
917+
name = self.id().split('.')[-1]
918+
if name in _tests_needing_symlinks and not self.can_symlink:
919+
self.skipTest('requires symlinks')
891920
pathmod = self.cls.pathmod
892921
p = self.cls(self.base)
893922
p.mkdir(parents=True)
@@ -1045,9 +1074,8 @@ def test_iterdir(self):
10451074
expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
10461075
self.assertEqual(paths, { P(self.base, q) for q in expected })
10471076

1077+
@needs_symlinks
10481078
def test_iterdir_symlink(self):
1049-
if not self.can_symlink:
1050-
self.skipTest("symlinks required")
10511079
# __iter__ on a symlink to a directory.
10521080
P = self.cls
10531081
p = P(self.base, 'linkB')
@@ -1116,9 +1144,8 @@ def _check(path, pattern, case_sensitive, expected):
11161144
_check(path, "dirb/file*", True, [])
11171145
_check(path, "dirb/file*", False, ["dirB/fileB"])
11181146

1147+
@needs_symlinks
11191148
def test_glob_follow_symlinks_common(self):
1120-
if not self.can_symlink:
1121-
self.skipTest("symlinks required")
11221149
def _check(path, glob, expected):
11231150
actual = {path for path in path.glob(glob, follow_symlinks=True)
11241151
if path.parts.count("linkD") <= 1} # exclude symlink loop.
@@ -1144,9 +1171,8 @@ def _check(path, glob, expected):
11441171
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
11451172
_check(p, "*/dirD/**/", ["dirC/dirD/"])
11461173

1174+
@needs_symlinks
11471175
def test_glob_no_follow_symlinks_common(self):
1148-
if not self.can_symlink:
1149-
self.skipTest("symlinks required")
11501176
def _check(path, glob, expected):
11511177
actual = {path for path in path.glob(glob, follow_symlinks=False)}
11521178
self.assertEqual(actual, { P(self.base, q) for q in expected })
@@ -1210,9 +1236,8 @@ def _check(glob, expected):
12101236
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
12111237
_check(p.rglob("*.*"), ["dirC/novel.txt"])
12121238

1239+
@needs_symlinks
12131240
def test_rglob_follow_symlinks_common(self):
1214-
if not self.can_symlink:
1215-
self.skipTest("symlinks required")
12161241
def _check(path, glob, expected):
12171242
actual = {path for path in path.rglob(glob, follow_symlinks=True)
12181243
if path.parts.count("linkD") <= 1} # exclude symlink loop.
@@ -1243,9 +1268,8 @@ def _check(path, glob, expected):
12431268
_check(p, "*.txt", ["dirC/novel.txt"])
12441269
_check(p, "*.*", ["dirC/novel.txt"])
12451270

1271+
@needs_symlinks
12461272
def test_rglob_no_follow_symlinks_common(self):
1247-
if not self.can_symlink:
1248-
self.skipTest("symlinks required")
12491273
def _check(path, glob, expected):
12501274
actual = {path for path in path.rglob(glob, follow_symlinks=False)}
12511275
self.assertEqual(actual, { P(self.base, q) for q in expected })
@@ -1269,10 +1293,9 @@ def _check(path, glob, expected):
12691293
_check(p, "*.txt", ["dirC/novel.txt"])
12701294
_check(p, "*.*", ["dirC/novel.txt"])
12711295

1296+
@needs_symlinks
12721297
def test_rglob_symlink_loop(self):
12731298
# Don't get fooled by symlink loops (Issue #26012).
1274-
if not self.can_symlink:
1275-
self.skipTest("symlinks required")
12761299
P = self.cls
12771300
p = P(self.base)
12781301
given = set(p.rglob('*'))
@@ -1302,10 +1325,9 @@ def test_glob_dotdot(self):
13021325
self.assertEqual(set(p.glob("xyzzy/..")), set())
13031326
self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(self.base, *[".."] * 50)})
13041327

1328+
@needs_symlinks
13051329
def test_glob_permissions(self):
13061330
# See bpo-38894
1307-
if not self.can_symlink:
1308-
self.skipTest("symlinks required")
13091331
P = self.cls
13101332
base = P(self.base) / 'permissions'
13111333
base.mkdir()
@@ -1322,19 +1344,17 @@ def test_glob_permissions(self):
13221344
self.assertEqual(len(set(base.glob("*/fileC"))), 50)
13231345
self.assertEqual(len(set(base.glob("*/file*"))), 50)
13241346

1347+
@needs_symlinks
13251348
def test_glob_long_symlink(self):
13261349
# See gh-87695
1327-
if not self.can_symlink:
1328-
self.skipTest("symlinks required")
13291350
base = self.cls(self.base) / 'long_symlink'
13301351
base.mkdir()
13311352
bad_link = base / 'bad_link'
13321353
bad_link.symlink_to("bad" * 200)
13331354
self.assertEqual(sorted(base.glob('**/*')), [bad_link])
13341355

1356+
@needs_symlinks
13351357
def test_readlink(self):
1336-
if not self.can_symlink:
1337-
self.skipTest("symlinks required")
13381358
P = self.cls(self.base)
13391359
self.assertEqual((P / 'linkA').readlink(), self.cls('fileA'))
13401360
self.assertEqual((P / 'brokenLink').readlink(),
@@ -1358,9 +1378,8 @@ def _check_resolve(self, p, expected, strict=True):
13581378
# This can be used to check both relative and absolute resolutions.
13591379
_check_resolve_relative = _check_resolve_absolute = _check_resolve
13601380

1381+
@needs_symlinks
13611382
def test_resolve_common(self):
1362-
if not self.can_symlink:
1363-
self.skipTest("symlinks required")
13641383
P = self.cls
13651384
p = P(self.base, 'foo')
13661385
with self.assertRaises(OSError) as cm:
@@ -1419,10 +1438,9 @@ def test_resolve_common(self):
14191438
# resolves to 'dirB/..' first before resolving to parent of dirB.
14201439
self._check_resolve_relative(p, P(self.base, 'foo', 'in', 'spam'), False)
14211440

1441+
@needs_symlinks
14221442
def test_resolve_dot(self):
14231443
# See http://web.archive.org/web/20200623062557/https://bitbucket.org/pitrou/pathlib/issues/9/
1424-
if not self.can_symlink:
1425-
self.skipTest("symlinks required")
14261444
pathmod = self.pathmod
14271445
p = self.cls(self.base)
14281446
p.joinpath('0').symlink_to('.', target_is_directory=True)
@@ -1441,11 +1459,9 @@ def _check_symlink_loop(self, *args):
14411459
path.resolve(strict=True)
14421460
self.assertEqual(cm.exception.errno, errno.ELOOP)
14431461

1462+
@needs_posix
1463+
@needs_symlinks
14441464
def test_resolve_loop(self):
1445-
if not self.can_symlink:
1446-
self.skipTest("symlinks required")
1447-
if self.cls.pathmod is not posixpath:
1448-
self.skipTest("symlink loops work differently with concrete Windows paths")
14491465
# Loops with relative symlinks.
14501466
self.cls(self.base, 'linkX').symlink_to('linkX/inside')
14511467
self._check_symlink_loop(self.base, 'linkX')
@@ -1487,9 +1503,8 @@ def test_stat(self):
14871503
self.assertEqual(statA.st_dev, statC.st_dev)
14881504
# other attributes not used by pathlib.
14891505

1506+
@needs_symlinks
14901507
def test_stat_no_follow_symlinks(self):
1491-
if not self.can_symlink:
1492-
self.skipTest("symlinks required")
14931508
p = self.cls(self.base) / 'linkA'
14941509
st = p.stat()
14951510
self.assertNotEqual(st, p.stat(follow_symlinks=False))
@@ -1499,9 +1514,8 @@ def test_stat_no_follow_symlinks_nosymlink(self):
14991514
st = p.stat()
15001515
self.assertEqual(st, p.stat(follow_symlinks=False))
15011516

1517+
@needs_symlinks
15021518
def test_lstat(self):
1503-
if not self.can_symlink:
1504-
self.skipTest("symlinks required")
15051519
p = self.cls(self.base)/ 'linkA'
15061520
st = p.stat()
15071521
self.assertNotEqual(st, p.lstat())
@@ -1634,9 +1648,6 @@ def test_is_char_device_false(self):
16341648
self.assertIs((P / 'fileA\x00').is_char_device(), False)
16351649

16361650
def _check_complex_symlinks(self, link0_target):
1637-
if not self.can_symlink:
1638-
self.skipTest("symlinks required")
1639-
16401651
# Test solving a non-looping chain of symlinks (issue #19887).
16411652
pathmod = self.pathmod
16421653
P = self.cls(self.base)
@@ -1682,12 +1693,15 @@ def _check_complex_symlinks(self, link0_target):
16821693
finally:
16831694
os.chdir(old_path)
16841695

1696+
@needs_symlinks
16851697
def test_complex_symlinks_absolute(self):
16861698
self._check_complex_symlinks(self.base)
16871699

1700+
@needs_symlinks
16881701
def test_complex_symlinks_relative(self):
16891702
self._check_complex_symlinks('.')
16901703

1704+
@needs_symlinks
16911705
def test_complex_symlinks_relative_dot_dot(self):
16921706
self._check_complex_symlinks(self.pathmod.join('dirA', '..'))
16931707

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

1820+
@needs_symlinks
18061821
def test_walk_follow_symlinks(self):
1807-
if not self.can_symlink:
1808-
self.skipTest("symlinks required")
18091822
self.setUpWalk()
18101823
walk_it = self.walk_path.walk(follow_symlinks=True)
18111824
for root, dirs, files in walk_it:
@@ -1816,9 +1829,8 @@ def test_walk_follow_symlinks(self):
18161829
else:
18171830
self.fail("Didn't follow symlink with follow_symlinks=True")
18181831

1832+
@needs_symlinks
18191833
def test_walk_symlink_location(self):
1820-
if not self.can_symlink:
1821-
self.skipTest("symlinks required")
18221834
self.setUpWalk()
18231835
# Tests whether symlinks end up in filenames or dirnames depending
18241836
# on the `follow_symlinks` argument.

0 commit comments

Comments
 (0)