From b3dba18eab96dc95653031863bb2a222af912f2b Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Mon, 8 Jan 2024 19:31:52 +0000 Subject: GH-113528: Speed up pathlib ABC tests. (#113788) - Add `__slots__` to dummy path classes. - Return namedtuple rather than `os.stat_result` from `DummyPath.stat()`. - Reduce maximum symlink count in `DummyPathWithSymlinks.resolve()`. --- Lib/pathlib/_abc.py | 8 ++++---- Lib/test/test_pathlib/test_pathlib_abc.py | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 97663b9..be22ece 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -10,9 +10,6 @@ from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO # Internals # -# Maximum number of symlinks to follow in PathBase.resolve() -_MAX_SYMLINKS = 40 - # Reference for Windows paths can be found at # https://learn.microsoft.com/en-gb/windows/win32/fileio/naming-a-file . _WIN_RESERVED_NAMES = frozenset( @@ -500,6 +497,9 @@ class PathBase(PurePathBase): """ __slots__ = () + # Maximum number of symlinks to follow in resolve() + _max_symlinks = 40 + @classmethod def _unsupported(cls, method_name): msg = f"{cls.__name__}.{method_name}() is unsupported" @@ -971,7 +971,7 @@ class PathBase(PurePathBase): # Like Linux and macOS, raise OSError(errno.ELOOP) if too many symlinks are # encountered during resolution. link_count += 1 - if link_count >= _MAX_SYMLINKS: + if link_count >= self._max_symlinks: raise OSError(ELOOP, "Too many symbolic links in path", str(self)) target, target_parts = next_path.readlink()._split_stack() # If the symlink target is absolute (like '/etc/hosts'), set the current diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index f33d497..b088be8 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -1,4 +1,4 @@ -import collections.abc +import collections import io import os import errno @@ -43,6 +43,8 @@ class PurePathBaseTest(unittest.TestCase): class DummyPurePath(PurePathBase): + __slots__ = () + def __eq__(self, other): if not isinstance(other, DummyPurePath): return NotImplemented @@ -660,11 +662,18 @@ class DummyPathIO(io.BytesIO): super().close() +DummyPathStatResult = collections.namedtuple( + 'DummyPathStatResult', + 'st_mode st_ino st_dev st_nlink st_uid st_gid st_size st_atime st_mtime st_ctime') + + class DummyPath(PathBase): """ Simple implementation of PathBase that keeps files and directories in memory. """ + __slots__ = () + _files = {} _directories = {} _symlinks = {} @@ -693,7 +702,7 @@ class DummyPath(PathBase): st_mode = stat.S_IFLNK else: raise FileNotFoundError(errno.ENOENT, "Not found", str(self)) - return os.stat_result((st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0)) + return DummyPathStatResult(st_mode, hash(str(self)), 0, 0, 0, 0, 0, 0, 0, 0) def open(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None): @@ -1728,6 +1737,11 @@ class DummyPathTest(DummyPurePathTest): 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: -- cgit v0.12