]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-110109: Move tests for pathlib ABCs to new module. (#112904)
authorBarney Gale <barney.gale@gmail.com>
Sat, 16 Dec 2023 19:04:33 +0000 (19:04 +0000)
committerGitHub <noreply@github.com>
Sat, 16 Dec 2023 19:04:33 +0000 (19:04 +0000)
Lib/test/test_pathlib/__init__.py [new file with mode: 0644]
Lib/test/test_pathlib/test_pathlib.py [moved from Lib/test/test_pathlib.py with 50% similarity]
Lib/test/test_pathlib/test_pathlib_abc.py [new file with mode: 0644]
Makefile.pre.in

diff --git a/Lib/test/test_pathlib/__init__.py b/Lib/test/test_pathlib/__init__.py
new file mode 100644 (file)
index 0000000..4b16ecc
--- /dev/null
@@ -0,0 +1,5 @@
+import os
+from test.support import load_package_tests
+
+def load_tests(*args):
+    return load_package_tests(os.path.dirname(__file__), *args)
similarity index 50%
rename from Lib/test/test_pathlib.py
rename to Lib/test/test_pathlib/test_pathlib.py
index a1acba406ea37c088f5718351f4a251fd8e0b25e..304786fc6f7fd957853ca91d3204a75609e9967d 100644 (file)
@@ -1,11 +1,9 @@
-import collections.abc
 import io
 import os
 import sys
 import errno
 import pathlib
 import pickle
-import posixpath
 import socket
 import stat
 import tempfile
@@ -14,10 +12,10 @@ from unittest import mock
 from urllib.request import pathname2url
 
 from test.support import import_helper
-from test.support import set_recursion_limit
 from test.support import is_emscripten, is_wasi
 from test.support import os_helper
 from test.support.os_helper import TESTFN, FakePath
+from test.test_pathlib import test_pathlib_abc
 
 try:
     import grp, pwd
@@ -25,12 +23,6 @@ except ImportError:
     grp = pwd = None
 
 
-class UnsupportedOperationTest(unittest.TestCase):
-    def test_is_notimplemented(self):
-        self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError))
-        self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))
-
-
 # Make sure any symbolic links in the base test path are resolved.
 BASE = os.path.realpath(TESTFN)
 join = lambda *x: os.path.join(BASE, *x)
@@ -49,654 +41,7 @@ if hasattr(os, 'geteuid'):
 # Tests for the pure classes.
 #
 
-
-class PurePathBaseTest(unittest.TestCase):
-    cls = pathlib._abc.PurePathBase
-
-    def test_magic_methods(self):
-        P = self.cls
-        self.assertFalse(hasattr(P, '__fspath__'))
-        self.assertFalse(hasattr(P, '__bytes__'))
-        self.assertIs(P.__reduce__, object.__reduce__)
-        self.assertIs(P.__hash__, object.__hash__)
-        self.assertIs(P.__eq__, object.__eq__)
-        self.assertIs(P.__lt__, object.__lt__)
-        self.assertIs(P.__le__, object.__le__)
-        self.assertIs(P.__gt__, object.__gt__)
-        self.assertIs(P.__ge__, object.__ge__)
-
-
-class DummyPurePath(pathlib._abc.PurePathBase):
-    def __eq__(self, other):
-        if not isinstance(other, DummyPurePath):
-            return NotImplemented
-        return str(self) == str(other)
-
-    def __hash__(self):
-        return hash(str(self))
-
-
-class DummyPurePathTest(unittest.TestCase):
-    cls = DummyPurePath
-
-    # Keys are canonical paths, values are list of tuples of arguments
-    # supposed to produce equal paths.
-    equivalences = {
-        'a/b': [
-            ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'),
-            ('a/b/',), ('a//b',), ('a//b//',),
-            # Empty components get removed.
-            ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''),
-            ],
-        '/b/c/d': [
-            ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'),
-            # Empty components get removed.
-            ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'),
-            ],
-    }
-
-    def setUp(self):
-        p = self.cls('a')
-        self.pathmod = p.pathmod
-        self.sep = self.pathmod.sep
-        self.altsep = self.pathmod.altsep
-
-    def test_constructor_common(self):
-        P = self.cls
-        p = P('a')
-        self.assertIsInstance(p, P)
-        P('a', 'b', 'c')
-        P('/a', 'b', 'c')
-        P('a/b/c')
-        P('/a/b/c')
-
-    def test_concrete_class(self):
-        if self.cls is pathlib.PurePath:
-            expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath
-        else:
-            expected = self.cls
-        p = self.cls('a')
-        self.assertIs(type(p), expected)
-
-    def test_different_pathmods_unequal(self):
-        p = self.cls('a')
-        if p.pathmod is posixpath:
-            q = pathlib.PureWindowsPath('a')
-        else:
-            q = pathlib.PurePosixPath('a')
-        self.assertNotEqual(p, q)
-
-    def test_different_pathmods_unordered(self):
-        p = self.cls('a')
-        if p.pathmod is posixpath:
-            q = pathlib.PureWindowsPath('a')
-        else:
-            q = pathlib.PurePosixPath('a')
-        with self.assertRaises(TypeError):
-            p < q
-        with self.assertRaises(TypeError):
-            p <= q
-        with self.assertRaises(TypeError):
-            p > q
-        with self.assertRaises(TypeError):
-            p >= q
-
-    def _check_str_subclass(self, *args):
-        # Issue #21127: it should be possible to construct a PurePath object
-        # from a str subclass instance, and it then gets converted to
-        # a pure str object.
-        class StrSubclass(str):
-            pass
-        P = self.cls
-        p = P(*(StrSubclass(x) for x in args))
-        self.assertEqual(p, P(*args))
-        for part in p.parts:
-            self.assertIs(type(part), str)
-
-    def test_str_subclass_common(self):
-        self._check_str_subclass('')
-        self._check_str_subclass('.')
-        self._check_str_subclass('a')
-        self._check_str_subclass('a/b.txt')
-        self._check_str_subclass('/a/b.txt')
-
-    def test_with_segments_common(self):
-        class P(self.cls):
-            def __init__(self, *pathsegments, session_id):
-                super().__init__(*pathsegments)
-                self.session_id = session_id
-
-            def with_segments(self, *pathsegments):
-                return type(self)(*pathsegments, session_id=self.session_id)
-        p = P('foo', 'bar', session_id=42)
-        self.assertEqual(42, (p / 'foo').session_id)
-        self.assertEqual(42, ('foo' / p).session_id)
-        self.assertEqual(42, p.joinpath('foo').session_id)
-        self.assertEqual(42, p.with_name('foo').session_id)
-        self.assertEqual(42, p.with_stem('foo').session_id)
-        self.assertEqual(42, p.with_suffix('.foo').session_id)
-        self.assertEqual(42, p.with_segments('foo').session_id)
-        self.assertEqual(42, p.relative_to('foo').session_id)
-        self.assertEqual(42, p.parent.session_id)
-        for parent in p.parents:
-            self.assertEqual(42, parent.session_id)
-
-    def _check_parse_path(self, raw_path, *expected):
-        sep = self.pathmod.sep
-        actual = self.cls._parse_path(raw_path.replace('/', sep))
-        self.assertEqual(actual, expected)
-        if altsep := self.pathmod.altsep:
-            actual = self.cls._parse_path(raw_path.replace('/', altsep))
-            self.assertEqual(actual, expected)
-
-    def test_parse_path_common(self):
-        check = self._check_parse_path
-        sep = self.pathmod.sep
-        check('',         '', '', [])
-        check('a',        '', '', ['a'])
-        check('a/',       '', '', ['a'])
-        check('a/b',      '', '', ['a', 'b'])
-        check('a/b/',     '', '', ['a', 'b'])
-        check('a/b/c/d',  '', '', ['a', 'b', 'c', 'd'])
-        check('a/b//c/d', '', '', ['a', 'b', 'c', 'd'])
-        check('a/b/c/d',  '', '', ['a', 'b', 'c', 'd'])
-        check('.',        '', '', [])
-        check('././b',    '', '', ['b'])
-        check('a/./b',    '', '', ['a', 'b'])
-        check('a/./.',    '', '', ['a'])
-        check('/a/b',     '', sep, ['a', 'b'])
-
-    def test_join_common(self):
-        P = self.cls
-        p = P('a/b')
-        pp = p.joinpath('c')
-        self.assertEqual(pp, P('a/b/c'))
-        self.assertIs(type(pp), type(p))
-        pp = p.joinpath('c', 'd')
-        self.assertEqual(pp, P('a/b/c/d'))
-        pp = p.joinpath('/c')
-        self.assertEqual(pp, P('/c'))
-
-    def test_div_common(self):
-        # Basically the same as joinpath().
-        P = self.cls
-        p = P('a/b')
-        pp = p / 'c'
-        self.assertEqual(pp, P('a/b/c'))
-        self.assertIs(type(pp), type(p))
-        pp = p / 'c/d'
-        self.assertEqual(pp, P('a/b/c/d'))
-        pp = p / 'c' / 'd'
-        self.assertEqual(pp, P('a/b/c/d'))
-        pp = 'c' / p / 'd'
-        self.assertEqual(pp, P('c/a/b/d'))
-        pp = p/ '/c'
-        self.assertEqual(pp, P('/c'))
-
-    def _check_str(self, expected, args):
-        p = self.cls(*args)
-        self.assertEqual(str(p), expected.replace('/', self.sep))
-
-    def test_str_common(self):
-        # Canonicalized paths roundtrip.
-        for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
-            self._check_str(pathstr, (pathstr,))
-        # Special case for the empty path.
-        self._check_str('.', ('',))
-        # Other tests for str() are in test_equivalences().
-
-    def test_as_posix_common(self):
-        P = self.cls
-        for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
-            self.assertEqual(P(pathstr).as_posix(), pathstr)
-        # Other tests for as_posix() are in test_equivalences().
-
-    def test_repr_common(self):
-        for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
-            with self.subTest(pathstr=pathstr):
-                p = self.cls(pathstr)
-                clsname = p.__class__.__name__
-                r = repr(p)
-                # The repr() is in the form ClassName("forward-slashes path").
-                self.assertTrue(r.startswith(clsname + '('), r)
-                self.assertTrue(r.endswith(')'), r)
-                inner = r[len(clsname) + 1 : -1]
-                self.assertEqual(eval(inner), p.as_posix())
-
-    def test_eq_common(self):
-        P = self.cls
-        self.assertEqual(P('a/b'), P('a/b'))
-        self.assertEqual(P('a/b'), P('a', 'b'))
-        self.assertNotEqual(P('a/b'), P('a'))
-        self.assertNotEqual(P('a/b'), P('/a/b'))
-        self.assertNotEqual(P('a/b'), P())
-        self.assertNotEqual(P('/a/b'), P('/'))
-        self.assertNotEqual(P(), P('/'))
-        self.assertNotEqual(P(), "")
-        self.assertNotEqual(P(), {})
-        self.assertNotEqual(P(), int)
-
-    def test_match_common(self):
-        P = self.cls
-        self.assertRaises(ValueError, P('a').match, '')
-        self.assertRaises(ValueError, P('a').match, '.')
-        # Simple relative pattern.
-        self.assertTrue(P('b.py').match('b.py'))
-        self.assertTrue(P('a/b.py').match('b.py'))
-        self.assertTrue(P('/a/b.py').match('b.py'))
-        self.assertFalse(P('a.py').match('b.py'))
-        self.assertFalse(P('b/py').match('b.py'))
-        self.assertFalse(P('/a.py').match('b.py'))
-        self.assertFalse(P('b.py/c').match('b.py'))
-        # Wildcard relative pattern.
-        self.assertTrue(P('b.py').match('*.py'))
-        self.assertTrue(P('a/b.py').match('*.py'))
-        self.assertTrue(P('/a/b.py').match('*.py'))
-        self.assertFalse(P('b.pyc').match('*.py'))
-        self.assertFalse(P('b./py').match('*.py'))
-        self.assertFalse(P('b.py/c').match('*.py'))
-        # Multi-part relative pattern.
-        self.assertTrue(P('ab/c.py').match('a*/*.py'))
-        self.assertTrue(P('/d/ab/c.py').match('a*/*.py'))
-        self.assertFalse(P('a.py').match('a*/*.py'))
-        self.assertFalse(P('/dab/c.py').match('a*/*.py'))
-        self.assertFalse(P('ab/c.py/d').match('a*/*.py'))
-        # Absolute pattern.
-        self.assertTrue(P('/b.py').match('/*.py'))
-        self.assertFalse(P('b.py').match('/*.py'))
-        self.assertFalse(P('a/b.py').match('/*.py'))
-        self.assertFalse(P('/a/b.py').match('/*.py'))
-        # Multi-part absolute pattern.
-        self.assertTrue(P('/a/b.py').match('/a/*.py'))
-        self.assertFalse(P('/ab.py').match('/a/*.py'))
-        self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
-        # Multi-part glob-style pattern.
-        self.assertTrue(P('a').match('**'))
-        self.assertTrue(P('c.py').match('**'))
-        self.assertTrue(P('a/b/c.py').match('**'))
-        self.assertTrue(P('/a/b/c.py').match('**'))
-        self.assertTrue(P('/a/b/c.py').match('/**'))
-        self.assertTrue(P('/a/b/c.py').match('**/'))
-        self.assertTrue(P('/a/b/c.py').match('/a/**'))
-        self.assertTrue(P('/a/b/c.py').match('**/*.py'))
-        self.assertTrue(P('/a/b/c.py').match('/**/*.py'))
-        self.assertTrue(P('/a/b/c.py').match('/a/**/*.py'))
-        self.assertTrue(P('/a/b/c.py').match('/a/b/**/*.py'))
-        self.assertTrue(P('/a/b/c.py').match('/**/**/**/**/*.py'))
-        self.assertFalse(P('c.py').match('**/a.py'))
-        self.assertFalse(P('c.py').match('c/**'))
-        self.assertFalse(P('a/b/c.py').match('**/a'))
-        self.assertFalse(P('a/b/c.py').match('**/a/b'))
-        self.assertFalse(P('a/b/c.py').match('**/a/b/c'))
-        self.assertFalse(P('a/b/c.py').match('**/a/b/c.'))
-        self.assertFalse(P('a/b/c.py').match('**/a/b/c./**'))
-        self.assertFalse(P('a/b/c.py').match('**/a/b/c./**'))
-        self.assertFalse(P('a/b/c.py').match('/a/b/c.py/**'))
-        self.assertFalse(P('a/b/c.py').match('/**/a/b/c.py'))
-        self.assertRaises(ValueError, P('a').match, '**a/b/c')
-        self.assertRaises(ValueError, P('a').match, 'a/b/c**')
-        # Case-sensitive flag
-        self.assertFalse(P('A.py').match('a.PY', case_sensitive=True))
-        self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
-        self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
-        self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
-        # Matching against empty path
-        self.assertFalse(P().match('*'))
-        self.assertTrue(P().match('**'))
-        self.assertFalse(P().match('**/*'))
-
-    def test_parts_common(self):
-        # `parts` returns a tuple.
-        sep = self.sep
-        P = self.cls
-        p = P('a/b')
-        parts = p.parts
-        self.assertEqual(parts, ('a', 'b'))
-        # When the path is absolute, the anchor is a separate part.
-        p = P('/a/b')
-        parts = p.parts
-        self.assertEqual(parts, (sep, 'a', 'b'))
-
-    def test_equivalences(self):
-        for k, tuples in self.equivalences.items():
-            canon = k.replace('/', self.sep)
-            posix = k.replace(self.sep, '/')
-            if canon != posix:
-                tuples = tuples + [
-                    tuple(part.replace('/', self.sep) for part in t)
-                    for t in tuples
-                    ]
-                tuples.append((posix, ))
-            pcanon = self.cls(canon)
-            for t in tuples:
-                p = self.cls(*t)
-                self.assertEqual(p, pcanon, "failed with args {}".format(t))
-                self.assertEqual(hash(p), hash(pcanon))
-                self.assertEqual(str(p), canon)
-                self.assertEqual(p.as_posix(), posix)
-
-    def test_parent_common(self):
-        # Relative
-        P = self.cls
-        p = P('a/b/c')
-        self.assertEqual(p.parent, P('a/b'))
-        self.assertEqual(p.parent.parent, P('a'))
-        self.assertEqual(p.parent.parent.parent, P())
-        self.assertEqual(p.parent.parent.parent.parent, P())
-        # Anchored
-        p = P('/a/b/c')
-        self.assertEqual(p.parent, P('/a/b'))
-        self.assertEqual(p.parent.parent, P('/a'))
-        self.assertEqual(p.parent.parent.parent, P('/'))
-        self.assertEqual(p.parent.parent.parent.parent, P('/'))
-
-    def test_parents_common(self):
-        # Relative
-        P = self.cls
-        p = P('a/b/c')
-        par = p.parents
-        self.assertEqual(len(par), 3)
-        self.assertEqual(par[0], P('a/b'))
-        self.assertEqual(par[1], P('a'))
-        self.assertEqual(par[2], P('.'))
-        self.assertEqual(par[-1], P('.'))
-        self.assertEqual(par[-2], P('a'))
-        self.assertEqual(par[-3], P('a/b'))
-        self.assertEqual(par[0:1], (P('a/b'),))
-        self.assertEqual(par[:2], (P('a/b'), P('a')))
-        self.assertEqual(par[:-1], (P('a/b'), P('a')))
-        self.assertEqual(par[1:], (P('a'), P('.')))
-        self.assertEqual(par[::2], (P('a/b'), P('.')))
-        self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b')))
-        self.assertEqual(list(par), [P('a/b'), P('a'), P('.')])
-        with self.assertRaises(IndexError):
-            par[-4]
-        with self.assertRaises(IndexError):
-            par[3]
-        with self.assertRaises(TypeError):
-            par[0] = p
-        # Anchored
-        p = P('/a/b/c')
-        par = p.parents
-        self.assertEqual(len(par), 3)
-        self.assertEqual(par[0], P('/a/b'))
-        self.assertEqual(par[1], P('/a'))
-        self.assertEqual(par[2], P('/'))
-        self.assertEqual(par[-1], P('/'))
-        self.assertEqual(par[-2], P('/a'))
-        self.assertEqual(par[-3], P('/a/b'))
-        self.assertEqual(par[0:1], (P('/a/b'),))
-        self.assertEqual(par[:2], (P('/a/b'), P('/a')))
-        self.assertEqual(par[:-1], (P('/a/b'), P('/a')))
-        self.assertEqual(par[1:], (P('/a'), P('/')))
-        self.assertEqual(par[::2], (P('/a/b'), P('/')))
-        self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b')))
-        self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')])
-        with self.assertRaises(IndexError):
-            par[-4]
-        with self.assertRaises(IndexError):
-            par[3]
-
-    def test_drive_common(self):
-        P = self.cls
-        self.assertEqual(P('a/b').drive, '')
-        self.assertEqual(P('/a/b').drive, '')
-        self.assertEqual(P('').drive, '')
-
-    def test_root_common(self):
-        P = self.cls
-        sep = self.sep
-        self.assertEqual(P('').root, '')
-        self.assertEqual(P('a/b').root, '')
-        self.assertEqual(P('/').root, sep)
-        self.assertEqual(P('/a/b').root, sep)
-
-    def test_anchor_common(self):
-        P = self.cls
-        sep = self.sep
-        self.assertEqual(P('').anchor, '')
-        self.assertEqual(P('a/b').anchor, '')
-        self.assertEqual(P('/').anchor, sep)
-        self.assertEqual(P('/a/b').anchor, sep)
-
-    def test_name_common(self):
-        P = self.cls
-        self.assertEqual(P('').name, '')
-        self.assertEqual(P('.').name, '')
-        self.assertEqual(P('/').name, '')
-        self.assertEqual(P('a/b').name, 'b')
-        self.assertEqual(P('/a/b').name, 'b')
-        self.assertEqual(P('/a/b/.').name, 'b')
-        self.assertEqual(P('a/b.py').name, 'b.py')
-        self.assertEqual(P('/a/b.py').name, 'b.py')
-
-    def test_suffix_common(self):
-        P = self.cls
-        self.assertEqual(P('').suffix, '')
-        self.assertEqual(P('.').suffix, '')
-        self.assertEqual(P('..').suffix, '')
-        self.assertEqual(P('/').suffix, '')
-        self.assertEqual(P('a/b').suffix, '')
-        self.assertEqual(P('/a/b').suffix, '')
-        self.assertEqual(P('/a/b/.').suffix, '')
-        self.assertEqual(P('a/b.py').suffix, '.py')
-        self.assertEqual(P('/a/b.py').suffix, '.py')
-        self.assertEqual(P('a/.hgrc').suffix, '')
-        self.assertEqual(P('/a/.hgrc').suffix, '')
-        self.assertEqual(P('a/.hg.rc').suffix, '.rc')
-        self.assertEqual(P('/a/.hg.rc').suffix, '.rc')
-        self.assertEqual(P('a/b.tar.gz').suffix, '.gz')
-        self.assertEqual(P('/a/b.tar.gz').suffix, '.gz')
-        self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '')
-        self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '')
-
-    def test_suffixes_common(self):
-        P = self.cls
-        self.assertEqual(P('').suffixes, [])
-        self.assertEqual(P('.').suffixes, [])
-        self.assertEqual(P('/').suffixes, [])
-        self.assertEqual(P('a/b').suffixes, [])
-        self.assertEqual(P('/a/b').suffixes, [])
-        self.assertEqual(P('/a/b/.').suffixes, [])
-        self.assertEqual(P('a/b.py').suffixes, ['.py'])
-        self.assertEqual(P('/a/b.py').suffixes, ['.py'])
-        self.assertEqual(P('a/.hgrc').suffixes, [])
-        self.assertEqual(P('/a/.hgrc').suffixes, [])
-        self.assertEqual(P('a/.hg.rc').suffixes, ['.rc'])
-        self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc'])
-        self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz'])
-        self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz'])
-        self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, [])
-        self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, [])
-
-    def test_stem_common(self):
-        P = self.cls
-        self.assertEqual(P('').stem, '')
-        self.assertEqual(P('.').stem, '')
-        self.assertEqual(P('..').stem, '..')
-        self.assertEqual(P('/').stem, '')
-        self.assertEqual(P('a/b').stem, 'b')
-        self.assertEqual(P('a/b.py').stem, 'b')
-        self.assertEqual(P('a/.hgrc').stem, '.hgrc')
-        self.assertEqual(P('a/.hg.rc').stem, '.hg')
-        self.assertEqual(P('a/b.tar.gz').stem, 'b.tar')
-        self.assertEqual(P('a/Some name. Ending with a dot.').stem,
-                         'Some name. Ending with a dot.')
-
-    def test_with_name_common(self):
-        P = self.cls
-        self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml'))
-        self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml'))
-        self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml'))
-        self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml'))
-        self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml'))
-        self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml'))
-        self.assertRaises(ValueError, P('').with_name, 'd.xml')
-        self.assertRaises(ValueError, P('.').with_name, 'd.xml')
-        self.assertRaises(ValueError, P('/').with_name, 'd.xml')
-        self.assertRaises(ValueError, P('a/b').with_name, '')
-        self.assertRaises(ValueError, P('a/b').with_name, '.')
-        self.assertRaises(ValueError, P('a/b').with_name, '/c')
-        self.assertRaises(ValueError, P('a/b').with_name, 'c/')
-        self.assertRaises(ValueError, P('a/b').with_name, 'c/d')
-
-    def test_with_stem_common(self):
-        P = self.cls
-        self.assertEqual(P('a/b').with_stem('d'), P('a/d'))
-        self.assertEqual(P('/a/b').with_stem('d'), P('/a/d'))
-        self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py'))
-        self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py'))
-        self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz'))
-        self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d'))
-        self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d'))
-        self.assertRaises(ValueError, P('').with_stem, 'd')
-        self.assertRaises(ValueError, P('.').with_stem, 'd')
-        self.assertRaises(ValueError, P('/').with_stem, 'd')
-        self.assertRaises(ValueError, P('a/b').with_stem, '')
-        self.assertRaises(ValueError, P('a/b').with_stem, '.')
-        self.assertRaises(ValueError, P('a/b').with_stem, '/c')
-        self.assertRaises(ValueError, P('a/b').with_stem, 'c/')
-        self.assertRaises(ValueError, P('a/b').with_stem, 'c/d')
-
-    def test_with_suffix_common(self):
-        P = self.cls
-        self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz'))
-        self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz'))
-        self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz'))
-        self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz'))
-        # Stripping suffix.
-        self.assertEqual(P('a/b.py').with_suffix(''), P('a/b'))
-        self.assertEqual(P('/a/b').with_suffix(''), P('/a/b'))
-        # Path doesn't have a "filename" component.
-        self.assertRaises(ValueError, P('').with_suffix, '.gz')
-        self.assertRaises(ValueError, P('.').with_suffix, '.gz')
-        self.assertRaises(ValueError, P('/').with_suffix, '.gz')
-        # Invalid suffix.
-        self.assertRaises(ValueError, P('a/b').with_suffix, 'gz')
-        self.assertRaises(ValueError, P('a/b').with_suffix, '/')
-        self.assertRaises(ValueError, P('a/b').with_suffix, '.')
-        self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz')
-        self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d')
-        self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d')
-        self.assertRaises(ValueError, P('a/b').with_suffix, './.d')
-        self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.')
-
-    def test_relative_to_common(self):
-        P = self.cls
-        p = P('a/b')
-        self.assertRaises(TypeError, p.relative_to)
-        self.assertRaises(TypeError, p.relative_to, b'a')
-        self.assertEqual(p.relative_to(P()), P('a/b'))
-        self.assertEqual(p.relative_to(''), P('a/b'))
-        self.assertEqual(p.relative_to(P('a')), P('b'))
-        self.assertEqual(p.relative_to('a'), P('b'))
-        self.assertEqual(p.relative_to('a/'), P('b'))
-        self.assertEqual(p.relative_to(P('a/b')), P())
-        self.assertEqual(p.relative_to('a/b'), P())
-        self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b'))
-        self.assertEqual(p.relative_to('', walk_up=True), P('a/b'))
-        self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b'))
-        self.assertEqual(p.relative_to('a', walk_up=True), P('b'))
-        self.assertEqual(p.relative_to('a/', walk_up=True), P('b'))
-        self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P())
-        self.assertEqual(p.relative_to('a/b', walk_up=True), P())
-        self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b'))
-        self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b'))
-        self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..'))
-        self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..'))
-        self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b'))
-        self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b'))
-        # With several args.
-        with self.assertWarns(DeprecationWarning):
-            p.relative_to('a', 'b')
-            p.relative_to('a', 'b', walk_up=True)
-        # Unrelated paths.
-        self.assertRaises(ValueError, p.relative_to, P('c'))
-        self.assertRaises(ValueError, p.relative_to, P('a/b/c'))
-        self.assertRaises(ValueError, p.relative_to, P('a/c'))
-        self.assertRaises(ValueError, p.relative_to, P('/a'))
-        self.assertRaises(ValueError, p.relative_to, P("../a"))
-        self.assertRaises(ValueError, p.relative_to, P("a/.."))
-        self.assertRaises(ValueError, p.relative_to, P("/a/.."))
-        self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True)
-        p = P('/a/b')
-        self.assertEqual(p.relative_to(P('/')), P('a/b'))
-        self.assertEqual(p.relative_to('/'), P('a/b'))
-        self.assertEqual(p.relative_to(P('/a')), P('b'))
-        self.assertEqual(p.relative_to('/a'), P('b'))
-        self.assertEqual(p.relative_to('/a/'), P('b'))
-        self.assertEqual(p.relative_to(P('/a/b')), P())
-        self.assertEqual(p.relative_to('/a/b'), P())
-        self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b'))
-        self.assertEqual(p.relative_to('/', walk_up=True), P('a/b'))
-        self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b'))
-        self.assertEqual(p.relative_to('/a', walk_up=True), P('b'))
-        self.assertEqual(p.relative_to('/a/', walk_up=True), P('b'))
-        self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P())
-        self.assertEqual(p.relative_to('/a/b', walk_up=True), P())
-        self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b'))
-        self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b'))
-        self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..'))
-        self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..'))
-        self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b'))
-        self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b'))
-        # Unrelated paths.
-        self.assertRaises(ValueError, p.relative_to, P('/c'))
-        self.assertRaises(ValueError, p.relative_to, P('/a/b/c'))
-        self.assertRaises(ValueError, p.relative_to, P('/a/c'))
-        self.assertRaises(ValueError, p.relative_to, P())
-        self.assertRaises(ValueError, p.relative_to, '')
-        self.assertRaises(ValueError, p.relative_to, P('a'))
-        self.assertRaises(ValueError, p.relative_to, P("../a"))
-        self.assertRaises(ValueError, p.relative_to, P("a/.."))
-        self.assertRaises(ValueError, p.relative_to, P("/a/.."))
-        self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True)
-        self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True)
-
-    def test_is_relative_to_common(self):
-        P = self.cls
-        p = P('a/b')
-        self.assertRaises(TypeError, p.is_relative_to)
-        self.assertRaises(TypeError, p.is_relative_to, b'a')
-        self.assertTrue(p.is_relative_to(P()))
-        self.assertTrue(p.is_relative_to(''))
-        self.assertTrue(p.is_relative_to(P('a')))
-        self.assertTrue(p.is_relative_to('a/'))
-        self.assertTrue(p.is_relative_to(P('a/b')))
-        self.assertTrue(p.is_relative_to('a/b'))
-        # With several args.
-        with self.assertWarns(DeprecationWarning):
-            p.is_relative_to('a', 'b')
-        # Unrelated paths.
-        self.assertFalse(p.is_relative_to(P('c')))
-        self.assertFalse(p.is_relative_to(P('a/b/c')))
-        self.assertFalse(p.is_relative_to(P('a/c')))
-        self.assertFalse(p.is_relative_to(P('/a')))
-        p = P('/a/b')
-        self.assertTrue(p.is_relative_to(P('/')))
-        self.assertTrue(p.is_relative_to('/'))
-        self.assertTrue(p.is_relative_to(P('/a')))
-        self.assertTrue(p.is_relative_to('/a'))
-        self.assertTrue(p.is_relative_to('/a/'))
-        self.assertTrue(p.is_relative_to(P('/a/b')))
-        self.assertTrue(p.is_relative_to('/a/b'))
-        # Unrelated paths.
-        self.assertFalse(p.is_relative_to(P('/c')))
-        self.assertFalse(p.is_relative_to(P('/a/b/c')))
-        self.assertFalse(p.is_relative_to(P('/a/c')))
-        self.assertFalse(p.is_relative_to(P()))
-        self.assertFalse(p.is_relative_to(''))
-        self.assertFalse(p.is_relative_to(P('a')))
-
-
-class PurePathTest(DummyPurePathTest):
+class PurePathTest(test_pathlib_abc.DummyPurePathTest):
     cls = pathlib.PurePath
 
     def test_constructor_nested(self):
@@ -1581,1246 +926,11 @@ class PurePathSubclassTest(PurePathTest):
     test_repr_roundtrips = None
 
 
-#
-# Tests for the virtual classes.
-#
-
-class PathBaseTest(PurePathBaseTest):
-    cls = pathlib._abc.PathBase
-
-    def test_unsupported_operation(self):
-        P = self.cls
-        p = self.cls()
-        e = pathlib.UnsupportedOperation
-        self.assertRaises(e, p.stat)
-        self.assertRaises(e, p.lstat)
-        self.assertRaises(e, p.exists)
-        self.assertRaises(e, p.samefile, 'foo')
-        self.assertRaises(e, p.is_dir)
-        self.assertRaises(e, p.is_file)
-        self.assertRaises(e, p.is_mount)
-        self.assertRaises(e, p.is_symlink)
-        self.assertRaises(e, p.is_block_device)
-        self.assertRaises(e, p.is_char_device)
-        self.assertRaises(e, p.is_fifo)
-        self.assertRaises(e, p.is_socket)
-        self.assertRaises(e, p.open)
-        self.assertRaises(e, p.read_bytes)
-        self.assertRaises(e, p.read_text)
-        self.assertRaises(e, p.write_bytes, b'foo')
-        self.assertRaises(e, p.write_text, 'foo')
-        self.assertRaises(e, p.iterdir)
-        self.assertRaises(e, p.glob, '*')
-        self.assertRaises(e, p.rglob, '*')
-        self.assertRaises(e, lambda: list(p.walk()))
-        self.assertRaises(e, p.absolute)
-        self.assertRaises(e, P.cwd)
-        self.assertRaises(e, p.expanduser)
-        self.assertRaises(e, p.home)
-        self.assertRaises(e, p.readlink)
-        self.assertRaises(e, p.symlink_to, 'foo')
-        self.assertRaises(e, p.hardlink_to, 'foo')
-        self.assertRaises(e, p.mkdir)
-        self.assertRaises(e, p.touch)
-        self.assertRaises(e, p.rename, 'foo')
-        self.assertRaises(e, p.replace, 'foo')
-        self.assertRaises(e, p.chmod, 0o755)
-        self.assertRaises(e, p.lchmod, 0o755)
-        self.assertRaises(e, p.unlink)
-        self.assertRaises(e, p.rmdir)
-        self.assertRaises(e, p.owner)
-        self.assertRaises(e, p.group)
-        self.assertRaises(e, p.as_uri)
-
-    def test_as_uri_common(self):
-        e = pathlib.UnsupportedOperation
-        self.assertRaises(e, self.cls().as_uri)
-
-    def test_fspath_common(self):
-        self.assertRaises(TypeError, os.fspath, self.cls())
-
-    def test_as_bytes_common(self):
-        self.assertRaises(TypeError, bytes, self.cls())
-
-    def test_matches_path_api(self):
-        our_names = {name for name in dir(self.cls) if name[0] != '_'}
-        path_names = {name for name in dir(pathlib.Path) if name[0] != '_'}
-        self.assertEqual(our_names, path_names)
-        for attr_name in our_names:
-            our_attr = getattr(self.cls, attr_name)
-            path_attr = getattr(pathlib.Path, attr_name)
-            self.assertEqual(our_attr.__doc__, path_attr.__doc__)
-
-
-class DummyPathIO(io.BytesIO):
-    """
-    Used by DummyPath to implement `open('w')`
-    """
-
-    def __init__(self, files, path):
-        super().__init__()
-        self.files = files
-        self.path = path
-
-    def close(self):
-        self.files[self.path] = self.getvalue()
-        super().close()
-
-
-class DummyPath(pathlib._abc.PathBase):
-    """
-    Simple implementation of PathBase that keeps files and directories in
-    memory.
-    """
-    _files = {}
-    _directories = {}
-    _symlinks = {}
-
-    def __eq__(self, other):
-        if not isinstance(other, DummyPath):
-            return NotImplemented
-        return str(self) == str(other)
-
-    def __hash__(self):
-        return hash(str(self))
-
-    def stat(self, *, follow_symlinks=True):
-        if follow_symlinks:
-            path = str(self.resolve())
-        else:
-            path = str(self.parent.resolve() / self.name)
-        if path in self._files:
-            st_mode = stat.S_IFREG
-        elif path in self._directories:
-            st_mode = stat.S_IFDIR
-        elif path in self._symlinks:
-            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))
-
-    def open(self, mode='r', buffering=-1, encoding=None,
-             errors=None, newline=None):
-        if buffering != -1:
-            raise NotImplementedError
-        path_obj = self.resolve()
-        path = str(path_obj)
-        name = path_obj.name
-        parent = str(path_obj.parent)
-        if path in self._directories:
-            raise IsADirectoryError(errno.EISDIR, "Is a directory", path)
-
-        text = 'b' not in mode
-        mode = ''.join(c for c in mode if c not in 'btU')
-        if mode == 'r':
-            if path not in self._files:
-                raise FileNotFoundError(errno.ENOENT, "File not found", path)
-            stream = io.BytesIO(self._files[path])
-        elif mode == 'w':
-            if parent not in self._directories:
-                raise FileNotFoundError(errno.ENOENT, "File not found", parent)
-            stream = DummyPathIO(self._files, path)
-            self._files[path] = b''
-            self._directories[parent].add(name)
-        else:
-            raise NotImplementedError
-        if text:
-            stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline)
-        return stream
-
-    def iterdir(self):
-        path = str(self.resolve())
-        if path in self._files:
-            raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path)
-        elif path in self._directories:
-            return (self / name for name in self._directories[path])
-        else:
-            raise FileNotFoundError(errno.ENOENT, "File not found", path)
-
-    def mkdir(self, mode=0o777, parents=False, exist_ok=False):
-        path = str(self.resolve())
-        if path in self._directories:
-            if exist_ok:
-                return
-            else:
-                raise FileExistsError(errno.EEXIST, "File exists", path)
-        try:
-            if self.name:
-                self._directories[str(self.parent)].add(self.name)
-            self._directories[path] = set()
-        except KeyError:
-            if not parents:
-                raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None
-            self.parent.mkdir(parents=True, exist_ok=True)
-            self.mkdir(mode, parents=False, exist_ok=exist_ok)
-
-
-class DummyPathTest(DummyPurePathTest):
-    """Tests for PathBase methods that use stat(), open() and iterdir()."""
-
-    cls = DummyPath
-    can_symlink = False
-
-    # (BASE)
-    #  |
-    #  |-- brokenLink -> non-existing
-    #  |-- dirA
-    #  |   `-- linkC -> ../dirB
-    #  |-- dirB
-    #  |   |-- fileB
-    #  |   `-- linkD -> ../dirB
-    #  |-- dirC
-    #  |   |-- dirD
-    #  |   |   `-- fileD
-    #  |   `-- fileC
-    #  |   `-- novel.txt
-    #  |-- dirE  # No permissions
-    #  |-- fileA
-    #  |-- linkA -> fileA
-    #  |-- linkB -> dirB
-    #  `-- brokenLinkLoop -> brokenLinkLoop
-    #
-
-    def setUp(self):
-        super().setUp()
-        pathmod = self.cls.pathmod
-        p = self.cls(BASE)
-        p.mkdir(parents=True)
-        p.joinpath('dirA').mkdir()
-        p.joinpath('dirB').mkdir()
-        p.joinpath('dirC').mkdir()
-        p.joinpath('dirC', 'dirD').mkdir()
-        p.joinpath('dirE').mkdir()
-        with p.joinpath('fileA').open('wb') as f:
-            f.write(b"this is file A\n")
-        with p.joinpath('dirB', 'fileB').open('wb') as f:
-            f.write(b"this is file B\n")
-        with p.joinpath('dirC', 'fileC').open('wb') as f:
-            f.write(b"this is file C\n")
-        with p.joinpath('dirC', 'novel.txt').open('wb') as f:
-            f.write(b"this is a novel\n")
-        with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f:
-            f.write(b"this is file D\n")
-        if self.can_symlink:
-            p.joinpath('linkA').symlink_to('fileA')
-            p.joinpath('brokenLink').symlink_to('non-existing')
-            p.joinpath('linkB').symlink_to('dirB')
-            p.joinpath('dirA', 'linkC').symlink_to(pathmod.join('..', 'dirB'))
-            p.joinpath('dirB', 'linkD').symlink_to(pathmod.join('..', 'dirB'))
-            p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop')
-
-    def tearDown(self):
-        cls = self.cls
-        cls._files.clear()
-        cls._directories.clear()
-        cls._symlinks.clear()
-
-    def tempdir(self):
-        path = self.cls(BASE).with_name('tmp-dirD')
-        path.mkdir()
-        return path
-
-    def assertFileNotFound(self, func, *args, **kwargs):
-        with self.assertRaises(FileNotFoundError) as cm:
-            func(*args, **kwargs)
-        self.assertEqual(cm.exception.errno, errno.ENOENT)
-
-    def assertEqualNormCase(self, path_a, path_b):
-        self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b))
-
-    def test_samefile(self):
-        fileA_path = os.path.join(BASE, 'fileA')
-        fileB_path = os.path.join(BASE, 'dirB', 'fileB')
-        p = self.cls(fileA_path)
-        pp = self.cls(fileA_path)
-        q = self.cls(fileB_path)
-        self.assertTrue(p.samefile(fileA_path))
-        self.assertTrue(p.samefile(pp))
-        self.assertFalse(p.samefile(fileB_path))
-        self.assertFalse(p.samefile(q))
-        # Test the non-existent file case
-        non_existent = os.path.join(BASE, 'foo')
-        r = self.cls(non_existent)
-        self.assertRaises(FileNotFoundError, p.samefile, r)
-        self.assertRaises(FileNotFoundError, p.samefile, non_existent)
-        self.assertRaises(FileNotFoundError, r.samefile, p)
-        self.assertRaises(FileNotFoundError, r.samefile, non_existent)
-        self.assertRaises(FileNotFoundError, r.samefile, r)
-        self.assertRaises(FileNotFoundError, r.samefile, non_existent)
-
-    def test_empty_path(self):
-        # The empty path points to '.'
-        p = self.cls('')
-        self.assertEqual(str(p), '.')
-
-    def test_exists(self):
-        P = self.cls
-        p = P(BASE)
-        self.assertIs(True, p.exists())
-        self.assertIs(True, (p / 'dirA').exists())
-        self.assertIs(True, (p / 'fileA').exists())
-        self.assertIs(False, (p / 'fileA' / 'bah').exists())
-        if self.can_symlink:
-            self.assertIs(True, (p / 'linkA').exists())
-            self.assertIs(True, (p / 'linkB').exists())
-            self.assertIs(True, (p / 'linkB' / 'fileB').exists())
-            self.assertIs(False, (p / 'linkA' / 'bah').exists())
-            self.assertIs(False, (p / 'brokenLink').exists())
-            self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False))
-        self.assertIs(False, (p / 'foo').exists())
-        self.assertIs(False, P('/xyzzy').exists())
-        self.assertIs(False, P(BASE + '\udfff').exists())
-        self.assertIs(False, P(BASE + '\x00').exists())
-
-    def test_open_common(self):
-        p = self.cls(BASE)
-        with (p / 'fileA').open('r') as f:
-            self.assertIsInstance(f, io.TextIOBase)
-            self.assertEqual(f.read(), "this is file A\n")
-        with (p / 'fileA').open('rb') as f:
-            self.assertIsInstance(f, io.BufferedIOBase)
-            self.assertEqual(f.read().strip(), b"this is file A")
-
-    def test_read_write_bytes(self):
-        p = self.cls(BASE)
-        (p / 'fileA').write_bytes(b'abcdefg')
-        self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg')
-        # Check that trying to write str does not truncate the file.
-        self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr')
-        self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg')
-
-    def test_read_write_text(self):
-        p = self.cls(BASE)
-        (p / 'fileA').write_text('äbcdefg', encoding='latin-1')
-        self.assertEqual((p / 'fileA').read_text(
-            encoding='utf-8', errors='ignore'), 'bcdefg')
-        # Check that trying to write bytes does not truncate the file.
-        self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes')
-        self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg')
-
-    def test_read_text_with_newlines(self):
-        p = self.cls(BASE)
-        # Check that `\n` character change nothing
-        (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
-        self.assertEqual((p / 'fileA').read_text(newline='\n'),
-                         'abcde\r\nfghlk\n\rmnopq')
-        # Check that `\r` character replaces `\n`
-        (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
-        self.assertEqual((p / 'fileA').read_text(newline='\r'),
-                         'abcde\r\nfghlk\n\rmnopq')
-        # Check that `\r\n` character replaces `\n`
-        (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
-        self.assertEqual((p / 'fileA').read_text(newline='\r\n'),
-                             'abcde\r\nfghlk\n\rmnopq')
-
-    def test_write_text_with_newlines(self):
-        p = self.cls(BASE)
-        # Check that `\n` character change nothing
-        (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n')
-        self.assertEqual((p / 'fileA').read_bytes(),
-                         b'abcde\r\nfghlk\n\rmnopq')
-        # Check that `\r` character replaces `\n`
-        (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r')
-        self.assertEqual((p / 'fileA').read_bytes(),
-                         b'abcde\r\rfghlk\r\rmnopq')
-        # Check that `\r\n` character replaces `\n`
-        (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n')
-        self.assertEqual((p / 'fileA').read_bytes(),
-                         b'abcde\r\r\nfghlk\r\n\rmnopq')
-        # Check that no argument passed will change `\n` to `os.linesep`
-        os_linesep_byte = bytes(os.linesep, encoding='ascii')
-        (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq')
-        self.assertEqual((p / 'fileA').read_bytes(),
-                          b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq')
-
-    def test_iterdir(self):
-        P = self.cls
-        p = P(BASE)
-        it = p.iterdir()
-        paths = set(it)
-        expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA']
-        if self.can_symlink:
-            expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
-        self.assertEqual(paths, { P(BASE, q) for q in expected })
-
-    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')
-        paths = set(p.iterdir())
-        expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] }
-        self.assertEqual(paths, expected)
-
-    def test_iterdir_nodir(self):
-        # __iter__ on something that is not a directory.
-        p = self.cls(BASE, 'fileA')
-        with self.assertRaises(OSError) as cm:
-            p.iterdir()
-        # ENOENT or EINVAL under Windows, ENOTDIR otherwise
-        # (see issue #12802).
-        self.assertIn(cm.exception.errno, (errno.ENOTDIR,
-                                           errno.ENOENT, errno.EINVAL))
-
-    def test_glob_common(self):
-        def _check(glob, expected):
-            self.assertEqual(set(glob), { P(BASE, q) for q in expected })
-        P = self.cls
-        p = P(BASE)
-        it = p.glob("fileA")
-        self.assertIsInstance(it, collections.abc.Iterator)
-        _check(it, ["fileA"])
-        _check(p.glob("fileB"), [])
-        _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"])
-        if not self.can_symlink:
-            _check(p.glob("*A"), ['dirA', 'fileA'])
-        else:
-            _check(p.glob("*A"), ['dirA', 'fileA', 'linkA'])
-        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 self.can_symlink:
-            _check(p.glob("*/fileB"), ['dirB/fileB'])
-        else:
-            _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
-        if self.can_symlink:
-            _check(p.glob("brokenLink"), ['brokenLink'])
-
-        if not self.can_symlink:
-            _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"])
-        else:
-            _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
-
-    def test_glob_empty_pattern(self):
-        p = self.cls()
-        with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'):
-            list(p.glob(''))
-
-    def test_glob_case_sensitive(self):
-        P = self.cls
-        def _check(path, pattern, case_sensitive, expected):
-            actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)}
-            expected = {str(P(BASE, q)) for q in expected}
-            self.assertEqual(actual, expected)
-        path = P(BASE)
-        _check(path, "DIRB/FILE*", True, [])
-        _check(path, "DIRB/FILE*", False, ["dirB/fileB"])
-        _check(path, "dirb/file*", True, [])
-        _check(path, "dirb/file*", False, ["dirB/fileB"])
-
-    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.
-            self.assertEqual(actual, { P(BASE, q) for q in expected })
-        P = self.cls
-        p = P(BASE)
-        _check(p, "fileB", [])
-        _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
-        _check(p, "*A", ["dirA", "fileA", "linkA"])
-        _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"])
-        _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
-        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
-        _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."])
-        _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
-                               "dirC/", "dirC/dirD/", "dirE/"])
-        _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
-                                 "dirC/..", "dirC/dirD/..", "dirE/.."])
-        _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
-        _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."])
-        _check(p, "dir*/**/fileC", ["dirC/fileC"])
-        _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
-        _check(p, "*/dirD/**/", ["dirC/dirD/"])
-
-    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 })
-        P = self.cls
-        p = P(BASE)
-        _check(p, "fileB", [])
-        _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
-        _check(p, "*A", ["dirA", "fileA", "linkA"])
-        _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"])
-        _check(p, "*/fileB", ["dirB/fileB"])
-        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"])
-        _check(p, "dir*/*/..", ["dirC/dirD/.."])
-        _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
-        _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."])
-        _check(p, "dir*/*/**/", ["dirC/dirD/"])
-        _check(p, "dir*/*/**/..", ["dirC/dirD/.."])
-        _check(p, "dir*/**/fileC", ["dirC/fileC"])
-        _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
-        _check(p, "*/dirD/**/", ["dirC/dirD/"])
-
-    def test_rglob_common(self):
-        def _check(glob, expected):
-            self.assertEqual(set(glob), {P(BASE, q) for q in expected})
-        P = self.cls
-        p = P(BASE)
-        it = p.rglob("fileA")
-        self.assertIsInstance(it, collections.abc.Iterator)
-        _check(it, ["fileA"])
-        _check(p.rglob("fileB"), ["dirB/fileB"])
-        _check(p.rglob("**/fileB"), ["dirB/fileB"])
-        _check(p.rglob("*/fileA"), [])
-        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 self.can_symlink:
-            _check(p.rglob("*/"), [
-                "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/",
-            ])
-        else:
-            _check(p.rglob("*/"), [
-                "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/",
-                "dirC/dirD/", "dirE/", "linkB/",
-            ])
-        _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"])
-
-        p = P(BASE, "dirC")
-        _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt",
-                              "dirC/dirD", "dirC/dirD/fileD"])
-        _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
-        _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"])
-        _check(p.rglob("dir*/**/"), ["dirC/dirD/"])
-        _check(p.rglob("*/*"), ["dirC/dirD/fileD"])
-        _check(p.rglob("*/"), ["dirC/dirD/"])
-        _check(p.rglob(""), ["dirC/", "dirC/dirD/"])
-        _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
-        # gh-91616, a re module regression
-        _check(p.rglob("*.txt"), ["dirC/novel.txt"])
-        _check(p.rglob("*.*"), ["dirC/novel.txt"])
-
-    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.
-            self.assertEqual(actual, { P(BASE, q) for q in expected })
-        P = self.cls
-        p = P(BASE)
-        _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"])
-        _check(p, "*/fileA", [])
-        _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"])
-        _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB",
-                            "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"])
-        _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
-                         "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"])
-        _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
-                       "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"])
-
-        p = P(BASE, "dirC")
-        _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
-                        "dirC/dirD", "dirC/dirD/fileD"])
-        _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
-        _check(p, "*/*", ["dirC/dirD/fileD"])
-        _check(p, "*/", ["dirC/dirD/"])
-        _check(p, "", ["dirC/", "dirC/dirD/"])
-        # gh-91616, a re module regression
-        _check(p, "*.txt", ["dirC/novel.txt"])
-        _check(p, "*.*", ["dirC/novel.txt"])
-
-    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 })
-        P = self.cls
-        p = P(BASE)
-        _check(p, "fileB", ["dirB/fileB"])
-        _check(p, "*/fileA", [])
-        _check(p, "*/fileB", ["dirB/fileB"])
-        _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ])
-        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
-        _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"])
-
-        p = P(BASE, "dirC")
-        _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
-                        "dirC/dirD", "dirC/dirD/fileD"])
-        _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
-        _check(p, "*/*", ["dirC/dirD/fileD"])
-        _check(p, "*/", ["dirC/dirD/"])
-        _check(p, "", ["dirC/", "dirC/dirD/"])
-        # gh-91616, a re module regression
-        _check(p, "*.txt", ["dirC/novel.txt"])
-        _check(p, "*.*", ["dirC/novel.txt"])
-
-    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('*'))
-        expect = {'brokenLink',
-                  'dirA', 'dirA/linkC',
-                  'dirB', 'dirB/fileB', 'dirB/linkD',
-                  'dirC', 'dirC/dirD', 'dirC/dirD/fileD',
-                  'dirC/fileC', 'dirC/novel.txt',
-                  'dirE',
-                  'fileA',
-                  'linkA',
-                  'linkB',
-                  'brokenLinkLoop',
-                  }
-        self.assertEqual(given, {p / x for x in expect})
-
-    def test_glob_many_open_files(self):
-        depth = 30
-        P = self.cls
-        p = base = P(BASE) / 'deep'
-        p.mkdir()
-        for _ in range(depth):
-            p /= 'd'
-            p.mkdir()
-        pattern = '/'.join(['*'] * depth)
-        iters = [base.glob(pattern) for j in range(100)]
-        for it in iters:
-            self.assertEqual(next(it), p)
-        iters = [base.rglob('d') for j in range(100)]
-        p = base
-        for i in range(depth):
-            p = p / 'd'
-            for it in iters:
-                self.assertEqual(next(it), p)
-
-    def test_glob_dotdot(self):
-        # ".." is not special in globs.
-        P = self.cls
-        p = P(BASE)
-        self.assertEqual(set(p.glob("..")), { P(BASE, "..") })
-        self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") })
-        self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") })
-        self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") })
-        self.assertEqual(set(p.glob("dirA/../file*/..")), set())
-        self.assertEqual(set(p.glob("../xyzzy")), set())
-        self.assertEqual(set(p.glob("xyzzy/..")), set())
-        self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)})
-
-    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()
-
-        for i in range(100):
-            link = base / f"link{i}"
-            if i % 2:
-                link.symlink_to(P(BASE, "dirE", "nonexistent"))
-            else:
-                link.symlink_to(P(BASE, "dirC"))
-
-        self.assertEqual(len(set(base.glob("*"))), 100)
-        self.assertEqual(len(set(base.glob("*/"))), 50)
-        self.assertEqual(len(set(base.glob("*/fileC"))), 50)
-        self.assertEqual(len(set(base.glob("*/file*"))), 50)
-
-    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'
-        bad_link.symlink_to("bad" * 200)
-        self.assertEqual(sorted(base.glob('**/*')), [bad_link])
-
-    def test_glob_above_recursion_limit(self):
-        recursion_limit = 50
-        # directory_depth > recursion_limit
-        directory_depth = recursion_limit + 10
-        base = self.cls(BASE, 'deep')
-        path = base.joinpath(*(['d'] * directory_depth))
-        path.mkdir(parents=True)
-
-        with set_recursion_limit(recursion_limit):
-            list(base.glob('**/'))
-
-    def test_glob_recursive_no_trailing_slash(self):
-        P = self.cls
-        p = P(BASE)
-        with self.assertWarns(FutureWarning):
-            p.glob('**')
-        with self.assertWarns(FutureWarning):
-            p.glob('*/**')
-        with self.assertWarns(FutureWarning):
-            p.rglob('**')
-        with self.assertWarns(FutureWarning):
-            p.rglob('*/**')
-
-
-    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(),
-                         self.cls('non-existing'))
-        self.assertEqual((P / 'linkB').readlink(), self.cls('dirB'))
-        self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB'))
-        with self.assertRaises(OSError):
-            (P / 'fileA').readlink()
-
-    @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present")
-    def test_readlink_unsupported(self):
-        P = self.cls(BASE)
-        p = P / 'fileA'
-        with self.assertRaises(pathlib.UnsupportedOperation):
-            q.readlink(p)
-
-    def _check_resolve(self, p, expected, strict=True):
-        q = p.resolve(strict)
-        self.assertEqual(q, expected)
-
-    # This can be used to check both relative and absolute resolutions.
-    _check_resolve_relative = _check_resolve_absolute = _check_resolve
-
-    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:
-            p.resolve(strict=True)
-        self.assertEqual(cm.exception.errno, errno.ENOENT)
-        # Non-strict
-        self.assertEqualNormCase(str(p.resolve(strict=False)),
-                                 os.path.join(BASE, 'foo'))
-        p = P(BASE, 'foo', 'in', 'spam')
-        self.assertEqualNormCase(str(p.resolve(strict=False)),
-                                 os.path.join(BASE, 'foo', 'in', 'spam'))
-        p = P(BASE, '..', 'foo', 'in', 'spam')
-        self.assertEqualNormCase(str(p.resolve(strict=False)),
-                                 os.path.abspath(os.path.join('foo', 'in', 'spam')))
-        # These are all relative symlinks.
-        p = P(BASE, 'dirB', 'fileB')
-        self._check_resolve_relative(p, p)
-        p = P(BASE, 'linkA')
-        self._check_resolve_relative(p, P(BASE, 'fileA'))
-        p = P(BASE, 'dirA', 'linkC', 'fileB')
-        self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
-        p = P(BASE, 'dirB', 'linkD', 'fileB')
-        self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
-        # Non-strict
-        p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam')
-        self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in',
-                                          'spam'), False)
-        p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')
-        if os.name == 'nt' and isinstance(p, pathlib.Path):
-            # In Windows, if linkY points to dirB, 'dirA\linkY\..'
-            # resolves to 'dirA' without resolving linkY first.
-            self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in',
-                                              'spam'), False)
-        else:
-            # In Posix, if linkY points to dirB, 'dirA/linkY/..'
-            # resolves to 'dirB/..' first before resolving to parent of dirB.
-            self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False)
-        # Now create absolute symlinks.
-        d = self.tempdir()
-        P(BASE, 'dirA', 'linkX').symlink_to(d)
-        P(BASE, str(d), 'linkY').symlink_to(join('dirB'))
-        p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB')
-        self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB'))
-        # Non-strict
-        p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam')
-        self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'),
-                                     False)
-        p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')
-        if os.name == 'nt' and isinstance(p, pathlib.Path):
-            # In Windows, if linkY points to dirB, 'dirA\linkY\..'
-            # resolves to 'dirA' without resolving linkY first.
-            self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False)
-        else:
-            # In Posix, if linkY points to dirB, 'dirA/linkY/..'
-            # resolves to 'dirB/..' first before resolving to parent of dirB.
-            self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False)
-
-    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)
-        p.joinpath('0').symlink_to('.', target_is_directory=True)
-        p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True)
-        p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True)
-        q = p / '2'
-        self.assertEqual(q.resolve(strict=True), p)
-        r = q / '3' / '4'
-        self.assertRaises(FileNotFoundError, r.resolve, strict=True)
-        # Non-strict
-        self.assertEqual(r.resolve(strict=False), p / '3' / '4')
-
-    def _check_symlink_loop(self, *args):
-        path = self.cls(*args)
-        with self.assertRaises(OSError) as cm:
-            path.resolve(strict=True)
-        self.assertEqual(cm.exception.errno, errno.ELOOP)
-
-    def test_resolve_loop(self):
-        if not self.can_symlink:
-            self.skipTest("symlinks required")
-        if os.name == 'nt' and issubclass(self.cls, pathlib.Path):
-            self.skipTest("symlink loops work differently with concrete Windows paths")
-        # Loops with relative symlinks.
-        self.cls(BASE, 'linkX').symlink_to('linkX/inside')
-        self._check_symlink_loop(BASE, 'linkX')
-        self.cls(BASE, 'linkY').symlink_to('linkY')
-        self._check_symlink_loop(BASE, 'linkY')
-        self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ')
-        self._check_symlink_loop(BASE, 'linkZ')
-        # Non-strict
-        p = self.cls(BASE, 'linkZ', 'foo')
-        self.assertEqual(p.resolve(strict=False), p)
-        # Loops with absolute symlinks.
-        self.cls(BASE, 'linkU').symlink_to(join('linkU/inside'))
-        self._check_symlink_loop(BASE, 'linkU')
-        self.cls(BASE, 'linkV').symlink_to(join('linkV'))
-        self._check_symlink_loop(BASE, 'linkV')
-        self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW'))
-        self._check_symlink_loop(BASE, 'linkW')
-        # Non-strict
-        q = self.cls(BASE, 'linkW', 'foo')
-        self.assertEqual(q.resolve(strict=False), q)
-
-    def test_stat(self):
-        statA = self.cls(BASE).joinpath('fileA').stat()
-        statB = self.cls(BASE).joinpath('dirB', 'fileB').stat()
-        statC = self.cls(BASE).joinpath('dirC').stat()
-        # st_mode: files are the same, directory differs.
-        self.assertIsInstance(statA.st_mode, int)
-        self.assertEqual(statA.st_mode, statB.st_mode)
-        self.assertNotEqual(statA.st_mode, statC.st_mode)
-        self.assertNotEqual(statB.st_mode, statC.st_mode)
-        # st_ino: all different,
-        self.assertIsInstance(statA.st_ino, int)
-        self.assertNotEqual(statA.st_ino, statB.st_ino)
-        self.assertNotEqual(statA.st_ino, statC.st_ino)
-        self.assertNotEqual(statB.st_ino, statC.st_ino)
-        # st_dev: all the same.
-        self.assertIsInstance(statA.st_dev, int)
-        self.assertEqual(statA.st_dev, statB.st_dev)
-        self.assertEqual(statA.st_dev, statC.st_dev)
-        # other attributes not used by pathlib.
-
-    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))
-
-    def test_stat_no_follow_symlinks_nosymlink(self):
-        p = self.cls(BASE) / 'fileA'
-        st = p.stat()
-        self.assertEqual(st, p.stat(follow_symlinks=False))
-
-    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())
-
-    def test_lstat_nosymlink(self):
-        p = self.cls(BASE) / 'fileA'
-        st = p.stat()
-        self.assertEqual(st, p.lstat())
-
-    def test_is_dir(self):
-        P = self.cls(BASE)
-        self.assertTrue((P / 'dirA').is_dir())
-        self.assertFalse((P / 'fileA').is_dir())
-        self.assertFalse((P / 'non-existing').is_dir())
-        self.assertFalse((P / 'fileA' / 'bah').is_dir())
-        if self.can_symlink:
-            self.assertFalse((P / 'linkA').is_dir())
-            self.assertTrue((P / 'linkB').is_dir())
-            self.assertFalse((P/ 'brokenLink').is_dir())
-        self.assertFalse((P / 'dirA\udfff').is_dir())
-        self.assertFalse((P / 'dirA\x00').is_dir())
-
-    def test_is_dir_no_follow_symlinks(self):
-        P = self.cls(BASE)
-        self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False))
-        self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False))
-        self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False))
-        self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False))
-        if self.can_symlink:
-            self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False))
-            self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False))
-            self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False))
-        self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False))
-        self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False))
-
-    def test_is_file(self):
-        P = self.cls(BASE)
-        self.assertTrue((P / 'fileA').is_file())
-        self.assertFalse((P / 'dirA').is_file())
-        self.assertFalse((P / 'non-existing').is_file())
-        self.assertFalse((P / 'fileA' / 'bah').is_file())
-        if self.can_symlink:
-            self.assertTrue((P / 'linkA').is_file())
-            self.assertFalse((P / 'linkB').is_file())
-            self.assertFalse((P/ 'brokenLink').is_file())
-        self.assertFalse((P / 'fileA\udfff').is_file())
-        self.assertFalse((P / 'fileA\x00').is_file())
-
-    def test_is_file_no_follow_symlinks(self):
-        P = self.cls(BASE)
-        self.assertTrue((P / 'fileA').is_file(follow_symlinks=False))
-        self.assertFalse((P / 'dirA').is_file(follow_symlinks=False))
-        self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False))
-        self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False))
-        if self.can_symlink:
-            self.assertFalse((P / 'linkA').is_file(follow_symlinks=False))
-            self.assertFalse((P / 'linkB').is_file(follow_symlinks=False))
-            self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False))
-        self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False))
-        self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False))
-
-    def test_is_mount(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_mount())
-        self.assertFalse((P / 'dirA').is_mount())
-        self.assertFalse((P / 'non-existing').is_mount())
-        self.assertFalse((P / 'fileA' / 'bah').is_mount())
-        if self.can_symlink:
-            self.assertFalse((P / 'linkA').is_mount())
-
-    def test_is_symlink(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_symlink())
-        self.assertFalse((P / 'dirA').is_symlink())
-        self.assertFalse((P / 'non-existing').is_symlink())
-        self.assertFalse((P / 'fileA' / 'bah').is_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 self.can_symlink:
-            self.assertIs((P / 'linkA\udfff').is_file(), False)
-            self.assertIs((P / 'linkA\x00').is_file(), False)
-
-    def test_is_junction_false(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_junction())
-        self.assertFalse((P / 'dirA').is_junction())
-        self.assertFalse((P / 'non-existing').is_junction())
-        self.assertFalse((P / 'fileA' / 'bah').is_junction())
-        self.assertFalse((P / 'fileA\udfff').is_junction())
-        self.assertFalse((P / 'fileA\x00').is_junction())
-
-    def test_is_fifo_false(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_fifo())
-        self.assertFalse((P / 'dirA').is_fifo())
-        self.assertFalse((P / 'non-existing').is_fifo())
-        self.assertFalse((P / 'fileA' / 'bah').is_fifo())
-        self.assertIs((P / 'fileA\udfff').is_fifo(), False)
-        self.assertIs((P / 'fileA\x00').is_fifo(), False)
-
-    def test_is_socket_false(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_socket())
-        self.assertFalse((P / 'dirA').is_socket())
-        self.assertFalse((P / 'non-existing').is_socket())
-        self.assertFalse((P / 'fileA' / 'bah').is_socket())
-        self.assertIs((P / 'fileA\udfff').is_socket(), False)
-        self.assertIs((P / 'fileA\x00').is_socket(), False)
-
-    def test_is_block_device_false(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_block_device())
-        self.assertFalse((P / 'dirA').is_block_device())
-        self.assertFalse((P / 'non-existing').is_block_device())
-        self.assertFalse((P / 'fileA' / 'bah').is_block_device())
-        self.assertIs((P / 'fileA\udfff').is_block_device(), False)
-        self.assertIs((P / 'fileA\x00').is_block_device(), False)
-
-    def test_is_char_device_false(self):
-        P = self.cls(BASE)
-        self.assertFalse((P / 'fileA').is_char_device())
-        self.assertFalse((P / 'dirA').is_char_device())
-        self.assertFalse((P / 'non-existing').is_char_device())
-        self.assertFalse((P / 'fileA' / 'bah').is_char_device())
-        self.assertIs((P / 'fileA\udfff').is_char_device(), False)
-        self.assertIs((P / 'fileA\x00').is_char_device(), False)
-
-    def test_pickling_common(self):
-        p = self.cls(BASE, 'fileA')
-        for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
-            dumped = pickle.dumps(p, proto)
-            pp = pickle.loads(dumped)
-            self.assertEqual(pp.stat(), p.stat())
-
-    def test_parts_interning(self):
-        P = self.cls
-        p = P('/usr/bin/foo')
-        q = P('/usr/local/bin')
-        # 'usr'
-        self.assertIs(p.parts[1], q.parts[1])
-        # 'bin'
-        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)
-        P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True)
-        P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True)
-        P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True)
-        P.joinpath('link0').symlink_to(link0_target, target_is_directory=True)
-
-        # Resolve absolute paths.
-        p = (P / 'link0').resolve()
-        self.assertEqual(p, P)
-        self.assertEqualNormCase(str(p), BASE)
-        p = (P / 'link1').resolve()
-        self.assertEqual(p, P)
-        self.assertEqualNormCase(str(p), BASE)
-        p = (P / 'link2').resolve()
-        self.assertEqual(p, P)
-        self.assertEqualNormCase(str(p), BASE)
-        p = (P / 'link3').resolve()
-        self.assertEqual(p, P)
-        self.assertEqualNormCase(str(p), BASE)
-
-        # Resolve relative paths.
-        try:
-            self.cls().absolute()
-        except pathlib.UnsupportedOperation:
-            return
-        old_path = os.getcwd()
-        os.chdir(BASE)
-        try:
-            p = self.cls('link0').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), BASE)
-            p = self.cls('link1').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), BASE)
-            p = self.cls('link2').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), BASE)
-            p = self.cls('link3').resolve()
-            self.assertEqual(p, P)
-            self.assertEqualNormCase(str(p), BASE)
-        finally:
-            os.chdir(old_path)
-
-    def test_complex_symlinks_absolute(self):
-        self._check_complex_symlinks(BASE)
-
-    def test_complex_symlinks_relative(self):
-        self._check_complex_symlinks('.')
-
-    def test_complex_symlinks_relative_dot_dot(self):
-        self._check_complex_symlinks(os.path.join('dirA', '..'))
-
-    def setUpWalk(self):
-        # Build:
-        #     TESTFN/
-        #       TEST1/              a file kid and two directory kids
-        #         tmp1
-        #         SUB1/             a file kid and a directory kid
-        #           tmp2
-        #           SUB11/          no kids
-        #         SUB2/             a file kid and a dirsymlink kid
-        #           tmp3
-        #           link/           a symlink to TEST2
-        #           broken_link
-        #           broken_link2
-        #       TEST2/
-        #         tmp4              a lone file
-        self.walk_path = self.cls(BASE, "TEST1")
-        self.sub1_path = self.walk_path / "SUB1"
-        self.sub11_path = self.sub1_path / "SUB11"
-        self.sub2_path = self.walk_path / "SUB2"
-        tmp1_path = self.walk_path / "tmp1"
-        tmp2_path = self.sub1_path / "tmp2"
-        tmp3_path = self.sub2_path / "tmp3"
-        self.link_path = self.sub2_path / "link"
-        t2_path = self.cls(BASE, "TEST2")
-        tmp4_path = self.cls(BASE, "TEST2", "tmp4")
-        broken_link_path = self.sub2_path / "broken_link"
-        broken_link2_path = self.sub2_path / "broken_link2"
-
-        self.sub11_path.mkdir(parents=True)
-        self.sub2_path.mkdir(parents=True)
-        t2_path.mkdir(parents=True)
-
-        for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path:
-            with path.open("w", encoding='utf-8') as f:
-                f.write(f"I'm {path} and proud of it.  Blame test_pathlib.\n")
-
-        if self.can_symlink:
-            self.link_path.symlink_to(t2_path)
-            broken_link_path.symlink_to('broken')
-            broken_link2_path.symlink_to(self.cls('tmp3', 'broken'))
-            self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"])
-        else:
-            self.sub2_tree = (self.sub2_path, [], ["tmp3"])
-
-    def test_walk_topdown(self):
-        self.setUpWalk()
-        walker = self.walk_path.walk()
-        entry = next(walker)
-        entry[1].sort()  # Ensure we visit SUB1 before SUB2
-        self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"]))
-        entry = next(walker)
-        self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"]))
-        entry = next(walker)
-        self.assertEqual(entry, (self.sub11_path, [], []))
-        entry = next(walker)
-        entry[1].sort()
-        entry[2].sort()
-        self.assertEqual(entry, self.sub2_tree)
-        with self.assertRaises(StopIteration):
-            next(walker)
-
-    def test_walk_prune(self):
-        self.setUpWalk()
-        # Prune the search.
-        all = []
-        for root, dirs, files in self.walk_path.walk():
-            all.append((root, dirs, files))
-            if 'SUB1' in dirs:
-                # Note that this also mutates the dirs we appended to all!
-                dirs.remove('SUB1')
-
-        self.assertEqual(len(all), 2)
-        self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"]))
-
-        all[1][-1].sort()
-        all[1][1].sort()
-        self.assertEqual(all[1], self.sub2_tree)
-
-    def test_walk_bottom_up(self):
-        self.setUpWalk()
-        seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False
-        for path, dirnames, filenames in self.walk_path.walk(top_down=False):
-            if path == self.walk_path:
-                self.assertFalse(seen_testfn)
-                self.assertTrue(seen_sub1)
-                self.assertTrue(seen_sub2)
-                self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"])
-                self.assertEqual(filenames, ["tmp1"])
-                seen_testfn = True
-            elif path == self.sub1_path:
-                self.assertFalse(seen_testfn)
-                self.assertFalse(seen_sub1)
-                self.assertTrue(seen_sub11)
-                self.assertEqual(dirnames, ["SUB11"])
-                self.assertEqual(filenames, ["tmp2"])
-                seen_sub1 = True
-            elif path == self.sub11_path:
-                self.assertFalse(seen_sub1)
-                self.assertFalse(seen_sub11)
-                self.assertEqual(dirnames, [])
-                self.assertEqual(filenames, [])
-                seen_sub11 = True
-            elif path == self.sub2_path:
-                self.assertFalse(seen_testfn)
-                self.assertFalse(seen_sub2)
-                self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1]))
-                self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2]))
-                seen_sub2 = True
-            else:
-                raise AssertionError(f"Unexpected path: {path}")
-        self.assertTrue(seen_testfn)
-
-    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:
-            if root == self.link_path:
-                self.assertEqual(dirs, [])
-                self.assertEqual(files, ["tmp4"])
-                break
-        else:
-            self.fail("Didn't follow symlink with follow_symlinks=True")
-
-    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.
-        walk_it = self.walk_path.walk(follow_symlinks=False)
-        for root, dirs, files in walk_it:
-            if root == self.sub2_path:
-                self.assertIn("link", files)
-                break
-        else:
-            self.fail("symlink not found")
-
-        walk_it = self.walk_path.walk(follow_symlinks=True)
-        for root, dirs, files in walk_it:
-            if root == self.sub2_path:
-                self.assertIn("link", dirs)
-                break
-        else:
-            self.fail("symlink not found")
-
-    def test_walk_above_recursion_limit(self):
-        recursion_limit = 40
-        # directory_depth > recursion_limit
-        directory_depth = recursion_limit + 10
-        base = self.cls(BASE, 'deep')
-        path = base.joinpath(*(['d'] * directory_depth))
-        path.mkdir(parents=True)
-
-        with set_recursion_limit(recursion_limit):
-            list(base.walk())
-            list(base.walk(top_down=False))
-
-class DummyPathWithSymlinks(DummyPath):
-    def readlink(self):
-        path = str(self.parent.resolve() / self.name)
-        if path in self._symlinks:
-            return self.with_segments(self._symlinks[path])
-        elif path in self._files or path in self._directories:
-            raise OSError(errno.EINVAL, "Not a symlink", path)
-        else:
-            raise FileNotFoundError(errno.ENOENT, "File not found", path)
-
-    def symlink_to(self, target, target_is_directory=False):
-        self._directories[str(self.parent)].add(self.name)
-        self._symlinks[str(self)] = str(target)
-
-
-class DummyPathWithSymlinksTest(DummyPathTest):
-    cls = DummyPathWithSymlinks
-    can_symlink = True
-
-
 #
 # Tests for the concrete classes.
 #
 
-class PathTest(DummyPathTest, PurePathTest):
+class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
     """Tests for the FS-accessing functionalities of the Path classes."""
     cls = pathlib.Path
     can_symlink = os_helper.can_symlink()
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
new file mode 100644 (file)
index 0000000..61ed3cb
--- /dev/null
@@ -0,0 +1,1918 @@
+import collections.abc
+import io
+import os
+import errno
+import pathlib
+import pickle
+import posixpath
+import stat
+import unittest
+
+from test.support import set_recursion_limit
+from test.support.os_helper import TESTFN
+
+
+class UnsupportedOperationTest(unittest.TestCase):
+    def test_is_notimplemented(self):
+        self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError))
+        self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))
+
+
+# Make sure any symbolic links in the base test path are resolved.
+BASE = os.path.realpath(TESTFN)
+join = lambda *x: os.path.join(BASE, *x)
+
+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')
+
+
+#
+# Tests for the pure classes.
+#
+
+
+class PurePathBaseTest(unittest.TestCase):
+    cls = pathlib._abc.PurePathBase
+
+    def test_magic_methods(self):
+        P = self.cls
+        self.assertFalse(hasattr(P, '__fspath__'))
+        self.assertFalse(hasattr(P, '__bytes__'))
+        self.assertIs(P.__reduce__, object.__reduce__)
+        self.assertIs(P.__hash__, object.__hash__)
+        self.assertIs(P.__eq__, object.__eq__)
+        self.assertIs(P.__lt__, object.__lt__)
+        self.assertIs(P.__le__, object.__le__)
+        self.assertIs(P.__gt__, object.__gt__)
+        self.assertIs(P.__ge__, object.__ge__)
+
+
+class DummyPurePath(pathlib._abc.PurePathBase):
+    def __eq__(self, other):
+        if not isinstance(other, DummyPurePath):
+            return NotImplemented
+        return str(self) == str(other)
+
+    def __hash__(self):
+        return hash(str(self))
+
+
+class DummyPurePathTest(unittest.TestCase):
+    cls = DummyPurePath
+
+    # Keys are canonical paths, values are list of tuples of arguments
+    # supposed to produce equal paths.
+    equivalences = {
+        'a/b': [
+            ('a', 'b'), ('a/', 'b'), ('a', 'b/'), ('a/', 'b/'),
+            ('a/b/',), ('a//b',), ('a//b//',),
+            # Empty components get removed.
+            ('', 'a', 'b'), ('a', '', 'b'), ('a', 'b', ''),
+            ],
+        '/b/c/d': [
+            ('a', '/b/c', 'd'), ('/a', '/b/c', 'd'),
+            # Empty components get removed.
+            ('/', 'b', '', 'c/d'), ('/', '', 'b/c/d'), ('', '/b/c/d'),
+            ],
+    }
+
+    def setUp(self):
+        p = self.cls('a')
+        self.pathmod = p.pathmod
+        self.sep = self.pathmod.sep
+        self.altsep = self.pathmod.altsep
+
+    def test_constructor_common(self):
+        P = self.cls
+        p = P('a')
+        self.assertIsInstance(p, P)
+        P('a', 'b', 'c')
+        P('/a', 'b', 'c')
+        P('a/b/c')
+        P('/a/b/c')
+
+    def test_concrete_class(self):
+        if self.cls is pathlib.PurePath:
+            expected = pathlib.PureWindowsPath if os.name == 'nt' else pathlib.PurePosixPath
+        else:
+            expected = self.cls
+        p = self.cls('a')
+        self.assertIs(type(p), expected)
+
+    def test_different_pathmods_unequal(self):
+        p = self.cls('a')
+        if p.pathmod is posixpath:
+            q = pathlib.PureWindowsPath('a')
+        else:
+            q = pathlib.PurePosixPath('a')
+        self.assertNotEqual(p, q)
+
+    def test_different_pathmods_unordered(self):
+        p = self.cls('a')
+        if p.pathmod is posixpath:
+            q = pathlib.PureWindowsPath('a')
+        else:
+            q = pathlib.PurePosixPath('a')
+        with self.assertRaises(TypeError):
+            p < q
+        with self.assertRaises(TypeError):
+            p <= q
+        with self.assertRaises(TypeError):
+            p > q
+        with self.assertRaises(TypeError):
+            p >= q
+
+    def _check_str_subclass(self, *args):
+        # Issue #21127: it should be possible to construct a PurePath object
+        # from a str subclass instance, and it then gets converted to
+        # a pure str object.
+        class StrSubclass(str):
+            pass
+        P = self.cls
+        p = P(*(StrSubclass(x) for x in args))
+        self.assertEqual(p, P(*args))
+        for part in p.parts:
+            self.assertIs(type(part), str)
+
+    def test_str_subclass_common(self):
+        self._check_str_subclass('')
+        self._check_str_subclass('.')
+        self._check_str_subclass('a')
+        self._check_str_subclass('a/b.txt')
+        self._check_str_subclass('/a/b.txt')
+
+    def test_with_segments_common(self):
+        class P(self.cls):
+            def __init__(self, *pathsegments, session_id):
+                super().__init__(*pathsegments)
+                self.session_id = session_id
+
+            def with_segments(self, *pathsegments):
+                return type(self)(*pathsegments, session_id=self.session_id)
+        p = P('foo', 'bar', session_id=42)
+        self.assertEqual(42, (p / 'foo').session_id)
+        self.assertEqual(42, ('foo' / p).session_id)
+        self.assertEqual(42, p.joinpath('foo').session_id)
+        self.assertEqual(42, p.with_name('foo').session_id)
+        self.assertEqual(42, p.with_stem('foo').session_id)
+        self.assertEqual(42, p.with_suffix('.foo').session_id)
+        self.assertEqual(42, p.with_segments('foo').session_id)
+        self.assertEqual(42, p.relative_to('foo').session_id)
+        self.assertEqual(42, p.parent.session_id)
+        for parent in p.parents:
+            self.assertEqual(42, parent.session_id)
+
+    def _check_parse_path(self, raw_path, *expected):
+        sep = self.pathmod.sep
+        actual = self.cls._parse_path(raw_path.replace('/', sep))
+        self.assertEqual(actual, expected)
+        if altsep := self.pathmod.altsep:
+            actual = self.cls._parse_path(raw_path.replace('/', altsep))
+            self.assertEqual(actual, expected)
+
+    def test_parse_path_common(self):
+        check = self._check_parse_path
+        sep = self.pathmod.sep
+        check('',         '', '', [])
+        check('a',        '', '', ['a'])
+        check('a/',       '', '', ['a'])
+        check('a/b',      '', '', ['a', 'b'])
+        check('a/b/',     '', '', ['a', 'b'])
+        check('a/b/c/d',  '', '', ['a', 'b', 'c', 'd'])
+        check('a/b//c/d', '', '', ['a', 'b', 'c', 'd'])
+        check('a/b/c/d',  '', '', ['a', 'b', 'c', 'd'])
+        check('.',        '', '', [])
+        check('././b',    '', '', ['b'])
+        check('a/./b',    '', '', ['a', 'b'])
+        check('a/./.',    '', '', ['a'])
+        check('/a/b',     '', sep, ['a', 'b'])
+
+    def test_join_common(self):
+        P = self.cls
+        p = P('a/b')
+        pp = p.joinpath('c')
+        self.assertEqual(pp, P('a/b/c'))
+        self.assertIs(type(pp), type(p))
+        pp = p.joinpath('c', 'd')
+        self.assertEqual(pp, P('a/b/c/d'))
+        pp = p.joinpath('/c')
+        self.assertEqual(pp, P('/c'))
+
+    def test_div_common(self):
+        # Basically the same as joinpath().
+        P = self.cls
+        p = P('a/b')
+        pp = p / 'c'
+        self.assertEqual(pp, P('a/b/c'))
+        self.assertIs(type(pp), type(p))
+        pp = p / 'c/d'
+        self.assertEqual(pp, P('a/b/c/d'))
+        pp = p / 'c' / 'd'
+        self.assertEqual(pp, P('a/b/c/d'))
+        pp = 'c' / p / 'd'
+        self.assertEqual(pp, P('c/a/b/d'))
+        pp = p/ '/c'
+        self.assertEqual(pp, P('/c'))
+
+    def _check_str(self, expected, args):
+        p = self.cls(*args)
+        self.assertEqual(str(p), expected.replace('/', self.sep))
+
+    def test_str_common(self):
+        # Canonicalized paths roundtrip.
+        for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
+            self._check_str(pathstr, (pathstr,))
+        # Special case for the empty path.
+        self._check_str('.', ('',))
+        # Other tests for str() are in test_equivalences().
+
+    def test_as_posix_common(self):
+        P = self.cls
+        for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
+            self.assertEqual(P(pathstr).as_posix(), pathstr)
+        # Other tests for as_posix() are in test_equivalences().
+
+    def test_repr_common(self):
+        for pathstr in ('a', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c'):
+            with self.subTest(pathstr=pathstr):
+                p = self.cls(pathstr)
+                clsname = p.__class__.__name__
+                r = repr(p)
+                # The repr() is in the form ClassName("forward-slashes path").
+                self.assertTrue(r.startswith(clsname + '('), r)
+                self.assertTrue(r.endswith(')'), r)
+                inner = r[len(clsname) + 1 : -1]
+                self.assertEqual(eval(inner), p.as_posix())
+
+    def test_eq_common(self):
+        P = self.cls
+        self.assertEqual(P('a/b'), P('a/b'))
+        self.assertEqual(P('a/b'), P('a', 'b'))
+        self.assertNotEqual(P('a/b'), P('a'))
+        self.assertNotEqual(P('a/b'), P('/a/b'))
+        self.assertNotEqual(P('a/b'), P())
+        self.assertNotEqual(P('/a/b'), P('/'))
+        self.assertNotEqual(P(), P('/'))
+        self.assertNotEqual(P(), "")
+        self.assertNotEqual(P(), {})
+        self.assertNotEqual(P(), int)
+
+    def test_match_common(self):
+        P = self.cls
+        self.assertRaises(ValueError, P('a').match, '')
+        self.assertRaises(ValueError, P('a').match, '.')
+        # Simple relative pattern.
+        self.assertTrue(P('b.py').match('b.py'))
+        self.assertTrue(P('a/b.py').match('b.py'))
+        self.assertTrue(P('/a/b.py').match('b.py'))
+        self.assertFalse(P('a.py').match('b.py'))
+        self.assertFalse(P('b/py').match('b.py'))
+        self.assertFalse(P('/a.py').match('b.py'))
+        self.assertFalse(P('b.py/c').match('b.py'))
+        # Wildcard relative pattern.
+        self.assertTrue(P('b.py').match('*.py'))
+        self.assertTrue(P('a/b.py').match('*.py'))
+        self.assertTrue(P('/a/b.py').match('*.py'))
+        self.assertFalse(P('b.pyc').match('*.py'))
+        self.assertFalse(P('b./py').match('*.py'))
+        self.assertFalse(P('b.py/c').match('*.py'))
+        # Multi-part relative pattern.
+        self.assertTrue(P('ab/c.py').match('a*/*.py'))
+        self.assertTrue(P('/d/ab/c.py').match('a*/*.py'))
+        self.assertFalse(P('a.py').match('a*/*.py'))
+        self.assertFalse(P('/dab/c.py').match('a*/*.py'))
+        self.assertFalse(P('ab/c.py/d').match('a*/*.py'))
+        # Absolute pattern.
+        self.assertTrue(P('/b.py').match('/*.py'))
+        self.assertFalse(P('b.py').match('/*.py'))
+        self.assertFalse(P('a/b.py').match('/*.py'))
+        self.assertFalse(P('/a/b.py').match('/*.py'))
+        # Multi-part absolute pattern.
+        self.assertTrue(P('/a/b.py').match('/a/*.py'))
+        self.assertFalse(P('/ab.py').match('/a/*.py'))
+        self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
+        # Multi-part glob-style pattern.
+        self.assertTrue(P('a').match('**'))
+        self.assertTrue(P('c.py').match('**'))
+        self.assertTrue(P('a/b/c.py').match('**'))
+        self.assertTrue(P('/a/b/c.py').match('**'))
+        self.assertTrue(P('/a/b/c.py').match('/**'))
+        self.assertTrue(P('/a/b/c.py').match('**/'))
+        self.assertTrue(P('/a/b/c.py').match('/a/**'))
+        self.assertTrue(P('/a/b/c.py').match('**/*.py'))
+        self.assertTrue(P('/a/b/c.py').match('/**/*.py'))
+        self.assertTrue(P('/a/b/c.py').match('/a/**/*.py'))
+        self.assertTrue(P('/a/b/c.py').match('/a/b/**/*.py'))
+        self.assertTrue(P('/a/b/c.py').match('/**/**/**/**/*.py'))
+        self.assertFalse(P('c.py').match('**/a.py'))
+        self.assertFalse(P('c.py').match('c/**'))
+        self.assertFalse(P('a/b/c.py').match('**/a'))
+        self.assertFalse(P('a/b/c.py').match('**/a/b'))
+        self.assertFalse(P('a/b/c.py').match('**/a/b/c'))
+        self.assertFalse(P('a/b/c.py').match('**/a/b/c.'))
+        self.assertFalse(P('a/b/c.py').match('**/a/b/c./**'))
+        self.assertFalse(P('a/b/c.py').match('**/a/b/c./**'))
+        self.assertFalse(P('a/b/c.py').match('/a/b/c.py/**'))
+        self.assertFalse(P('a/b/c.py').match('/**/a/b/c.py'))
+        self.assertRaises(ValueError, P('a').match, '**a/b/c')
+        self.assertRaises(ValueError, P('a').match, 'a/b/c**')
+        # Case-sensitive flag
+        self.assertFalse(P('A.py').match('a.PY', case_sensitive=True))
+        self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
+        self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
+        self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
+        # Matching against empty path
+        self.assertFalse(P().match('*'))
+        self.assertTrue(P().match('**'))
+        self.assertFalse(P().match('**/*'))
+
+    def test_parts_common(self):
+        # `parts` returns a tuple.
+        sep = self.sep
+        P = self.cls
+        p = P('a/b')
+        parts = p.parts
+        self.assertEqual(parts, ('a', 'b'))
+        # When the path is absolute, the anchor is a separate part.
+        p = P('/a/b')
+        parts = p.parts
+        self.assertEqual(parts, (sep, 'a', 'b'))
+
+    def test_equivalences(self):
+        for k, tuples in self.equivalences.items():
+            canon = k.replace('/', self.sep)
+            posix = k.replace(self.sep, '/')
+            if canon != posix:
+                tuples = tuples + [
+                    tuple(part.replace('/', self.sep) for part in t)
+                    for t in tuples
+                    ]
+                tuples.append((posix, ))
+            pcanon = self.cls(canon)
+            for t in tuples:
+                p = self.cls(*t)
+                self.assertEqual(p, pcanon, "failed with args {}".format(t))
+                self.assertEqual(hash(p), hash(pcanon))
+                self.assertEqual(str(p), canon)
+                self.assertEqual(p.as_posix(), posix)
+
+    def test_parent_common(self):
+        # Relative
+        P = self.cls
+        p = P('a/b/c')
+        self.assertEqual(p.parent, P('a/b'))
+        self.assertEqual(p.parent.parent, P('a'))
+        self.assertEqual(p.parent.parent.parent, P())
+        self.assertEqual(p.parent.parent.parent.parent, P())
+        # Anchored
+        p = P('/a/b/c')
+        self.assertEqual(p.parent, P('/a/b'))
+        self.assertEqual(p.parent.parent, P('/a'))
+        self.assertEqual(p.parent.parent.parent, P('/'))
+        self.assertEqual(p.parent.parent.parent.parent, P('/'))
+
+    def test_parents_common(self):
+        # Relative
+        P = self.cls
+        p = P('a/b/c')
+        par = p.parents
+        self.assertEqual(len(par), 3)
+        self.assertEqual(par[0], P('a/b'))
+        self.assertEqual(par[1], P('a'))
+        self.assertEqual(par[2], P('.'))
+        self.assertEqual(par[-1], P('.'))
+        self.assertEqual(par[-2], P('a'))
+        self.assertEqual(par[-3], P('a/b'))
+        self.assertEqual(par[0:1], (P('a/b'),))
+        self.assertEqual(par[:2], (P('a/b'), P('a')))
+        self.assertEqual(par[:-1], (P('a/b'), P('a')))
+        self.assertEqual(par[1:], (P('a'), P('.')))
+        self.assertEqual(par[::2], (P('a/b'), P('.')))
+        self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b')))
+        self.assertEqual(list(par), [P('a/b'), P('a'), P('.')])
+        with self.assertRaises(IndexError):
+            par[-4]
+        with self.assertRaises(IndexError):
+            par[3]
+        with self.assertRaises(TypeError):
+            par[0] = p
+        # Anchored
+        p = P('/a/b/c')
+        par = p.parents
+        self.assertEqual(len(par), 3)
+        self.assertEqual(par[0], P('/a/b'))
+        self.assertEqual(par[1], P('/a'))
+        self.assertEqual(par[2], P('/'))
+        self.assertEqual(par[-1], P('/'))
+        self.assertEqual(par[-2], P('/a'))
+        self.assertEqual(par[-3], P('/a/b'))
+        self.assertEqual(par[0:1], (P('/a/b'),))
+        self.assertEqual(par[:2], (P('/a/b'), P('/a')))
+        self.assertEqual(par[:-1], (P('/a/b'), P('/a')))
+        self.assertEqual(par[1:], (P('/a'), P('/')))
+        self.assertEqual(par[::2], (P('/a/b'), P('/')))
+        self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b')))
+        self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')])
+        with self.assertRaises(IndexError):
+            par[-4]
+        with self.assertRaises(IndexError):
+            par[3]
+
+    def test_drive_common(self):
+        P = self.cls
+        self.assertEqual(P('a/b').drive, '')
+        self.assertEqual(P('/a/b').drive, '')
+        self.assertEqual(P('').drive, '')
+
+    def test_root_common(self):
+        P = self.cls
+        sep = self.sep
+        self.assertEqual(P('').root, '')
+        self.assertEqual(P('a/b').root, '')
+        self.assertEqual(P('/').root, sep)
+        self.assertEqual(P('/a/b').root, sep)
+
+    def test_anchor_common(self):
+        P = self.cls
+        sep = self.sep
+        self.assertEqual(P('').anchor, '')
+        self.assertEqual(P('a/b').anchor, '')
+        self.assertEqual(P('/').anchor, sep)
+        self.assertEqual(P('/a/b').anchor, sep)
+
+    def test_name_common(self):
+        P = self.cls
+        self.assertEqual(P('').name, '')
+        self.assertEqual(P('.').name, '')
+        self.assertEqual(P('/').name, '')
+        self.assertEqual(P('a/b').name, 'b')
+        self.assertEqual(P('/a/b').name, 'b')
+        self.assertEqual(P('/a/b/.').name, 'b')
+        self.assertEqual(P('a/b.py').name, 'b.py')
+        self.assertEqual(P('/a/b.py').name, 'b.py')
+
+    def test_suffix_common(self):
+        P = self.cls
+        self.assertEqual(P('').suffix, '')
+        self.assertEqual(P('.').suffix, '')
+        self.assertEqual(P('..').suffix, '')
+        self.assertEqual(P('/').suffix, '')
+        self.assertEqual(P('a/b').suffix, '')
+        self.assertEqual(P('/a/b').suffix, '')
+        self.assertEqual(P('/a/b/.').suffix, '')
+        self.assertEqual(P('a/b.py').suffix, '.py')
+        self.assertEqual(P('/a/b.py').suffix, '.py')
+        self.assertEqual(P('a/.hgrc').suffix, '')
+        self.assertEqual(P('/a/.hgrc').suffix, '')
+        self.assertEqual(P('a/.hg.rc').suffix, '.rc')
+        self.assertEqual(P('/a/.hg.rc').suffix, '.rc')
+        self.assertEqual(P('a/b.tar.gz').suffix, '.gz')
+        self.assertEqual(P('/a/b.tar.gz').suffix, '.gz')
+        self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '')
+        self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '')
+
+    def test_suffixes_common(self):
+        P = self.cls
+        self.assertEqual(P('').suffixes, [])
+        self.assertEqual(P('.').suffixes, [])
+        self.assertEqual(P('/').suffixes, [])
+        self.assertEqual(P('a/b').suffixes, [])
+        self.assertEqual(P('/a/b').suffixes, [])
+        self.assertEqual(P('/a/b/.').suffixes, [])
+        self.assertEqual(P('a/b.py').suffixes, ['.py'])
+        self.assertEqual(P('/a/b.py').suffixes, ['.py'])
+        self.assertEqual(P('a/.hgrc').suffixes, [])
+        self.assertEqual(P('/a/.hgrc').suffixes, [])
+        self.assertEqual(P('a/.hg.rc').suffixes, ['.rc'])
+        self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc'])
+        self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz'])
+        self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz'])
+        self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, [])
+        self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, [])
+
+    def test_stem_common(self):
+        P = self.cls
+        self.assertEqual(P('').stem, '')
+        self.assertEqual(P('.').stem, '')
+        self.assertEqual(P('..').stem, '..')
+        self.assertEqual(P('/').stem, '')
+        self.assertEqual(P('a/b').stem, 'b')
+        self.assertEqual(P('a/b.py').stem, 'b')
+        self.assertEqual(P('a/.hgrc').stem, '.hgrc')
+        self.assertEqual(P('a/.hg.rc').stem, '.hg')
+        self.assertEqual(P('a/b.tar.gz').stem, 'b.tar')
+        self.assertEqual(P('a/Some name. Ending with a dot.').stem,
+                         'Some name. Ending with a dot.')
+
+    def test_with_name_common(self):
+        P = self.cls
+        self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml'))
+        self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml'))
+        self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml'))
+        self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml'))
+        self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml'))
+        self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml'))
+        self.assertRaises(ValueError, P('').with_name, 'd.xml')
+        self.assertRaises(ValueError, P('.').with_name, 'd.xml')
+        self.assertRaises(ValueError, P('/').with_name, 'd.xml')
+        self.assertRaises(ValueError, P('a/b').with_name, '')
+        self.assertRaises(ValueError, P('a/b').with_name, '.')
+        self.assertRaises(ValueError, P('a/b').with_name, '/c')
+        self.assertRaises(ValueError, P('a/b').with_name, 'c/')
+        self.assertRaises(ValueError, P('a/b').with_name, 'c/d')
+
+    def test_with_stem_common(self):
+        P = self.cls
+        self.assertEqual(P('a/b').with_stem('d'), P('a/d'))
+        self.assertEqual(P('/a/b').with_stem('d'), P('/a/d'))
+        self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py'))
+        self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py'))
+        self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz'))
+        self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d'))
+        self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d'))
+        self.assertRaises(ValueError, P('').with_stem, 'd')
+        self.assertRaises(ValueError, P('.').with_stem, 'd')
+        self.assertRaises(ValueError, P('/').with_stem, 'd')
+        self.assertRaises(ValueError, P('a/b').with_stem, '')
+        self.assertRaises(ValueError, P('a/b').with_stem, '.')
+        self.assertRaises(ValueError, P('a/b').with_stem, '/c')
+        self.assertRaises(ValueError, P('a/b').with_stem, 'c/')
+        self.assertRaises(ValueError, P('a/b').with_stem, 'c/d')
+
+    def test_with_suffix_common(self):
+        P = self.cls
+        self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz'))
+        self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz'))
+        self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz'))
+        self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz'))
+        # Stripping suffix.
+        self.assertEqual(P('a/b.py').with_suffix(''), P('a/b'))
+        self.assertEqual(P('/a/b').with_suffix(''), P('/a/b'))
+        # Path doesn't have a "filename" component.
+        self.assertRaises(ValueError, P('').with_suffix, '.gz')
+        self.assertRaises(ValueError, P('.').with_suffix, '.gz')
+        self.assertRaises(ValueError, P('/').with_suffix, '.gz')
+        # Invalid suffix.
+        self.assertRaises(ValueError, P('a/b').with_suffix, 'gz')
+        self.assertRaises(ValueError, P('a/b').with_suffix, '/')
+        self.assertRaises(ValueError, P('a/b').with_suffix, '.')
+        self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz')
+        self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d')
+        self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d')
+        self.assertRaises(ValueError, P('a/b').with_suffix, './.d')
+        self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.')
+
+    def test_relative_to_common(self):
+        P = self.cls
+        p = P('a/b')
+        self.assertRaises(TypeError, p.relative_to)
+        self.assertRaises(TypeError, p.relative_to, b'a')
+        self.assertEqual(p.relative_to(P()), P('a/b'))
+        self.assertEqual(p.relative_to(''), P('a/b'))
+        self.assertEqual(p.relative_to(P('a')), P('b'))
+        self.assertEqual(p.relative_to('a'), P('b'))
+        self.assertEqual(p.relative_to('a/'), P('b'))
+        self.assertEqual(p.relative_to(P('a/b')), P())
+        self.assertEqual(p.relative_to('a/b'), P())
+        self.assertEqual(p.relative_to(P(), walk_up=True), P('a/b'))
+        self.assertEqual(p.relative_to('', walk_up=True), P('a/b'))
+        self.assertEqual(p.relative_to(P('a'), walk_up=True), P('b'))
+        self.assertEqual(p.relative_to('a', walk_up=True), P('b'))
+        self.assertEqual(p.relative_to('a/', walk_up=True), P('b'))
+        self.assertEqual(p.relative_to(P('a/b'), walk_up=True), P())
+        self.assertEqual(p.relative_to('a/b', walk_up=True), P())
+        self.assertEqual(p.relative_to(P('a/c'), walk_up=True), P('../b'))
+        self.assertEqual(p.relative_to('a/c', walk_up=True), P('../b'))
+        self.assertEqual(p.relative_to(P('a/b/c'), walk_up=True), P('..'))
+        self.assertEqual(p.relative_to('a/b/c', walk_up=True), P('..'))
+        self.assertEqual(p.relative_to(P('c'), walk_up=True), P('../a/b'))
+        self.assertEqual(p.relative_to('c', walk_up=True), P('../a/b'))
+        # With several args.
+        with self.assertWarns(DeprecationWarning):
+            p.relative_to('a', 'b')
+            p.relative_to('a', 'b', walk_up=True)
+        # Unrelated paths.
+        self.assertRaises(ValueError, p.relative_to, P('c'))
+        self.assertRaises(ValueError, p.relative_to, P('a/b/c'))
+        self.assertRaises(ValueError, p.relative_to, P('a/c'))
+        self.assertRaises(ValueError, p.relative_to, P('/a'))
+        self.assertRaises(ValueError, p.relative_to, P("../a"))
+        self.assertRaises(ValueError, p.relative_to, P("a/.."))
+        self.assertRaises(ValueError, p.relative_to, P("/a/.."))
+        self.assertRaises(ValueError, p.relative_to, P('/'), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P('/a'), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True)
+        p = P('/a/b')
+        self.assertEqual(p.relative_to(P('/')), P('a/b'))
+        self.assertEqual(p.relative_to('/'), P('a/b'))
+        self.assertEqual(p.relative_to(P('/a')), P('b'))
+        self.assertEqual(p.relative_to('/a'), P('b'))
+        self.assertEqual(p.relative_to('/a/'), P('b'))
+        self.assertEqual(p.relative_to(P('/a/b')), P())
+        self.assertEqual(p.relative_to('/a/b'), P())
+        self.assertEqual(p.relative_to(P('/'), walk_up=True), P('a/b'))
+        self.assertEqual(p.relative_to('/', walk_up=True), P('a/b'))
+        self.assertEqual(p.relative_to(P('/a'), walk_up=True), P('b'))
+        self.assertEqual(p.relative_to('/a', walk_up=True), P('b'))
+        self.assertEqual(p.relative_to('/a/', walk_up=True), P('b'))
+        self.assertEqual(p.relative_to(P('/a/b'), walk_up=True), P())
+        self.assertEqual(p.relative_to('/a/b', walk_up=True), P())
+        self.assertEqual(p.relative_to(P('/a/c'), walk_up=True), P('../b'))
+        self.assertEqual(p.relative_to('/a/c', walk_up=True), P('../b'))
+        self.assertEqual(p.relative_to(P('/a/b/c'), walk_up=True), P('..'))
+        self.assertEqual(p.relative_to('/a/b/c', walk_up=True), P('..'))
+        self.assertEqual(p.relative_to(P('/c'), walk_up=True), P('../a/b'))
+        self.assertEqual(p.relative_to('/c', walk_up=True), P('../a/b'))
+        # Unrelated paths.
+        self.assertRaises(ValueError, p.relative_to, P('/c'))
+        self.assertRaises(ValueError, p.relative_to, P('/a/b/c'))
+        self.assertRaises(ValueError, p.relative_to, P('/a/c'))
+        self.assertRaises(ValueError, p.relative_to, P())
+        self.assertRaises(ValueError, p.relative_to, '')
+        self.assertRaises(ValueError, p.relative_to, P('a'))
+        self.assertRaises(ValueError, p.relative_to, P("../a"))
+        self.assertRaises(ValueError, p.relative_to, P("a/.."))
+        self.assertRaises(ValueError, p.relative_to, P("/a/.."))
+        self.assertRaises(ValueError, p.relative_to, P(''), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P('a'), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P("../a"), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True)
+        self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True)
+
+    def test_is_relative_to_common(self):
+        P = self.cls
+        p = P('a/b')
+        self.assertRaises(TypeError, p.is_relative_to)
+        self.assertRaises(TypeError, p.is_relative_to, b'a')
+        self.assertTrue(p.is_relative_to(P()))
+        self.assertTrue(p.is_relative_to(''))
+        self.assertTrue(p.is_relative_to(P('a')))
+        self.assertTrue(p.is_relative_to('a/'))
+        self.assertTrue(p.is_relative_to(P('a/b')))
+        self.assertTrue(p.is_relative_to('a/b'))
+        # With several args.
+        with self.assertWarns(DeprecationWarning):
+            p.is_relative_to('a', 'b')
+        # Unrelated paths.
+        self.assertFalse(p.is_relative_to(P('c')))
+        self.assertFalse(p.is_relative_to(P('a/b/c')))
+        self.assertFalse(p.is_relative_to(P('a/c')))
+        self.assertFalse(p.is_relative_to(P('/a')))
+        p = P('/a/b')
+        self.assertTrue(p.is_relative_to(P('/')))
+        self.assertTrue(p.is_relative_to('/'))
+        self.assertTrue(p.is_relative_to(P('/a')))
+        self.assertTrue(p.is_relative_to('/a'))
+        self.assertTrue(p.is_relative_to('/a/'))
+        self.assertTrue(p.is_relative_to(P('/a/b')))
+        self.assertTrue(p.is_relative_to('/a/b'))
+        # Unrelated paths.
+        self.assertFalse(p.is_relative_to(P('/c')))
+        self.assertFalse(p.is_relative_to(P('/a/b/c')))
+        self.assertFalse(p.is_relative_to(P('/a/c')))
+        self.assertFalse(p.is_relative_to(P()))
+        self.assertFalse(p.is_relative_to(''))
+        self.assertFalse(p.is_relative_to(P('a')))
+
+
+#
+# Tests for the virtual classes.
+#
+
+class PathBaseTest(PurePathBaseTest):
+    cls = pathlib._abc.PathBase
+
+    def test_unsupported_operation(self):
+        P = self.cls
+        p = self.cls()
+        e = pathlib.UnsupportedOperation
+        self.assertRaises(e, p.stat)
+        self.assertRaises(e, p.lstat)
+        self.assertRaises(e, p.exists)
+        self.assertRaises(e, p.samefile, 'foo')
+        self.assertRaises(e, p.is_dir)
+        self.assertRaises(e, p.is_file)
+        self.assertRaises(e, p.is_mount)
+        self.assertRaises(e, p.is_symlink)
+        self.assertRaises(e, p.is_block_device)
+        self.assertRaises(e, p.is_char_device)
+        self.assertRaises(e, p.is_fifo)
+        self.assertRaises(e, p.is_socket)
+        self.assertRaises(e, p.open)
+        self.assertRaises(e, p.read_bytes)
+        self.assertRaises(e, p.read_text)
+        self.assertRaises(e, p.write_bytes, b'foo')
+        self.assertRaises(e, p.write_text, 'foo')
+        self.assertRaises(e, p.iterdir)
+        self.assertRaises(e, p.glob, '*')
+        self.assertRaises(e, p.rglob, '*')
+        self.assertRaises(e, lambda: list(p.walk()))
+        self.assertRaises(e, p.absolute)
+        self.assertRaises(e, P.cwd)
+        self.assertRaises(e, p.expanduser)
+        self.assertRaises(e, p.home)
+        self.assertRaises(e, p.readlink)
+        self.assertRaises(e, p.symlink_to, 'foo')
+        self.assertRaises(e, p.hardlink_to, 'foo')
+        self.assertRaises(e, p.mkdir)
+        self.assertRaises(e, p.touch)
+        self.assertRaises(e, p.rename, 'foo')
+        self.assertRaises(e, p.replace, 'foo')
+        self.assertRaises(e, p.chmod, 0o755)
+        self.assertRaises(e, p.lchmod, 0o755)
+        self.assertRaises(e, p.unlink)
+        self.assertRaises(e, p.rmdir)
+        self.assertRaises(e, p.owner)
+        self.assertRaises(e, p.group)
+        self.assertRaises(e, p.as_uri)
+
+    def test_as_uri_common(self):
+        e = pathlib.UnsupportedOperation
+        self.assertRaises(e, self.cls().as_uri)
+
+    def test_fspath_common(self):
+        self.assertRaises(TypeError, os.fspath, self.cls())
+
+    def test_as_bytes_common(self):
+        self.assertRaises(TypeError, bytes, self.cls())
+
+    def test_matches_path_api(self):
+        our_names = {name for name in dir(self.cls) if name[0] != '_'}
+        path_names = {name for name in dir(pathlib.Path) if name[0] != '_'}
+        self.assertEqual(our_names, path_names)
+        for attr_name in our_names:
+            our_attr = getattr(self.cls, attr_name)
+            path_attr = getattr(pathlib.Path, attr_name)
+            self.assertEqual(our_attr.__doc__, path_attr.__doc__)
+
+
+class DummyPathIO(io.BytesIO):
+    """
+    Used by DummyPath to implement `open('w')`
+    """
+
+    def __init__(self, files, path):
+        super().__init__()
+        self.files = files
+        self.path = path
+
+    def close(self):
+        self.files[self.path] = self.getvalue()
+        super().close()
+
+
+class DummyPath(pathlib._abc.PathBase):
+    """
+    Simple implementation of PathBase that keeps files and directories in
+    memory.
+    """
+    _files = {}
+    _directories = {}
+    _symlinks = {}
+
+    def __eq__(self, other):
+        if not isinstance(other, DummyPath):
+            return NotImplemented
+        return str(self) == str(other)
+
+    def __hash__(self):
+        return hash(str(self))
+
+    def stat(self, *, follow_symlinks=True):
+        if follow_symlinks:
+            path = str(self.resolve())
+        else:
+            path = str(self.parent.resolve() / self.name)
+        if path in self._files:
+            st_mode = stat.S_IFREG
+        elif path in self._directories:
+            st_mode = stat.S_IFDIR
+        elif path in self._symlinks:
+            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))
+
+    def open(self, mode='r', buffering=-1, encoding=None,
+             errors=None, newline=None):
+        if buffering != -1:
+            raise NotImplementedError
+        path_obj = self.resolve()
+        path = str(path_obj)
+        name = path_obj.name
+        parent = str(path_obj.parent)
+        if path in self._directories:
+            raise IsADirectoryError(errno.EISDIR, "Is a directory", path)
+
+        text = 'b' not in mode
+        mode = ''.join(c for c in mode if c not in 'btU')
+        if mode == 'r':
+            if path not in self._files:
+                raise FileNotFoundError(errno.ENOENT, "File not found", path)
+            stream = io.BytesIO(self._files[path])
+        elif mode == 'w':
+            if parent not in self._directories:
+                raise FileNotFoundError(errno.ENOENT, "File not found", parent)
+            stream = DummyPathIO(self._files, path)
+            self._files[path] = b''
+            self._directories[parent].add(name)
+        else:
+            raise NotImplementedError
+        if text:
+            stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline)
+        return stream
+
+    def iterdir(self):
+        path = str(self.resolve())
+        if path in self._files:
+            raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path)
+        elif path in self._directories:
+            return (self / name for name in self._directories[path])
+        else:
+            raise FileNotFoundError(errno.ENOENT, "File not found", path)
+
+    def mkdir(self, mode=0o777, parents=False, exist_ok=False):
+        path = str(self.resolve())
+        if path in self._directories:
+            if exist_ok:
+                return
+            else:
+                raise FileExistsError(errno.EEXIST, "File exists", path)
+        try:
+            if self.name:
+                self._directories[str(self.parent)].add(self.name)
+            self._directories[path] = set()
+        except KeyError:
+            if not parents:
+                raise FileNotFoundError(errno.ENOENT, "File not found", str(self.parent)) from None
+            self.parent.mkdir(parents=True, exist_ok=True)
+            self.mkdir(mode, parents=False, exist_ok=exist_ok)
+
+
+class DummyPathTest(DummyPurePathTest):
+    """Tests for PathBase methods that use stat(), open() and iterdir()."""
+
+    cls = DummyPath
+    can_symlink = False
+
+    # (BASE)
+    #  |
+    #  |-- brokenLink -> non-existing
+    #  |-- dirA
+    #  |   `-- linkC -> ../dirB
+    #  |-- dirB
+    #  |   |-- fileB
+    #  |   `-- linkD -> ../dirB
+    #  |-- dirC
+    #  |   |-- dirD
+    #  |   |   `-- fileD
+    #  |   `-- fileC
+    #  |   `-- novel.txt
+    #  |-- dirE  # No permissions
+    #  |-- fileA
+    #  |-- linkA -> fileA
+    #  |-- linkB -> dirB
+    #  `-- brokenLinkLoop -> brokenLinkLoop
+    #
+
+    def setUp(self):
+        super().setUp()
+        pathmod = self.cls.pathmod
+        p = self.cls(BASE)
+        p.mkdir(parents=True)
+        p.joinpath('dirA').mkdir()
+        p.joinpath('dirB').mkdir()
+        p.joinpath('dirC').mkdir()
+        p.joinpath('dirC', 'dirD').mkdir()
+        p.joinpath('dirE').mkdir()
+        with p.joinpath('fileA').open('wb') as f:
+            f.write(b"this is file A\n")
+        with p.joinpath('dirB', 'fileB').open('wb') as f:
+            f.write(b"this is file B\n")
+        with p.joinpath('dirC', 'fileC').open('wb') as f:
+            f.write(b"this is file C\n")
+        with p.joinpath('dirC', 'novel.txt').open('wb') as f:
+            f.write(b"this is a novel\n")
+        with p.joinpath('dirC', 'dirD', 'fileD').open('wb') as f:
+            f.write(b"this is file D\n")
+        if self.can_symlink:
+            p.joinpath('linkA').symlink_to('fileA')
+            p.joinpath('brokenLink').symlink_to('non-existing')
+            p.joinpath('linkB').symlink_to('dirB')
+            p.joinpath('dirA', 'linkC').symlink_to(pathmod.join('..', 'dirB'))
+            p.joinpath('dirB', 'linkD').symlink_to(pathmod.join('..', 'dirB'))
+            p.joinpath('brokenLinkLoop').symlink_to('brokenLinkLoop')
+
+    def tearDown(self):
+        cls = self.cls
+        cls._files.clear()
+        cls._directories.clear()
+        cls._symlinks.clear()
+
+    def tempdir(self):
+        path = self.cls(BASE).with_name('tmp-dirD')
+        path.mkdir()
+        return path
+
+    def assertFileNotFound(self, func, *args, **kwargs):
+        with self.assertRaises(FileNotFoundError) as cm:
+            func(*args, **kwargs)
+        self.assertEqual(cm.exception.errno, errno.ENOENT)
+
+    def assertEqualNormCase(self, path_a, path_b):
+        self.assertEqual(os.path.normcase(path_a), os.path.normcase(path_b))
+
+    def test_samefile(self):
+        fileA_path = os.path.join(BASE, 'fileA')
+        fileB_path = os.path.join(BASE, 'dirB', 'fileB')
+        p = self.cls(fileA_path)
+        pp = self.cls(fileA_path)
+        q = self.cls(fileB_path)
+        self.assertTrue(p.samefile(fileA_path))
+        self.assertTrue(p.samefile(pp))
+        self.assertFalse(p.samefile(fileB_path))
+        self.assertFalse(p.samefile(q))
+        # Test the non-existent file case
+        non_existent = os.path.join(BASE, 'foo')
+        r = self.cls(non_existent)
+        self.assertRaises(FileNotFoundError, p.samefile, r)
+        self.assertRaises(FileNotFoundError, p.samefile, non_existent)
+        self.assertRaises(FileNotFoundError, r.samefile, p)
+        self.assertRaises(FileNotFoundError, r.samefile, non_existent)
+        self.assertRaises(FileNotFoundError, r.samefile, r)
+        self.assertRaises(FileNotFoundError, r.samefile, non_existent)
+
+    def test_empty_path(self):
+        # The empty path points to '.'
+        p = self.cls('')
+        self.assertEqual(str(p), '.')
+
+    def test_exists(self):
+        P = self.cls
+        p = P(BASE)
+        self.assertIs(True, p.exists())
+        self.assertIs(True, (p / 'dirA').exists())
+        self.assertIs(True, (p / 'fileA').exists())
+        self.assertIs(False, (p / 'fileA' / 'bah').exists())
+        if self.can_symlink:
+            self.assertIs(True, (p / 'linkA').exists())
+            self.assertIs(True, (p / 'linkB').exists())
+            self.assertIs(True, (p / 'linkB' / 'fileB').exists())
+            self.assertIs(False, (p / 'linkA' / 'bah').exists())
+            self.assertIs(False, (p / 'brokenLink').exists())
+            self.assertIs(True, (p / 'brokenLink').exists(follow_symlinks=False))
+        self.assertIs(False, (p / 'foo').exists())
+        self.assertIs(False, P('/xyzzy').exists())
+        self.assertIs(False, P(BASE + '\udfff').exists())
+        self.assertIs(False, P(BASE + '\x00').exists())
+
+    def test_open_common(self):
+        p = self.cls(BASE)
+        with (p / 'fileA').open('r') as f:
+            self.assertIsInstance(f, io.TextIOBase)
+            self.assertEqual(f.read(), "this is file A\n")
+        with (p / 'fileA').open('rb') as f:
+            self.assertIsInstance(f, io.BufferedIOBase)
+            self.assertEqual(f.read().strip(), b"this is file A")
+
+    def test_read_write_bytes(self):
+        p = self.cls(BASE)
+        (p / 'fileA').write_bytes(b'abcdefg')
+        self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg')
+        # Check that trying to write str does not truncate the file.
+        self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr')
+        self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg')
+
+    def test_read_write_text(self):
+        p = self.cls(BASE)
+        (p / 'fileA').write_text('äbcdefg', encoding='latin-1')
+        self.assertEqual((p / 'fileA').read_text(
+            encoding='utf-8', errors='ignore'), 'bcdefg')
+        # Check that trying to write bytes does not truncate the file.
+        self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes')
+        self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg')
+
+    def test_read_text_with_newlines(self):
+        p = self.cls(BASE)
+        # Check that `\n` character change nothing
+        (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
+        self.assertEqual((p / 'fileA').read_text(newline='\n'),
+                         'abcde\r\nfghlk\n\rmnopq')
+        # Check that `\r` character replaces `\n`
+        (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
+        self.assertEqual((p / 'fileA').read_text(newline='\r'),
+                         'abcde\r\nfghlk\n\rmnopq')
+        # Check that `\r\n` character replaces `\n`
+        (p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
+        self.assertEqual((p / 'fileA').read_text(newline='\r\n'),
+                             'abcde\r\nfghlk\n\rmnopq')
+
+    def test_write_text_with_newlines(self):
+        p = self.cls(BASE)
+        # Check that `\n` character change nothing
+        (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n')
+        self.assertEqual((p / 'fileA').read_bytes(),
+                         b'abcde\r\nfghlk\n\rmnopq')
+        # Check that `\r` character replaces `\n`
+        (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r')
+        self.assertEqual((p / 'fileA').read_bytes(),
+                         b'abcde\r\rfghlk\r\rmnopq')
+        # Check that `\r\n` character replaces `\n`
+        (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n')
+        self.assertEqual((p / 'fileA').read_bytes(),
+                         b'abcde\r\r\nfghlk\r\n\rmnopq')
+        # Check that no argument passed will change `\n` to `os.linesep`
+        os_linesep_byte = bytes(os.linesep, encoding='ascii')
+        (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq')
+        self.assertEqual((p / 'fileA').read_bytes(),
+                          b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq')
+
+    def test_iterdir(self):
+        P = self.cls
+        p = P(BASE)
+        it = p.iterdir()
+        paths = set(it)
+        expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA']
+        if self.can_symlink:
+            expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
+        self.assertEqual(paths, { P(BASE, q) for q in expected })
+
+    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')
+        paths = set(p.iterdir())
+        expected = { P(BASE, 'linkB', q) for q in ['fileB', 'linkD'] }
+        self.assertEqual(paths, expected)
+
+    def test_iterdir_nodir(self):
+        # __iter__ on something that is not a directory.
+        p = self.cls(BASE, 'fileA')
+        with self.assertRaises(OSError) as cm:
+            p.iterdir()
+        # ENOENT or EINVAL under Windows, ENOTDIR otherwise
+        # (see issue #12802).
+        self.assertIn(cm.exception.errno, (errno.ENOTDIR,
+                                           errno.ENOENT, errno.EINVAL))
+
+    def test_glob_common(self):
+        def _check(glob, expected):
+            self.assertEqual(set(glob), { P(BASE, q) for q in expected })
+        P = self.cls
+        p = P(BASE)
+        it = p.glob("fileA")
+        self.assertIsInstance(it, collections.abc.Iterator)
+        _check(it, ["fileA"])
+        _check(p.glob("fileB"), [])
+        _check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"])
+        if not self.can_symlink:
+            _check(p.glob("*A"), ['dirA', 'fileA'])
+        else:
+            _check(p.glob("*A"), ['dirA', 'fileA', 'linkA'])
+        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 self.can_symlink:
+            _check(p.glob("*/fileB"), ['dirB/fileB'])
+        else:
+            _check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
+        if self.can_symlink:
+            _check(p.glob("brokenLink"), ['brokenLink'])
+
+        if not self.can_symlink:
+            _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"])
+        else:
+            _check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
+
+    def test_glob_empty_pattern(self):
+        p = self.cls()
+        with self.assertRaisesRegex(ValueError, 'Unacceptable pattern'):
+            list(p.glob(''))
+
+    def test_glob_case_sensitive(self):
+        P = self.cls
+        def _check(path, pattern, case_sensitive, expected):
+            actual = {str(q) for q in path.glob(pattern, case_sensitive=case_sensitive)}
+            expected = {str(P(BASE, q)) for q in expected}
+            self.assertEqual(actual, expected)
+        path = P(BASE)
+        _check(path, "DIRB/FILE*", True, [])
+        _check(path, "DIRB/FILE*", False, ["dirB/fileB"])
+        _check(path, "dirb/file*", True, [])
+        _check(path, "dirb/file*", False, ["dirB/fileB"])
+
+    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.
+            self.assertEqual(actual, { P(BASE, q) for q in expected })
+        P = self.cls
+        p = P(BASE)
+        _check(p, "fileB", [])
+        _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
+        _check(p, "*A", ["dirA", "fileA", "linkA"])
+        _check(p, "*B/*", ["dirB/fileB", "dirB/linkD", "linkB/fileB", "linkB/linkD"])
+        _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
+        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
+        _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/.."])
+        _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
+                               "dirC/", "dirC/dirD/", "dirE/"])
+        _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
+                                 "dirC/..", "dirC/dirD/..", "dirE/.."])
+        _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
+        _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirC/dirD/.."])
+        _check(p, "dir*/**/fileC", ["dirC/fileC"])
+        _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
+        _check(p, "*/dirD/**/", ["dirC/dirD/"])
+
+    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 })
+        P = self.cls
+        p = P(BASE)
+        _check(p, "fileB", [])
+        _check(p, "dir*/file*", ["dirB/fileB", "dirC/fileC"])
+        _check(p, "*A", ["dirA", "fileA", "linkA"])
+        _check(p, "*B/*", ["dirB/fileB", "dirB/linkD"])
+        _check(p, "*/fileB", ["dirB/fileB"])
+        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"])
+        _check(p, "dir*/*/..", ["dirC/dirD/.."])
+        _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
+        _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."])
+        _check(p, "dir*/*/**/", ["dirC/dirD/"])
+        _check(p, "dir*/*/**/..", ["dirC/dirD/.."])
+        _check(p, "dir*/**/fileC", ["dirC/fileC"])
+        _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
+        _check(p, "*/dirD/**/", ["dirC/dirD/"])
+
+    def test_rglob_common(self):
+        def _check(glob, expected):
+            self.assertEqual(set(glob), {P(BASE, q) for q in expected})
+        P = self.cls
+        p = P(BASE)
+        it = p.rglob("fileA")
+        self.assertIsInstance(it, collections.abc.Iterator)
+        _check(it, ["fileA"])
+        _check(p.rglob("fileB"), ["dirB/fileB"])
+        _check(p.rglob("**/fileB"), ["dirB/fileB"])
+        _check(p.rglob("*/fileA"), [])
+        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 self.can_symlink:
+            _check(p.rglob("*/"), [
+                "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/",
+            ])
+        else:
+            _check(p.rglob("*/"), [
+                "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/",
+                "dirC/dirD/", "dirE/", "linkB/",
+            ])
+        _check(p.rglob(""), ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"])
+
+        p = P(BASE, "dirC")
+        _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt",
+                              "dirC/dirD", "dirC/dirD/fileD"])
+        _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
+        _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"])
+        _check(p.rglob("dir*/**/"), ["dirC/dirD/"])
+        _check(p.rglob("*/*"), ["dirC/dirD/fileD"])
+        _check(p.rglob("*/"), ["dirC/dirD/"])
+        _check(p.rglob(""), ["dirC/", "dirC/dirD/"])
+        _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
+        # gh-91616, a re module regression
+        _check(p.rglob("*.txt"), ["dirC/novel.txt"])
+        _check(p.rglob("*.*"), ["dirC/novel.txt"])
+
+    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.
+            self.assertEqual(actual, { P(BASE, q) for q in expected })
+        P = self.cls
+        p = P(BASE)
+        _check(p, "fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"])
+        _check(p, "*/fileA", [])
+        _check(p, "*/fileB", ["dirB/fileB", "dirA/linkC/fileB", "linkB/fileB"])
+        _check(p, "file*", ["fileA", "dirA/linkC/fileB", "dirB/fileB",
+                            "dirC/fileC", "dirC/dirD/fileD", "linkB/fileB"])
+        _check(p, "*/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
+                         "dirC/", "dirC/dirD/", "dirE/", "linkB/", "linkB/linkD/"])
+        _check(p, "", ["./", "dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
+                       "dirC/", "dirE/", "dirC/dirD/", "linkB/", "linkB/linkD/"])
+
+        p = P(BASE, "dirC")
+        _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
+                        "dirC/dirD", "dirC/dirD/fileD"])
+        _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
+        _check(p, "*/*", ["dirC/dirD/fileD"])
+        _check(p, "*/", ["dirC/dirD/"])
+        _check(p, "", ["dirC/", "dirC/dirD/"])
+        # gh-91616, a re module regression
+        _check(p, "*.txt", ["dirC/novel.txt"])
+        _check(p, "*.*", ["dirC/novel.txt"])
+
+    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 })
+        P = self.cls
+        p = P(BASE)
+        _check(p, "fileB", ["dirB/fileB"])
+        _check(p, "*/fileA", [])
+        _check(p, "*/fileB", ["dirB/fileB"])
+        _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD", ])
+        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
+        _check(p, "", ["./", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"])
+
+        p = P(BASE, "dirC")
+        _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
+                        "dirC/dirD", "dirC/dirD/fileD"])
+        _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
+        _check(p, "*/*", ["dirC/dirD/fileD"])
+        _check(p, "*/", ["dirC/dirD/"])
+        _check(p, "", ["dirC/", "dirC/dirD/"])
+        # gh-91616, a re module regression
+        _check(p, "*.txt", ["dirC/novel.txt"])
+        _check(p, "*.*", ["dirC/novel.txt"])
+
+    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('*'))
+        expect = {'brokenLink',
+                  'dirA', 'dirA/linkC',
+                  'dirB', 'dirB/fileB', 'dirB/linkD',
+                  'dirC', 'dirC/dirD', 'dirC/dirD/fileD',
+                  'dirC/fileC', 'dirC/novel.txt',
+                  'dirE',
+                  'fileA',
+                  'linkA',
+                  'linkB',
+                  'brokenLinkLoop',
+                  }
+        self.assertEqual(given, {p / x for x in expect})
+
+    def test_glob_many_open_files(self):
+        depth = 30
+        P = self.cls
+        p = base = P(BASE) / 'deep'
+        p.mkdir()
+        for _ in range(depth):
+            p /= 'd'
+            p.mkdir()
+        pattern = '/'.join(['*'] * depth)
+        iters = [base.glob(pattern) for j in range(100)]
+        for it in iters:
+            self.assertEqual(next(it), p)
+        iters = [base.rglob('d') for j in range(100)]
+        p = base
+        for i in range(depth):
+            p = p / 'd'
+            for it in iters:
+                self.assertEqual(next(it), p)
+
+    def test_glob_dotdot(self):
+        # ".." is not special in globs.
+        P = self.cls
+        p = P(BASE)
+        self.assertEqual(set(p.glob("..")), { P(BASE, "..") })
+        self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") })
+        self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") })
+        self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") })
+        self.assertEqual(set(p.glob("dirA/../file*/..")), set())
+        self.assertEqual(set(p.glob("../xyzzy")), set())
+        self.assertEqual(set(p.glob("xyzzy/..")), set())
+        self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)})
+
+    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()
+
+        for i in range(100):
+            link = base / f"link{i}"
+            if i % 2:
+                link.symlink_to(P(BASE, "dirE", "nonexistent"))
+            else:
+                link.symlink_to(P(BASE, "dirC"))
+
+        self.assertEqual(len(set(base.glob("*"))), 100)
+        self.assertEqual(len(set(base.glob("*/"))), 50)
+        self.assertEqual(len(set(base.glob("*/fileC"))), 50)
+        self.assertEqual(len(set(base.glob("*/file*"))), 50)
+
+    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'
+        bad_link.symlink_to("bad" * 200)
+        self.assertEqual(sorted(base.glob('**/*')), [bad_link])
+
+    def test_glob_above_recursion_limit(self):
+        recursion_limit = 50
+        # directory_depth > recursion_limit
+        directory_depth = recursion_limit + 10
+        base = self.cls(BASE, 'deep')
+        path = base.joinpath(*(['d'] * directory_depth))
+        path.mkdir(parents=True)
+
+        with set_recursion_limit(recursion_limit):
+            list(base.glob('**/'))
+
+    def test_glob_recursive_no_trailing_slash(self):
+        P = self.cls
+        p = P(BASE)
+        with self.assertWarns(FutureWarning):
+            p.glob('**')
+        with self.assertWarns(FutureWarning):
+            p.glob('*/**')
+        with self.assertWarns(FutureWarning):
+            p.rglob('**')
+        with self.assertWarns(FutureWarning):
+            p.rglob('*/**')
+
+
+    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(),
+                         self.cls('non-existing'))
+        self.assertEqual((P / 'linkB').readlink(), self.cls('dirB'))
+        self.assertEqual((P / 'linkB' / 'linkD').readlink(), self.cls('../dirB'))
+        with self.assertRaises(OSError):
+            (P / 'fileA').readlink()
+
+    @unittest.skipIf(hasattr(os, "readlink"), "os.readlink() is present")
+    def test_readlink_unsupported(self):
+        P = self.cls(BASE)
+        p = P / 'fileA'
+        with self.assertRaises(pathlib.UnsupportedOperation):
+            q.readlink(p)
+
+    def _check_resolve(self, p, expected, strict=True):
+        q = p.resolve(strict)
+        self.assertEqual(q, expected)
+
+    # This can be used to check both relative and absolute resolutions.
+    _check_resolve_relative = _check_resolve_absolute = _check_resolve
+
+    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:
+            p.resolve(strict=True)
+        self.assertEqual(cm.exception.errno, errno.ENOENT)
+        # Non-strict
+        self.assertEqualNormCase(str(p.resolve(strict=False)),
+                                 os.path.join(BASE, 'foo'))
+        p = P(BASE, 'foo', 'in', 'spam')
+        self.assertEqualNormCase(str(p.resolve(strict=False)),
+                                 os.path.join(BASE, 'foo', 'in', 'spam'))
+        p = P(BASE, '..', 'foo', 'in', 'spam')
+        self.assertEqualNormCase(str(p.resolve(strict=False)),
+                                 os.path.abspath(os.path.join('foo', 'in', 'spam')))
+        # These are all relative symlinks.
+        p = P(BASE, 'dirB', 'fileB')
+        self._check_resolve_relative(p, p)
+        p = P(BASE, 'linkA')
+        self._check_resolve_relative(p, P(BASE, 'fileA'))
+        p = P(BASE, 'dirA', 'linkC', 'fileB')
+        self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
+        p = P(BASE, 'dirB', 'linkD', 'fileB')
+        self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB'))
+        # Non-strict
+        p = P(BASE, 'dirA', 'linkC', 'fileB', 'foo', 'in', 'spam')
+        self._check_resolve_relative(p, P(BASE, 'dirB', 'fileB', 'foo', 'in',
+                                          'spam'), False)
+        p = P(BASE, 'dirA', 'linkC', '..', 'foo', 'in', 'spam')
+        if os.name == 'nt' and isinstance(p, pathlib.Path):
+            # In Windows, if linkY points to dirB, 'dirA\linkY\..'
+            # resolves to 'dirA' without resolving linkY first.
+            self._check_resolve_relative(p, P(BASE, 'dirA', 'foo', 'in',
+                                              'spam'), False)
+        else:
+            # In Posix, if linkY points to dirB, 'dirA/linkY/..'
+            # resolves to 'dirB/..' first before resolving to parent of dirB.
+            self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False)
+        # Now create absolute symlinks.
+        d = self.tempdir()
+        P(BASE, 'dirA', 'linkX').symlink_to(d)
+        P(BASE, str(d), 'linkY').symlink_to(join('dirB'))
+        p = P(BASE, 'dirA', 'linkX', 'linkY', 'fileB')
+        self._check_resolve_absolute(p, P(BASE, 'dirB', 'fileB'))
+        # Non-strict
+        p = P(BASE, 'dirA', 'linkX', 'linkY', 'foo', 'in', 'spam')
+        self._check_resolve_relative(p, P(BASE, 'dirB', 'foo', 'in', 'spam'),
+                                     False)
+        p = P(BASE, 'dirA', 'linkX', 'linkY', '..', 'foo', 'in', 'spam')
+        if os.name == 'nt' and isinstance(p, pathlib.Path):
+            # In Windows, if linkY points to dirB, 'dirA\linkY\..'
+            # resolves to 'dirA' without resolving linkY first.
+            self._check_resolve_relative(p, P(d, 'foo', 'in', 'spam'), False)
+        else:
+            # In Posix, if linkY points to dirB, 'dirA/linkY/..'
+            # resolves to 'dirB/..' first before resolving to parent of dirB.
+            self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False)
+
+    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)
+        p.joinpath('0').symlink_to('.', target_is_directory=True)
+        p.joinpath('1').symlink_to(os.path.join('0', '0'), target_is_directory=True)
+        p.joinpath('2').symlink_to(os.path.join('1', '1'), target_is_directory=True)
+        q = p / '2'
+        self.assertEqual(q.resolve(strict=True), p)
+        r = q / '3' / '4'
+        self.assertRaises(FileNotFoundError, r.resolve, strict=True)
+        # Non-strict
+        self.assertEqual(r.resolve(strict=False), p / '3' / '4')
+
+    def _check_symlink_loop(self, *args):
+        path = self.cls(*args)
+        with self.assertRaises(OSError) as cm:
+            path.resolve(strict=True)
+        self.assertEqual(cm.exception.errno, errno.ELOOP)
+
+    def test_resolve_loop(self):
+        if not self.can_symlink:
+            self.skipTest("symlinks required")
+        if os.name == 'nt' and issubclass(self.cls, pathlib.Path):
+            self.skipTest("symlink loops work differently with concrete Windows paths")
+        # Loops with relative symlinks.
+        self.cls(BASE, 'linkX').symlink_to('linkX/inside')
+        self._check_symlink_loop(BASE, 'linkX')
+        self.cls(BASE, 'linkY').symlink_to('linkY')
+        self._check_symlink_loop(BASE, 'linkY')
+        self.cls(BASE, 'linkZ').symlink_to('linkZ/../linkZ')
+        self._check_symlink_loop(BASE, 'linkZ')
+        # Non-strict
+        p = self.cls(BASE, 'linkZ', 'foo')
+        self.assertEqual(p.resolve(strict=False), p)
+        # Loops with absolute symlinks.
+        self.cls(BASE, 'linkU').symlink_to(join('linkU/inside'))
+        self._check_symlink_loop(BASE, 'linkU')
+        self.cls(BASE, 'linkV').symlink_to(join('linkV'))
+        self._check_symlink_loop(BASE, 'linkV')
+        self.cls(BASE, 'linkW').symlink_to(join('linkW/../linkW'))
+        self._check_symlink_loop(BASE, 'linkW')
+        # Non-strict
+        q = self.cls(BASE, 'linkW', 'foo')
+        self.assertEqual(q.resolve(strict=False), q)
+
+    def test_stat(self):
+        statA = self.cls(BASE).joinpath('fileA').stat()
+        statB = self.cls(BASE).joinpath('dirB', 'fileB').stat()
+        statC = self.cls(BASE).joinpath('dirC').stat()
+        # st_mode: files are the same, directory differs.
+        self.assertIsInstance(statA.st_mode, int)
+        self.assertEqual(statA.st_mode, statB.st_mode)
+        self.assertNotEqual(statA.st_mode, statC.st_mode)
+        self.assertNotEqual(statB.st_mode, statC.st_mode)
+        # st_ino: all different,
+        self.assertIsInstance(statA.st_ino, int)
+        self.assertNotEqual(statA.st_ino, statB.st_ino)
+        self.assertNotEqual(statA.st_ino, statC.st_ino)
+        self.assertNotEqual(statB.st_ino, statC.st_ino)
+        # st_dev: all the same.
+        self.assertIsInstance(statA.st_dev, int)
+        self.assertEqual(statA.st_dev, statB.st_dev)
+        self.assertEqual(statA.st_dev, statC.st_dev)
+        # other attributes not used by pathlib.
+
+    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))
+
+    def test_stat_no_follow_symlinks_nosymlink(self):
+        p = self.cls(BASE) / 'fileA'
+        st = p.stat()
+        self.assertEqual(st, p.stat(follow_symlinks=False))
+
+    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())
+
+    def test_lstat_nosymlink(self):
+        p = self.cls(BASE) / 'fileA'
+        st = p.stat()
+        self.assertEqual(st, p.lstat())
+
+    def test_is_dir(self):
+        P = self.cls(BASE)
+        self.assertTrue((P / 'dirA').is_dir())
+        self.assertFalse((P / 'fileA').is_dir())
+        self.assertFalse((P / 'non-existing').is_dir())
+        self.assertFalse((P / 'fileA' / 'bah').is_dir())
+        if self.can_symlink:
+            self.assertFalse((P / 'linkA').is_dir())
+            self.assertTrue((P / 'linkB').is_dir())
+            self.assertFalse((P/ 'brokenLink').is_dir())
+        self.assertFalse((P / 'dirA\udfff').is_dir())
+        self.assertFalse((P / 'dirA\x00').is_dir())
+
+    def test_is_dir_no_follow_symlinks(self):
+        P = self.cls(BASE)
+        self.assertTrue((P / 'dirA').is_dir(follow_symlinks=False))
+        self.assertFalse((P / 'fileA').is_dir(follow_symlinks=False))
+        self.assertFalse((P / 'non-existing').is_dir(follow_symlinks=False))
+        self.assertFalse((P / 'fileA' / 'bah').is_dir(follow_symlinks=False))
+        if self.can_symlink:
+            self.assertFalse((P / 'linkA').is_dir(follow_symlinks=False))
+            self.assertFalse((P / 'linkB').is_dir(follow_symlinks=False))
+            self.assertFalse((P/ 'brokenLink').is_dir(follow_symlinks=False))
+        self.assertFalse((P / 'dirA\udfff').is_dir(follow_symlinks=False))
+        self.assertFalse((P / 'dirA\x00').is_dir(follow_symlinks=False))
+
+    def test_is_file(self):
+        P = self.cls(BASE)
+        self.assertTrue((P / 'fileA').is_file())
+        self.assertFalse((P / 'dirA').is_file())
+        self.assertFalse((P / 'non-existing').is_file())
+        self.assertFalse((P / 'fileA' / 'bah').is_file())
+        if self.can_symlink:
+            self.assertTrue((P / 'linkA').is_file())
+            self.assertFalse((P / 'linkB').is_file())
+            self.assertFalse((P/ 'brokenLink').is_file())
+        self.assertFalse((P / 'fileA\udfff').is_file())
+        self.assertFalse((P / 'fileA\x00').is_file())
+
+    def test_is_file_no_follow_symlinks(self):
+        P = self.cls(BASE)
+        self.assertTrue((P / 'fileA').is_file(follow_symlinks=False))
+        self.assertFalse((P / 'dirA').is_file(follow_symlinks=False))
+        self.assertFalse((P / 'non-existing').is_file(follow_symlinks=False))
+        self.assertFalse((P / 'fileA' / 'bah').is_file(follow_symlinks=False))
+        if self.can_symlink:
+            self.assertFalse((P / 'linkA').is_file(follow_symlinks=False))
+            self.assertFalse((P / 'linkB').is_file(follow_symlinks=False))
+            self.assertFalse((P/ 'brokenLink').is_file(follow_symlinks=False))
+        self.assertFalse((P / 'fileA\udfff').is_file(follow_symlinks=False))
+        self.assertFalse((P / 'fileA\x00').is_file(follow_symlinks=False))
+
+    def test_is_mount(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_mount())
+        self.assertFalse((P / 'dirA').is_mount())
+        self.assertFalse((P / 'non-existing').is_mount())
+        self.assertFalse((P / 'fileA' / 'bah').is_mount())
+        if self.can_symlink:
+            self.assertFalse((P / 'linkA').is_mount())
+
+    def test_is_symlink(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_symlink())
+        self.assertFalse((P / 'dirA').is_symlink())
+        self.assertFalse((P / 'non-existing').is_symlink())
+        self.assertFalse((P / 'fileA' / 'bah').is_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 self.can_symlink:
+            self.assertIs((P / 'linkA\udfff').is_file(), False)
+            self.assertIs((P / 'linkA\x00').is_file(), False)
+
+    def test_is_junction_false(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_junction())
+        self.assertFalse((P / 'dirA').is_junction())
+        self.assertFalse((P / 'non-existing').is_junction())
+        self.assertFalse((P / 'fileA' / 'bah').is_junction())
+        self.assertFalse((P / 'fileA\udfff').is_junction())
+        self.assertFalse((P / 'fileA\x00').is_junction())
+
+    def test_is_fifo_false(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_fifo())
+        self.assertFalse((P / 'dirA').is_fifo())
+        self.assertFalse((P / 'non-existing').is_fifo())
+        self.assertFalse((P / 'fileA' / 'bah').is_fifo())
+        self.assertIs((P / 'fileA\udfff').is_fifo(), False)
+        self.assertIs((P / 'fileA\x00').is_fifo(), False)
+
+    def test_is_socket_false(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_socket())
+        self.assertFalse((P / 'dirA').is_socket())
+        self.assertFalse((P / 'non-existing').is_socket())
+        self.assertFalse((P / 'fileA' / 'bah').is_socket())
+        self.assertIs((P / 'fileA\udfff').is_socket(), False)
+        self.assertIs((P / 'fileA\x00').is_socket(), False)
+
+    def test_is_block_device_false(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_block_device())
+        self.assertFalse((P / 'dirA').is_block_device())
+        self.assertFalse((P / 'non-existing').is_block_device())
+        self.assertFalse((P / 'fileA' / 'bah').is_block_device())
+        self.assertIs((P / 'fileA\udfff').is_block_device(), False)
+        self.assertIs((P / 'fileA\x00').is_block_device(), False)
+
+    def test_is_char_device_false(self):
+        P = self.cls(BASE)
+        self.assertFalse((P / 'fileA').is_char_device())
+        self.assertFalse((P / 'dirA').is_char_device())
+        self.assertFalse((P / 'non-existing').is_char_device())
+        self.assertFalse((P / 'fileA' / 'bah').is_char_device())
+        self.assertIs((P / 'fileA\udfff').is_char_device(), False)
+        self.assertIs((P / 'fileA\x00').is_char_device(), False)
+
+    def test_pickling_common(self):
+        p = self.cls(BASE, 'fileA')
+        for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
+            dumped = pickle.dumps(p, proto)
+            pp = pickle.loads(dumped)
+            self.assertEqual(pp.stat(), p.stat())
+
+    def test_parts_interning(self):
+        P = self.cls
+        p = P('/usr/bin/foo')
+        q = P('/usr/local/bin')
+        # 'usr'
+        self.assertIs(p.parts[1], q.parts[1])
+        # 'bin'
+        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)
+        P.joinpath('link1').symlink_to(os.path.join('link0', 'link0'), target_is_directory=True)
+        P.joinpath('link2').symlink_to(os.path.join('link1', 'link1'), target_is_directory=True)
+        P.joinpath('link3').symlink_to(os.path.join('link2', 'link2'), target_is_directory=True)
+        P.joinpath('link0').symlink_to(link0_target, target_is_directory=True)
+
+        # Resolve absolute paths.
+        p = (P / 'link0').resolve()
+        self.assertEqual(p, P)
+        self.assertEqualNormCase(str(p), BASE)
+        p = (P / 'link1').resolve()
+        self.assertEqual(p, P)
+        self.assertEqualNormCase(str(p), BASE)
+        p = (P / 'link2').resolve()
+        self.assertEqual(p, P)
+        self.assertEqualNormCase(str(p), BASE)
+        p = (P / 'link3').resolve()
+        self.assertEqual(p, P)
+        self.assertEqualNormCase(str(p), BASE)
+
+        # Resolve relative paths.
+        try:
+            self.cls().absolute()
+        except pathlib.UnsupportedOperation:
+            return
+        old_path = os.getcwd()
+        os.chdir(BASE)
+        try:
+            p = self.cls('link0').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), BASE)
+            p = self.cls('link1').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), BASE)
+            p = self.cls('link2').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), BASE)
+            p = self.cls('link3').resolve()
+            self.assertEqual(p, P)
+            self.assertEqualNormCase(str(p), BASE)
+        finally:
+            os.chdir(old_path)
+
+    def test_complex_symlinks_absolute(self):
+        self._check_complex_symlinks(BASE)
+
+    def test_complex_symlinks_relative(self):
+        self._check_complex_symlinks('.')
+
+    def test_complex_symlinks_relative_dot_dot(self):
+        self._check_complex_symlinks(os.path.join('dirA', '..'))
+
+    def setUpWalk(self):
+        # Build:
+        #     TESTFN/
+        #       TEST1/              a file kid and two directory kids
+        #         tmp1
+        #         SUB1/             a file kid and a directory kid
+        #           tmp2
+        #           SUB11/          no kids
+        #         SUB2/             a file kid and a dirsymlink kid
+        #           tmp3
+        #           link/           a symlink to TEST2
+        #           broken_link
+        #           broken_link2
+        #       TEST2/
+        #         tmp4              a lone file
+        self.walk_path = self.cls(BASE, "TEST1")
+        self.sub1_path = self.walk_path / "SUB1"
+        self.sub11_path = self.sub1_path / "SUB11"
+        self.sub2_path = self.walk_path / "SUB2"
+        tmp1_path = self.walk_path / "tmp1"
+        tmp2_path = self.sub1_path / "tmp2"
+        tmp3_path = self.sub2_path / "tmp3"
+        self.link_path = self.sub2_path / "link"
+        t2_path = self.cls(BASE, "TEST2")
+        tmp4_path = self.cls(BASE, "TEST2", "tmp4")
+        broken_link_path = self.sub2_path / "broken_link"
+        broken_link2_path = self.sub2_path / "broken_link2"
+
+        self.sub11_path.mkdir(parents=True)
+        self.sub2_path.mkdir(parents=True)
+        t2_path.mkdir(parents=True)
+
+        for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path:
+            with path.open("w", encoding='utf-8') as f:
+                f.write(f"I'm {path} and proud of it.  Blame test_pathlib.\n")
+
+        if self.can_symlink:
+            self.link_path.symlink_to(t2_path)
+            broken_link_path.symlink_to('broken')
+            broken_link2_path.symlink_to(self.cls('tmp3', 'broken'))
+            self.sub2_tree = (self.sub2_path, [], ["broken_link", "broken_link2", "link", "tmp3"])
+        else:
+            self.sub2_tree = (self.sub2_path, [], ["tmp3"])
+
+    def test_walk_topdown(self):
+        self.setUpWalk()
+        walker = self.walk_path.walk()
+        entry = next(walker)
+        entry[1].sort()  # Ensure we visit SUB1 before SUB2
+        self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"]))
+        entry = next(walker)
+        self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"]))
+        entry = next(walker)
+        self.assertEqual(entry, (self.sub11_path, [], []))
+        entry = next(walker)
+        entry[1].sort()
+        entry[2].sort()
+        self.assertEqual(entry, self.sub2_tree)
+        with self.assertRaises(StopIteration):
+            next(walker)
+
+    def test_walk_prune(self):
+        self.setUpWalk()
+        # Prune the search.
+        all = []
+        for root, dirs, files in self.walk_path.walk():
+            all.append((root, dirs, files))
+            if 'SUB1' in dirs:
+                # Note that this also mutates the dirs we appended to all!
+                dirs.remove('SUB1')
+
+        self.assertEqual(len(all), 2)
+        self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"]))
+
+        all[1][-1].sort()
+        all[1][1].sort()
+        self.assertEqual(all[1], self.sub2_tree)
+
+    def test_walk_bottom_up(self):
+        self.setUpWalk()
+        seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False
+        for path, dirnames, filenames in self.walk_path.walk(top_down=False):
+            if path == self.walk_path:
+                self.assertFalse(seen_testfn)
+                self.assertTrue(seen_sub1)
+                self.assertTrue(seen_sub2)
+                self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"])
+                self.assertEqual(filenames, ["tmp1"])
+                seen_testfn = True
+            elif path == self.sub1_path:
+                self.assertFalse(seen_testfn)
+                self.assertFalse(seen_sub1)
+                self.assertTrue(seen_sub11)
+                self.assertEqual(dirnames, ["SUB11"])
+                self.assertEqual(filenames, ["tmp2"])
+                seen_sub1 = True
+            elif path == self.sub11_path:
+                self.assertFalse(seen_sub1)
+                self.assertFalse(seen_sub11)
+                self.assertEqual(dirnames, [])
+                self.assertEqual(filenames, [])
+                seen_sub11 = True
+            elif path == self.sub2_path:
+                self.assertFalse(seen_testfn)
+                self.assertFalse(seen_sub2)
+                self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1]))
+                self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2]))
+                seen_sub2 = True
+            else:
+                raise AssertionError(f"Unexpected path: {path}")
+        self.assertTrue(seen_testfn)
+
+    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:
+            if root == self.link_path:
+                self.assertEqual(dirs, [])
+                self.assertEqual(files, ["tmp4"])
+                break
+        else:
+            self.fail("Didn't follow symlink with follow_symlinks=True")
+
+    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.
+        walk_it = self.walk_path.walk(follow_symlinks=False)
+        for root, dirs, files in walk_it:
+            if root == self.sub2_path:
+                self.assertIn("link", files)
+                break
+        else:
+            self.fail("symlink not found")
+
+        walk_it = self.walk_path.walk(follow_symlinks=True)
+        for root, dirs, files in walk_it:
+            if root == self.sub2_path:
+                self.assertIn("link", dirs)
+                break
+        else:
+            self.fail("symlink not found")
+
+    def test_walk_above_recursion_limit(self):
+        recursion_limit = 40
+        # directory_depth > recursion_limit
+        directory_depth = recursion_limit + 10
+        base = self.cls(BASE, 'deep')
+        path = base.joinpath(*(['d'] * directory_depth))
+        path.mkdir(parents=True)
+
+        with set_recursion_limit(recursion_limit):
+            list(base.walk())
+            list(base.walk(top_down=False))
+
+class DummyPathWithSymlinks(DummyPath):
+    def readlink(self):
+        path = str(self.parent.resolve() / self.name)
+        if path in self._symlinks:
+            return self.with_segments(self._symlinks[path])
+        elif path in self._files or path in self._directories:
+            raise OSError(errno.EINVAL, "Not a symlink", path)
+        else:
+            raise FileNotFoundError(errno.ENOENT, "File not found", path)
+
+    def symlink_to(self, target, target_is_directory=False):
+        self._directories[str(self.parent)].add(self.name)
+        self._symlinks[str(self)] = str(target)
+
+
+class DummyPathWithSymlinksTest(DummyPathTest):
+    cls = DummyPathWithSymlinks
+    can_symlink = True
+
+
+if __name__ == "__main__":
+    unittest.main()
index 5fb6ffc4e8f0cfc89aa995eeb2a464f4a489986a..195fc0ddddecd30f8fce7bef90ff112ccebf41bc 100644 (file)
@@ -2272,6 +2272,7 @@ TESTSUBDIRS=      idlelib/idle_test \
                test/test_importlib/source \
                test/test_json \
                test/test_module \
+               test/test_pathlib \
                test/test_peg_generator \
                test/test_sqlite3 \
                test/test_tkinter \