Skip to content

Commit fbdd075

Browse files
[3.6] bpo-32964: Reuse a testing implementation of the path protocol in tests. (GH-5930). (GH-5958)
(cherry picked from commit b21d155) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 10fb1bf commit fbdd075

File tree

11 files changed

+76
-95
lines changed

11 files changed

+76
-95
lines changed

Doc/library/test.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,3 +677,10 @@ The :mod:`test.support` module defines the following classes:
677677

678678
Class used to record warnings for unit tests. See documentation of
679679
:func:`check_warnings` above for more details.
680+
681+
682+
.. class:: FakePath(path)
683+
684+
Simple :term:`path-like object`. It implements the :meth:`__fspath__`
685+
method which just returns the *path* argument. If *path* is an exception,
686+
it will be raised in :meth:`!__fspath__`.

Lib/test/support/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,3 +2708,21 @@ def save(self):
27082708
def restore(self):
27092709
for signum, handler in self.handlers.items():
27102710
self.signal.signal(signum, handler)
2711+
2712+
2713+
class FakePath:
2714+
"""Simple implementing of the path protocol.
2715+
"""
2716+
def __init__(self, path):
2717+
self.path = path
2718+
2719+
def __repr__(self):
2720+
return f'<FakePath {self.path!r}>'
2721+
2722+
def __fspath__(self):
2723+
if (isinstance(self.path, BaseException) or
2724+
isinstance(self.path, type) and
2725+
issubclass(self.path, BaseException)):
2726+
raise self.path
2727+
else:
2728+
return self.path

Lib/test/test_compile.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import tempfile
77
import types
88
from test import support
9-
from test.support import script_helper
9+
from test.support import script_helper, FakePath
1010

1111
class TestSpecifics(unittest.TestCase):
1212

@@ -670,13 +670,7 @@ def check_different_constants(const1, const2):
670670

671671
def test_path_like_objects(self):
672672
# An implicit test for PyUnicode_FSDecoder().
673-
class PathLike:
674-
def __init__(self, path):
675-
self._path = path
676-
def __fspath__(self):
677-
return self._path
678-
679-
compile("42", PathLike("test_compile_pathlike"), "single")
673+
compile("42", FakePath("test_compile_pathlike"), "single")
680674

681675

682676
class TestStackSize(unittest.TestCase):

Lib/test/test_genericpath.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import warnings
1010
from test import support
1111
from test.support.script_helper import assert_python_ok
12+
from test.support import FakePath
1213

1314

1415
def create_file(filename, data=b'foo'):
@@ -493,18 +494,9 @@ def test_import(self):
493494

494495
class PathLikeTests(unittest.TestCase):
495496

496-
class PathLike:
497-
def __init__(self, path=''):
498-
self.path = path
499-
def __fspath__(self):
500-
if isinstance(self.path, BaseException):
501-
raise self.path
502-
else:
503-
return self.path
504-
505497
def setUp(self):
506498
self.file_name = support.TESTFN.lower()
507-
self.file_path = self.PathLike(support.TESTFN)
499+
self.file_path = FakePath(support.TESTFN)
508500
self.addCleanup(support.unlink, self.file_name)
509501
create_file(self.file_name, b"test_genericpath.PathLikeTests")
510502

Lib/test/test_io.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from itertools import cycle, count
3737
from test import support
3838
from test.support.script_helper import assert_python_ok, run_python_until_end
39+
from test.support import FakePath
3940

4041
import codecs
4142
import io # C implementation of io
@@ -880,30 +881,32 @@ def read(self, size):
880881
self.assertEqual(bytes(buffer), b"12345")
881882

882883
def test_fspath_support(self):
883-
class PathLike:
884-
def __init__(self, path):
885-
self.path = path
886-
887-
def __fspath__(self):
888-
return self.path
889-
890884
def check_path_succeeds(path):
891885
with self.open(path, "w") as f:
892886
f.write("egg\n")
893887

894888
with self.open(path, "r") as f:
895889
self.assertEqual(f.read(), "egg\n")
896890

897-
check_path_succeeds(PathLike(support.TESTFN))
898-
check_path_succeeds(PathLike(support.TESTFN.encode('utf-8')))
891+
check_path_succeeds(FakePath(support.TESTFN))
892+
check_path_succeeds(FakePath(support.TESTFN.encode('utf-8')))
893+
894+
with self.open(support.TESTFN, "w") as f:
895+
bad_path = FakePath(f.fileno())
896+
with self.assertRaises(TypeError):
897+
self.open(bad_path, 'w')
899898

900-
bad_path = PathLike(TypeError)
899+
bad_path = FakePath(None)
901900
with self.assertRaises(TypeError):
902901
self.open(bad_path, 'w')
903902

903+
bad_path = FakePath(FloatingPointError)
904+
with self.assertRaises(FloatingPointError):
905+
self.open(bad_path, 'w')
906+
904907
# ensure that refcounting is correct with some error conditions
905908
with self.assertRaisesRegex(ValueError, 'read/write/append mode'):
906-
self.open(PathLike(support.TESTFN), 'rwxa')
909+
self.open(FakePath(support.TESTFN), 'rwxa')
907910

908911
def test_RawIOBase_readall(self):
909912
# Exercise the default unlimited RawIOBase.read() and readall()

Lib/test/test_ntpath.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import unittest
55
import warnings
6-
from test.support import TestFailed
6+
from test.support import TestFailed, FakePath
77
from test import support, test_genericpath
88
from tempfile import TemporaryFile
99

@@ -456,18 +456,9 @@ class PathLikeTests(unittest.TestCase):
456456

457457
path = ntpath
458458

459-
class PathLike:
460-
def __init__(self, path=''):
461-
self.path = path
462-
def __fspath__(self):
463-
if isinstance(self.path, BaseException):
464-
raise self.path
465-
else:
466-
return self.path
467-
468459
def setUp(self):
469460
self.file_name = support.TESTFN.lower()
470-
self.file_path = self.PathLike(support.TESTFN)
461+
self.file_path = FakePath(support.TESTFN)
471462
self.addCleanup(support.unlink, self.file_name)
472463
with open(self.file_name, 'xb', 0) as file:
473464
file.write(b"test_ntpath.PathLikeTests")
@@ -482,7 +473,7 @@ def test_path_isabs(self):
482473
self.assertPathEqual(self.path.isabs)
483474

484475
def test_path_join(self):
485-
self.assertEqual(self.path.join('a', self.PathLike('b'), 'c'),
476+
self.assertEqual(self.path.join('a', FakePath('b'), 'c'),
486477
self.path.join('a', 'b', 'c'))
487478

488479
def test_path_split(self):

Lib/test/test_os.py

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
INT_MAX = PY_SSIZE_T_MAX = sys.maxsize
6565

6666
from test.support.script_helper import assert_python_ok
67-
from test.support import unix_shell
67+
from test.support import unix_shell, FakePath
6868

6969

7070
root_in_posix = False
@@ -94,21 +94,6 @@ def requires_os_func(name):
9494
return unittest.skipUnless(hasattr(os, name), 'requires os.%s' % name)
9595

9696

97-
class _PathLike(os.PathLike):
98-
99-
def __init__(self, path=""):
100-
self.path = path
101-
102-
def __str__(self):
103-
return str(self.path)
104-
105-
def __fspath__(self):
106-
if isinstance(self.path, BaseException):
107-
raise self.path
108-
else:
109-
return self.path
110-
111-
11297
def create_file(filename, content=b'content'):
11398
with open(filename, "xb", 0) as fp:
11499
fp.write(content)
@@ -970,15 +955,14 @@ def test_walk_prune(self, walk_path=None):
970955
dirs.remove('SUB1')
971956

972957
self.assertEqual(len(all), 2)
973-
self.assertEqual(all[0],
974-
(str(walk_path), ["SUB2"], ["tmp1"]))
958+
self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"]))
975959

976960
all[1][-1].sort()
977961
all[1][1].sort()
978962
self.assertEqual(all[1], self.sub2_tree)
979963

980964
def test_file_like_path(self):
981-
self.test_walk_prune(_PathLike(self.walk_path))
965+
self.test_walk_prune(FakePath(self.walk_path))
982966

983967
def test_walk_bottom_up(self):
984968
# Walk bottom-up.
@@ -2294,7 +2278,7 @@ def test_getppid(self):
22942278
def test_waitpid(self):
22952279
args = [sys.executable, '-c', 'pass']
22962280
# Add an implicit test for PyUnicode_FSConverter().
2297-
pid = os.spawnv(os.P_NOWAIT, _PathLike(args[0]), args)
2281+
pid = os.spawnv(os.P_NOWAIT, FakePath(args[0]), args)
22982282
status = os.waitpid(pid, 0)
22992283
self.assertEqual(status, (pid, 0))
23002284

@@ -3140,13 +3124,13 @@ def test_path_t_converter(self):
31403124
bytes_fspath = bytes_filename = None
31413125
else:
31423126
bytes_filename = support.TESTFN.encode('ascii')
3143-
bytes_fspath = _PathLike(bytes_filename)
3144-
fd = os.open(_PathLike(str_filename), os.O_WRONLY|os.O_CREAT)
3127+
bytes_fspath = FakePath(bytes_filename)
3128+
fd = os.open(FakePath(str_filename), os.O_WRONLY|os.O_CREAT)
31453129
self.addCleanup(support.unlink, support.TESTFN)
31463130
self.addCleanup(os.close, fd)
31473131

3148-
int_fspath = _PathLike(fd)
3149-
str_fspath = _PathLike(str_filename)
3132+
int_fspath = FakePath(fd)
3133+
str_fspath = FakePath(str_filename)
31503134

31513135
for name, allow_fd, extra_args, cleanup_fn in self.functions:
31523136
with self.subTest(name=name):
@@ -3519,16 +3503,16 @@ def test_return_string(self):
35193503

35203504
def test_fsencode_fsdecode(self):
35213505
for p in "path/like/object", b"path/like/object":
3522-
pathlike = _PathLike(p)
3506+
pathlike = FakePath(p)
35233507

35243508
self.assertEqual(p, self.fspath(pathlike))
35253509
self.assertEqual(b"path/like/object", os.fsencode(pathlike))
35263510
self.assertEqual("path/like/object", os.fsdecode(pathlike))
35273511

35283512
def test_pathlike(self):
3529-
self.assertEqual('#feelthegil', self.fspath(_PathLike('#feelthegil')))
3530-
self.assertTrue(issubclass(_PathLike, os.PathLike))
3531-
self.assertTrue(isinstance(_PathLike(), os.PathLike))
3513+
self.assertEqual('#feelthegil', self.fspath(FakePath('#feelthegil')))
3514+
self.assertTrue(issubclass(FakePath, os.PathLike))
3515+
self.assertTrue(isinstance(FakePath('x'), os.PathLike))
35323516

35333517
def test_garbage_in_exception_out(self):
35343518
vapor = type('blah', (), {})
@@ -3540,14 +3524,14 @@ def test_argument_required(self):
35403524

35413525
def test_bad_pathlike(self):
35423526
# __fspath__ returns a value other than str or bytes.
3543-
self.assertRaises(TypeError, self.fspath, _PathLike(42))
3527+
self.assertRaises(TypeError, self.fspath, FakePath(42))
35443528
# __fspath__ attribute that is not callable.
35453529
c = type('foo', (), {})
35463530
c.__fspath__ = 1
35473531
self.assertRaises(TypeError, self.fspath, c())
35483532
# __fspath__ raises an exception.
35493533
self.assertRaises(ZeroDivisionError, self.fspath,
3550-
_PathLike(ZeroDivisionError()))
3534+
FakePath(ZeroDivisionError()))
35513535

35523536
# Only test if the C version is provided, otherwise TestPEP519 already tested
35533537
# the pure Python implementation.

Lib/test/test_pathlib.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from unittest import mock
1212

1313
from test import support
14-
TESTFN = support.TESTFN
14+
from test.support import TESTFN, FakePath
1515

1616
try:
1717
import grp, pwd
@@ -191,18 +191,15 @@ def test_constructor_common(self):
191191
P = self.cls
192192
p = P('a')
193193
self.assertIsInstance(p, P)
194-
class PathLike:
195-
def __fspath__(self):
196-
return "a/b/c"
197194
P('a', 'b', 'c')
198195
P('/a', 'b', 'c')
199196
P('a/b/c')
200197
P('/a/b/c')
201-
P(PathLike())
198+
P(FakePath("a/b/c"))
202199
self.assertEqual(P(P('a')), P('a'))
203200
self.assertEqual(P(P('a'), 'b'), P('a/b'))
204201
self.assertEqual(P(P('a'), P('b')), P('a/b'))
205-
self.assertEqual(P(P('a'), P('b'), P('c')), P(PathLike()))
202+
self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c")))
206203

207204
def _check_str_subclass(self, *args):
208205
# Issue #21127: it should be possible to construct a PurePath object

Lib/test/test_posixpath.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import warnings
55
from posixpath import realpath, abspath, dirname, basename
66
from test import support, test_genericpath
7+
from test.support import FakePath
78

89
try:
910
import posix
@@ -600,18 +601,9 @@ class PathLikeTests(unittest.TestCase):
600601

601602
path = posixpath
602603

603-
class PathLike:
604-
def __init__(self, path=''):
605-
self.path = path
606-
def __fspath__(self):
607-
if isinstance(self.path, BaseException):
608-
raise self.path
609-
else:
610-
return self.path
611-
612604
def setUp(self):
613605
self.file_name = support.TESTFN.lower()
614-
self.file_path = self.PathLike(support.TESTFN)
606+
self.file_path = FakePath(support.TESTFN)
615607
self.addCleanup(support.unlink, self.file_name)
616608
with open(self.file_name, 'xb', 0) as file:
617609
file.write(b"test_posixpath.PathLikeTests")
@@ -626,7 +618,7 @@ def test_path_isabs(self):
626618
self.assertPathEqual(self.path.isabs)
627619

628620
def test_path_join(self):
629-
self.assertEqual(self.path.join('a', self.PathLike('b'), 'c'),
621+
self.assertEqual(self.path.join('a', FakePath('b'), 'c'),
630622
self.path.join('a', 'b', 'c'))
631623

632624
def test_path_split(self):

Lib/test/test_shutil.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import os.path
1111
import errno
1212
import functools
13+
import pathlib
1314
import subprocess
1415
from contextlib import ExitStack
1516
from shutil import (make_archive,
@@ -21,9 +22,10 @@
2122
import tarfile
2223
import zipfile
2324
import warnings
25+
import pathlib
2426

2527
from test import support
26-
from test.support import TESTFN, check_warnings, captured_stdout
28+
from test.support import TESTFN, FakePath
2729

2830
TESTFN2 = TESTFN + "2"
2931

@@ -1231,6 +1233,11 @@ def test_register_archive_format(self):
12311233
self.assertNotIn('xxx', formats)
12321234

12331235
def check_unpack_archive(self, format):
1236+
self.check_unpack_archive_with_converter(format, lambda path: path)
1237+
self.check_unpack_archive_with_converter(format, pathlib.Path)
1238+
self.check_unpack_archive_with_converter(format, FakePath)
1239+
1240+
def check_unpack_archive_with_converter(self, format, converter):
12341241
root_dir, base_dir = self._create_files()
12351242
expected = rlistdir(root_dir)
12361243
expected.remove('outer')

0 commit comments

Comments
 (0)