• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import posixpath
3import sys
4import unittest
5from functools import partial
6from posixpath import realpath, abspath, dirname, basename, ALLOW_MISSING
7from test import support
8from test import test_genericpath
9from test.support import import_helper
10from test.support import os_helper
11from test.support.os_helper import FakePath
12from unittest import mock
13
14try:
15    import posix
16except ImportError:
17    posix = None
18
19
20# An absolute path to a temporary filename for testing. We can't rely on TESTFN
21# being an absolute path, so we need this.
22
23ABSTFN = abspath(os_helper.TESTFN)
24
25def skip_if_ABSTFN_contains_backslash(test):
26    """
27    On Windows, posixpath.abspath still returns paths with backslashes
28    instead of posix forward slashes. If this is the case, several tests
29    fail, so skip them.
30    """
31    found_backslash = '\\' in ABSTFN
32    msg = "ABSTFN is not a posix path - tests fail"
33    return [test, unittest.skip(msg)(test)][found_backslash]
34
35def safe_rmdir(dirname):
36    try:
37        os.rmdir(dirname)
38    except OSError:
39        pass
40
41def _parameterize(*parameters):
42    """Simplistic decorator to parametrize a test
43
44    Runs the decorated test multiple times in subTest, with a value from
45    'parameters' passed as an extra positional argument.
46    Does *not* call doCleanups() after each run.
47
48    Not for general use. Intended to avoid indenting for easier backports.
49
50    See https://discuss.python.org/t/91827 for discussing generalizations.
51    """
52    def _parametrize_decorator(func):
53        def _parameterized(self, *args, **kwargs):
54            for parameter in parameters:
55                with self.subTest(parameter):
56                    func(self, *args, parameter, **kwargs)
57        return _parameterized
58    return _parametrize_decorator
59
60
61class PosixPathTest(unittest.TestCase):
62
63    def setUp(self):
64        self.tearDown()
65
66    def tearDown(self):
67        for suffix in ["", "1", "2"]:
68            os_helper.unlink(os_helper.TESTFN + suffix)
69            safe_rmdir(os_helper.TESTFN + suffix)
70
71    def test_join(self):
72        self.assertEqual(posixpath.join("/foo", "bar", "/bar", "baz"),
73                         "/bar/baz")
74        self.assertEqual(posixpath.join("/foo", "bar", "baz"), "/foo/bar/baz")
75        self.assertEqual(posixpath.join("/foo/", "bar/", "baz/"),
76                         "/foo/bar/baz/")
77
78        self.assertEqual(posixpath.join(b"/foo", b"bar", b"/bar", b"baz"),
79                         b"/bar/baz")
80        self.assertEqual(posixpath.join(b"/foo", b"bar", b"baz"),
81                         b"/foo/bar/baz")
82        self.assertEqual(posixpath.join(b"/foo/", b"bar/", b"baz/"),
83                         b"/foo/bar/baz/")
84
85    def test_split(self):
86        self.assertEqual(posixpath.split("/foo/bar"), ("/foo", "bar"))
87        self.assertEqual(posixpath.split("/"), ("/", ""))
88        self.assertEqual(posixpath.split("foo"), ("", "foo"))
89        self.assertEqual(posixpath.split("////foo"), ("////", "foo"))
90        self.assertEqual(posixpath.split("//foo//bar"), ("//foo", "bar"))
91
92        self.assertEqual(posixpath.split(b"/foo/bar"), (b"/foo", b"bar"))
93        self.assertEqual(posixpath.split(b"/"), (b"/", b""))
94        self.assertEqual(posixpath.split(b"foo"), (b"", b"foo"))
95        self.assertEqual(posixpath.split(b"////foo"), (b"////", b"foo"))
96        self.assertEqual(posixpath.split(b"//foo//bar"), (b"//foo", b"bar"))
97
98    def splitextTest(self, path, filename, ext):
99        self.assertEqual(posixpath.splitext(path), (filename, ext))
100        self.assertEqual(posixpath.splitext("/" + path), ("/" + filename, ext))
101        self.assertEqual(posixpath.splitext("abc/" + path),
102                         ("abc/" + filename, ext))
103        self.assertEqual(posixpath.splitext("abc.def/" + path),
104                         ("abc.def/" + filename, ext))
105        self.assertEqual(posixpath.splitext("/abc.def/" + path),
106                         ("/abc.def/" + filename, ext))
107        self.assertEqual(posixpath.splitext(path + "/"),
108                         (filename + ext + "/", ""))
109
110        path = bytes(path, "ASCII")
111        filename = bytes(filename, "ASCII")
112        ext = bytes(ext, "ASCII")
113
114        self.assertEqual(posixpath.splitext(path), (filename, ext))
115        self.assertEqual(posixpath.splitext(b"/" + path),
116                         (b"/" + filename, ext))
117        self.assertEqual(posixpath.splitext(b"abc/" + path),
118                         (b"abc/" + filename, ext))
119        self.assertEqual(posixpath.splitext(b"abc.def/" + path),
120                         (b"abc.def/" + filename, ext))
121        self.assertEqual(posixpath.splitext(b"/abc.def/" + path),
122                         (b"/abc.def/" + filename, ext))
123        self.assertEqual(posixpath.splitext(path + b"/"),
124                         (filename + ext + b"/", b""))
125
126    def test_splitext(self):
127        self.splitextTest("foo.bar", "foo", ".bar")
128        self.splitextTest("foo.boo.bar", "foo.boo", ".bar")
129        self.splitextTest("foo.boo.biff.bar", "foo.boo.biff", ".bar")
130        self.splitextTest(".csh.rc", ".csh", ".rc")
131        self.splitextTest("nodots", "nodots", "")
132        self.splitextTest(".cshrc", ".cshrc", "")
133        self.splitextTest("...manydots", "...manydots", "")
134        self.splitextTest("...manydots.ext", "...manydots", ".ext")
135        self.splitextTest(".", ".", "")
136        self.splitextTest("..", "..", "")
137        self.splitextTest("........", "........", "")
138        self.splitextTest("", "", "")
139
140    def test_isabs(self):
141        self.assertIs(posixpath.isabs(""), False)
142        self.assertIs(posixpath.isabs("/"), True)
143        self.assertIs(posixpath.isabs("/foo"), True)
144        self.assertIs(posixpath.isabs("/foo/bar"), True)
145        self.assertIs(posixpath.isabs("foo/bar"), False)
146
147        self.assertIs(posixpath.isabs(b""), False)
148        self.assertIs(posixpath.isabs(b"/"), True)
149        self.assertIs(posixpath.isabs(b"/foo"), True)
150        self.assertIs(posixpath.isabs(b"/foo/bar"), True)
151        self.assertIs(posixpath.isabs(b"foo/bar"), False)
152
153    def test_basename(self):
154        self.assertEqual(posixpath.basename("/foo/bar"), "bar")
155        self.assertEqual(posixpath.basename("/"), "")
156        self.assertEqual(posixpath.basename("foo"), "foo")
157        self.assertEqual(posixpath.basename("////foo"), "foo")
158        self.assertEqual(posixpath.basename("//foo//bar"), "bar")
159
160        self.assertEqual(posixpath.basename(b"/foo/bar"), b"bar")
161        self.assertEqual(posixpath.basename(b"/"), b"")
162        self.assertEqual(posixpath.basename(b"foo"), b"foo")
163        self.assertEqual(posixpath.basename(b"////foo"), b"foo")
164        self.assertEqual(posixpath.basename(b"//foo//bar"), b"bar")
165
166    def test_dirname(self):
167        self.assertEqual(posixpath.dirname("/foo/bar"), "/foo")
168        self.assertEqual(posixpath.dirname("/"), "/")
169        self.assertEqual(posixpath.dirname("foo"), "")
170        self.assertEqual(posixpath.dirname("////foo"), "////")
171        self.assertEqual(posixpath.dirname("//foo//bar"), "//foo")
172
173        self.assertEqual(posixpath.dirname(b"/foo/bar"), b"/foo")
174        self.assertEqual(posixpath.dirname(b"/"), b"/")
175        self.assertEqual(posixpath.dirname(b"foo"), b"")
176        self.assertEqual(posixpath.dirname(b"////foo"), b"////")
177        self.assertEqual(posixpath.dirname(b"//foo//bar"), b"//foo")
178
179    def test_islink(self):
180        self.assertIs(posixpath.islink(os_helper.TESTFN + "1"), False)
181        self.assertIs(posixpath.lexists(os_helper.TESTFN + "2"), False)
182
183        with open(os_helper.TESTFN + "1", "wb") as f:
184            f.write(b"foo")
185        self.assertIs(posixpath.islink(os_helper.TESTFN + "1"), False)
186
187        if os_helper.can_symlink():
188            os.symlink(os_helper.TESTFN + "1", os_helper.TESTFN + "2")
189            self.assertIs(posixpath.islink(os_helper.TESTFN + "2"), True)
190            os.remove(os_helper.TESTFN + "1")
191            self.assertIs(posixpath.islink(os_helper.TESTFN + "2"), True)
192            self.assertIs(posixpath.exists(os_helper.TESTFN + "2"), False)
193            self.assertIs(posixpath.lexists(os_helper.TESTFN + "2"), True)
194
195        self.assertIs(posixpath.islink(os_helper.TESTFN + "\udfff"), False)
196        self.assertIs(posixpath.islink(os.fsencode(os_helper.TESTFN) + b"\xff"), False)
197        self.assertIs(posixpath.islink(os_helper.TESTFN + "\x00"), False)
198        self.assertIs(posixpath.islink(os.fsencode(os_helper.TESTFN) + b"\x00"), False)
199
200    def test_ismount(self):
201        self.assertIs(posixpath.ismount("/"), True)
202        self.assertIs(posixpath.ismount(b"/"), True)
203        self.assertIs(posixpath.ismount(FakePath("/")), True)
204        self.assertIs(posixpath.ismount(FakePath(b"/")), True)
205
206    def test_ismount_non_existent(self):
207        # Non-existent mountpoint.
208        self.assertIs(posixpath.ismount(ABSTFN), False)
209        try:
210            os.mkdir(ABSTFN)
211            self.assertIs(posixpath.ismount(ABSTFN), False)
212        finally:
213            safe_rmdir(ABSTFN)
214
215        self.assertIs(posixpath.ismount('/\udfff'), False)
216        self.assertIs(posixpath.ismount(b'/\xff'), False)
217        self.assertIs(posixpath.ismount('/\x00'), False)
218        self.assertIs(posixpath.ismount(b'/\x00'), False)
219
220    @os_helper.skip_unless_symlink
221    def test_ismount_symlinks(self):
222        # Symlinks are never mountpoints.
223        try:
224            os.symlink("/", ABSTFN)
225            self.assertIs(posixpath.ismount(ABSTFN), False)
226        finally:
227            os.unlink(ABSTFN)
228
229    @unittest.skipIf(posix is None, "Test requires posix module")
230    def test_ismount_different_device(self):
231        # Simulate the path being on a different device from its parent by
232        # mocking out st_dev.
233        save_lstat = os.lstat
234        def fake_lstat(path):
235            st_ino = 0
236            st_dev = 0
237            if path == ABSTFN:
238                st_dev = 1
239                st_ino = 1
240            return posix.stat_result((0, st_ino, st_dev, 0, 0, 0, 0, 0, 0, 0))
241        try:
242            os.lstat = fake_lstat
243            self.assertIs(posixpath.ismount(ABSTFN), True)
244        finally:
245            os.lstat = save_lstat
246
247    @unittest.skipIf(posix is None, "Test requires posix module")
248    def test_ismount_directory_not_readable(self):
249        # issue #2466: Simulate ismount run on a directory that is not
250        # readable, which used to return False.
251        save_lstat = os.lstat
252        def fake_lstat(path):
253            st_ino = 0
254            st_dev = 0
255            if path.startswith(ABSTFN) and path != ABSTFN:
256                # ismount tries to read something inside the ABSTFN directory;
257                # simulate this being forbidden (no read permission).
258                raise OSError("Fake [Errno 13] Permission denied")
259            if path == ABSTFN:
260                st_dev = 1
261                st_ino = 1
262            return posix.stat_result((0, st_ino, st_dev, 0, 0, 0, 0, 0, 0, 0))
263        try:
264            os.lstat = fake_lstat
265            self.assertIs(posixpath.ismount(ABSTFN), True)
266        finally:
267            os.lstat = save_lstat
268
269    def test_expanduser(self):
270        self.assertEqual(posixpath.expanduser("foo"), "foo")
271        self.assertEqual(posixpath.expanduser(b"foo"), b"foo")
272
273    def test_expanduser_home_envvar(self):
274        with os_helper.EnvironmentVarGuard() as env:
275            env['HOME'] = '/home/victor'
276            self.assertEqual(posixpath.expanduser("~"), "/home/victor")
277
278            # expanduser() strips trailing slash
279            env['HOME'] = '/home/victor/'
280            self.assertEqual(posixpath.expanduser("~"), "/home/victor")
281
282            for home in '/', '', '//', '///':
283                with self.subTest(home=home):
284                    env['HOME'] = home
285                    self.assertEqual(posixpath.expanduser("~"), "/")
286                    self.assertEqual(posixpath.expanduser("~/"), "/")
287                    self.assertEqual(posixpath.expanduser("~/foo"), "/foo")
288
289    @unittest.skipIf(sys.platform == "vxworks",
290                     "no home directory on VxWorks")
291    def test_expanduser_pwd(self):
292        pwd = import_helper.import_module('pwd')
293
294        self.assertIsInstance(posixpath.expanduser("~/"), str)
295        self.assertIsInstance(posixpath.expanduser(b"~/"), bytes)
296
297        # if home directory == root directory, this test makes no sense
298        if posixpath.expanduser("~") != '/':
299            self.assertEqual(
300                posixpath.expanduser("~") + "/",
301                posixpath.expanduser("~/")
302            )
303            self.assertEqual(
304                posixpath.expanduser(b"~") + b"/",
305                posixpath.expanduser(b"~/")
306            )
307        self.assertIsInstance(posixpath.expanduser("~root/"), str)
308        self.assertIsInstance(posixpath.expanduser("~foo/"), str)
309        self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes)
310        self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes)
311
312        with os_helper.EnvironmentVarGuard() as env:
313            # expanduser should fall back to using the password database
314            del env['HOME']
315
316            home = pwd.getpwuid(os.getuid()).pw_dir
317            # $HOME can end with a trailing /, so strip it (see #17809)
318            home = home.rstrip("/") or '/'
319            self.assertEqual(posixpath.expanduser("~"), home)
320
321            # bpo-10496: If the HOME environment variable is not set and the
322            # user (current identifier or name in the path) doesn't exist in
323            # the password database (pwd.getuid() or pwd.getpwnam() fail),
324            # expanduser() must return the path unchanged.
325            with mock.patch.object(pwd, 'getpwuid', side_effect=KeyError), \
326                 mock.patch.object(pwd, 'getpwnam', side_effect=KeyError):
327                for path in ('~', '~/.local', '~vstinner/'):
328                    self.assertEqual(posixpath.expanduser(path), path)
329
330    NORMPATH_CASES = [
331        ("", "."),
332        ("/", "/"),
333        ("/.", "/"),
334        ("/./", "/"),
335        ("/.//.", "/"),
336        ("/foo", "/foo"),
337        ("/foo/bar", "/foo/bar"),
338        ("//", "//"),
339        ("///", "/"),
340        ("///foo/.//bar//", "/foo/bar"),
341        ("///foo/.//bar//.//..//.//baz///", "/foo/baz"),
342        ("///..//./foo/.//bar", "/foo/bar"),
343        (".", "."),
344        (".//.", "."),
345        ("..", ".."),
346        ("../", ".."),
347        ("../foo", "../foo"),
348        ("../../foo", "../../foo"),
349        ("../foo/../bar", "../bar"),
350        ("../../foo/../bar/./baz/boom/..", "../../bar/baz"),
351        ("/..", "/"),
352        ("/..", "/"),
353        ("/../", "/"),
354        ("/..//", "/"),
355        ("//.", "//"),
356        ("//..", "//"),
357        ("//...", "//..."),
358        ("//../foo", "//foo"),
359        ("//../../foo", "//foo"),
360        ("/../foo", "/foo"),
361        ("/../../foo", "/foo"),
362        ("/../foo/../", "/"),
363        ("/../foo/../bar", "/bar"),
364        ("/../../foo/../bar/./baz/boom/..", "/bar/baz"),
365        ("/../../foo/../bar/./baz/boom/.", "/bar/baz/boom"),
366        ("foo/../bar/baz", "bar/baz"),
367        ("foo/../../bar/baz", "../bar/baz"),
368        ("foo/../../../bar/baz", "../../bar/baz"),
369        ("foo///../bar/.././../baz/boom", "../baz/boom"),
370        ("foo/bar/../..///../../baz/boom", "../../baz/boom"),
371        ("/foo/..", "/"),
372        ("/foo/../..", "/"),
373        ("//foo/..", "//"),
374        ("//foo/../..", "//"),
375        ("///foo/..", "/"),
376        ("///foo/../..", "/"),
377        ("////foo/..", "/"),
378        ("/////foo/..", "/"),
379    ]
380
381    def test_normpath(self):
382        for path, expected in self.NORMPATH_CASES:
383            with self.subTest(path):
384                result = posixpath.normpath(path)
385                self.assertEqual(result, expected)
386
387            path = path.encode('utf-8')
388            expected = expected.encode('utf-8')
389            with self.subTest(path, type=bytes):
390                result = posixpath.normpath(path)
391                self.assertEqual(result, expected)
392
393    @skip_if_ABSTFN_contains_backslash
394    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
395    def test_realpath_curdir(self, kwargs):
396        self.assertEqual(realpath('.', **kwargs), os.getcwd())
397        self.assertEqual(realpath('./.', **kwargs), os.getcwd())
398        self.assertEqual(realpath('/'.join(['.'] * 100), **kwargs), os.getcwd())
399
400        self.assertEqual(realpath(b'.', **kwargs), os.getcwdb())
401        self.assertEqual(realpath(b'./.', **kwargs), os.getcwdb())
402        self.assertEqual(realpath(b'/'.join([b'.'] * 100), **kwargs), os.getcwdb())
403
404    @skip_if_ABSTFN_contains_backslash
405    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
406    def test_realpath_pardir(self, kwargs):
407        self.assertEqual(realpath('..', **kwargs), dirname(os.getcwd()))
408        self.assertEqual(realpath('../..', **kwargs), dirname(dirname(os.getcwd())))
409        self.assertEqual(realpath('/'.join(['..'] * 100), **kwargs), '/')
410
411        self.assertEqual(realpath(b'..', **kwargs), dirname(os.getcwdb()))
412        self.assertEqual(realpath(b'../..', **kwargs), dirname(dirname(os.getcwdb())))
413        self.assertEqual(realpath(b'/'.join([b'..'] * 100), **kwargs), b'/')
414
415    @os_helper.skip_unless_symlink
416    @skip_if_ABSTFN_contains_backslash
417    @_parameterize({}, {'strict': ALLOW_MISSING})
418    def test_realpath_basic(self, kwargs):
419        # Basic operation.
420        try:
421            os.symlink(ABSTFN+"1", ABSTFN)
422            self.assertEqual(realpath(ABSTFN, **kwargs), ABSTFN+"1")
423        finally:
424            os_helper.unlink(ABSTFN)
425
426    @os_helper.skip_unless_symlink
427    @skip_if_ABSTFN_contains_backslash
428    def test_realpath_strict(self):
429        # Bug #43757: raise FileNotFoundError in strict mode if we encounter
430        # a path that does not exist.
431        try:
432            os.symlink(ABSTFN+"1", ABSTFN)
433            self.assertRaises(FileNotFoundError, realpath, ABSTFN, strict=True)
434            self.assertRaises(FileNotFoundError, realpath, ABSTFN + "2", strict=True)
435        finally:
436            os_helper.unlink(ABSTFN)
437
438    def test_realpath_invalid_paths(self):
439        path = '/\x00'
440        self.assertRaises(ValueError, realpath, path, strict=False)
441        self.assertRaises(ValueError, realpath, path, strict=True)
442        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
443        path = b'/\x00'
444        self.assertRaises(ValueError, realpath, path, strict=False)
445        self.assertRaises(ValueError, realpath, path, strict=True)
446        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
447        path = '/nonexistent/x\x00'
448        self.assertRaises(ValueError, realpath, path, strict=False)
449        self.assertRaises(FileNotFoundError, realpath, path, strict=True)
450        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
451        path = b'/nonexistent/x\x00'
452        self.assertRaises(ValueError, realpath, path, strict=False)
453        self.assertRaises(FileNotFoundError, realpath, path, strict=True)
454        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
455        path = '/\x00/..'
456        self.assertRaises(ValueError, realpath, path, strict=False)
457        self.assertRaises(ValueError, realpath, path, strict=True)
458        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
459        path = b'/\x00/..'
460        self.assertRaises(ValueError, realpath, path, strict=False)
461        self.assertRaises(ValueError, realpath, path, strict=True)
462        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
463
464        path = '/nonexistent/x\x00/..'
465        self.assertRaises(ValueError, realpath, path, strict=False)
466        self.assertRaises(FileNotFoundError, realpath, path, strict=True)
467        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
468        path = b'/nonexistent/x\x00/..'
469        self.assertRaises(ValueError, realpath, path, strict=False)
470        self.assertRaises(FileNotFoundError, realpath, path, strict=True)
471        self.assertRaises(ValueError, realpath, path, strict=ALLOW_MISSING)
472
473        path = '/\udfff'
474        if sys.platform == 'win32':
475            self.assertEqual(realpath(path, strict=False), path)
476            self.assertRaises(FileNotFoundError, realpath, path, strict=True)
477            self.assertEqual(realpath(path, strict=ALLOW_MISSING), path)
478        else:
479            self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
480            self.assertRaises(UnicodeEncodeError, realpath, path, strict=True)
481            self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
482        path = '/nonexistent/\udfff'
483        if sys.platform == 'win32':
484            self.assertEqual(realpath(path, strict=False), path)
485            self.assertEqual(realpath(path, strict=ALLOW_MISSING), path)
486        else:
487            self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
488            self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
489        self.assertRaises(FileNotFoundError, realpath, path, strict=True)
490        path = '/\udfff/..'
491        if sys.platform == 'win32':
492            self.assertEqual(realpath(path, strict=False), '/')
493            self.assertRaises(FileNotFoundError, realpath, path, strict=True)
494            self.assertEqual(realpath(path, strict=ALLOW_MISSING), '/')
495        else:
496            self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
497            self.assertRaises(UnicodeEncodeError, realpath, path, strict=True)
498            self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
499        path = '/nonexistent/\udfff/..'
500        if sys.platform == 'win32':
501            self.assertEqual(realpath(path, strict=False), '/nonexistent')
502            self.assertEqual(realpath(path, strict=ALLOW_MISSING), '/nonexistent')
503        else:
504            self.assertRaises(UnicodeEncodeError, realpath, path, strict=False)
505            self.assertRaises(UnicodeEncodeError, realpath, path, strict=ALLOW_MISSING)
506        self.assertRaises(FileNotFoundError, realpath, path, strict=True)
507
508        path = b'/\xff'
509        if sys.platform == 'win32':
510            self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
511            self.assertRaises(UnicodeDecodeError, realpath, path, strict=True)
512            self.assertRaises(UnicodeDecodeError, realpath, path, strict=ALLOW_MISSING)
513        else:
514            self.assertEqual(realpath(path, strict=False), path)
515            if support.is_wasi:
516                self.assertRaises(OSError, realpath, path, strict=True)
517                self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
518            else:
519                self.assertRaises(FileNotFoundError, realpath, path, strict=True)
520                self.assertEqual(realpath(path, strict=ALLOW_MISSING), path)
521        path = b'/nonexistent/\xff'
522        if sys.platform == 'win32':
523            self.assertRaises(UnicodeDecodeError, realpath, path, strict=False)
524            self.assertRaises(UnicodeDecodeError, realpath, path, strict=ALLOW_MISSING)
525        else:
526            self.assertEqual(realpath(path, strict=False), path)
527        if support.is_wasi:
528            self.assertRaises(OSError, realpath, path, strict=True)
529            self.assertRaises(OSError, realpath, path, strict=ALLOW_MISSING)
530        else:
531            self.assertRaises(FileNotFoundError, realpath, path, strict=True)
532
533    @os_helper.skip_unless_symlink
534    @skip_if_ABSTFN_contains_backslash
535    @_parameterize({}, {'strict': ALLOW_MISSING})
536    def test_realpath_relative(self, kwargs):
537        try:
538            os.symlink(posixpath.relpath(ABSTFN+"1"), ABSTFN)
539            self.assertEqual(realpath(ABSTFN, **kwargs), ABSTFN+"1")
540        finally:
541            os_helper.unlink(ABSTFN)
542
543    @os_helper.skip_unless_symlink
544    @skip_if_ABSTFN_contains_backslash
545    @_parameterize({}, {'strict': ALLOW_MISSING})
546    def test_realpath_missing_pardir(self, kwargs):
547        try:
548            os.symlink(os_helper.TESTFN + "1", os_helper.TESTFN)
549            self.assertEqual(
550                realpath("nonexistent/../" + os_helper.TESTFN, **kwargs), ABSTFN + "1")
551        finally:
552            os_helper.unlink(os_helper.TESTFN)
553
554    @os_helper.skip_unless_symlink
555    @skip_if_ABSTFN_contains_backslash
556    def test_realpath_symlink_loops(self):
557        # Bug #930024, return the path unchanged if we get into an infinite
558        # symlink loop in non-strict mode (default).
559        try:
560            os.symlink(ABSTFN, ABSTFN)
561            self.assertEqual(realpath(ABSTFN), ABSTFN)
562
563            os.symlink(ABSTFN+"1", ABSTFN+"2")
564            os.symlink(ABSTFN+"2", ABSTFN+"1")
565            self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1")
566            self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2")
567
568            self.assertEqual(realpath(ABSTFN+"1/x"), ABSTFN+"1/x")
569            self.assertEqual(realpath(ABSTFN+"1/.."), dirname(ABSTFN))
570            self.assertEqual(realpath(ABSTFN+"1/../x"), dirname(ABSTFN) + "/x")
571            os.symlink(ABSTFN+"x", ABSTFN+"y")
572            self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "y"),
573                             ABSTFN + "y")
574            self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "1"),
575                             ABSTFN + "1")
576
577            os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a")
578            self.assertEqual(realpath(ABSTFN+"a"), ABSTFN+"a/b")
579
580            os.symlink("../" + basename(dirname(ABSTFN)) + "/" +
581                       basename(ABSTFN) + "c", ABSTFN+"c")
582            self.assertEqual(realpath(ABSTFN+"c"), ABSTFN+"c")
583
584            # Test using relative path as well.
585            with os_helper.change_cwd(dirname(ABSTFN)):
586                self.assertEqual(realpath(basename(ABSTFN)), ABSTFN)
587        finally:
588            os_helper.unlink(ABSTFN)
589            os_helper.unlink(ABSTFN+"1")
590            os_helper.unlink(ABSTFN+"2")
591            os_helper.unlink(ABSTFN+"y")
592            os_helper.unlink(ABSTFN+"c")
593            os_helper.unlink(ABSTFN+"a")
594
595    @os_helper.skip_unless_symlink
596    @skip_if_ABSTFN_contains_backslash
597    @_parameterize({'strict': True}, {'strict': ALLOW_MISSING})
598    def test_realpath_symlink_loops_strict(self, kwargs):
599        # Bug #43757, raise OSError if we get into an infinite symlink loop in
600        # the strict modes.
601        try:
602            os.symlink(ABSTFN, ABSTFN)
603            self.assertRaises(OSError, realpath, ABSTFN, **kwargs)
604
605            os.symlink(ABSTFN+"1", ABSTFN+"2")
606            os.symlink(ABSTFN+"2", ABSTFN+"1")
607            self.assertRaises(OSError, realpath, ABSTFN+"1", **kwargs)
608            self.assertRaises(OSError, realpath, ABSTFN+"2", **kwargs)
609
610            self.assertRaises(OSError, realpath, ABSTFN+"1/x", **kwargs)
611            self.assertRaises(OSError, realpath, ABSTFN+"1/..", **kwargs)
612            self.assertRaises(OSError, realpath, ABSTFN+"1/../x", **kwargs)
613            os.symlink(ABSTFN+"x", ABSTFN+"y")
614            self.assertRaises(OSError, realpath,
615                              ABSTFN+"1/../" + basename(ABSTFN) + "y", **kwargs)
616            self.assertRaises(OSError, realpath,
617                              ABSTFN+"1/../" + basename(ABSTFN) + "1", **kwargs)
618
619            os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a")
620            self.assertRaises(OSError, realpath, ABSTFN+"a", **kwargs)
621
622            os.symlink("../" + basename(dirname(ABSTFN)) + "/" +
623                       basename(ABSTFN) + "c", ABSTFN+"c")
624            self.assertRaises(OSError, realpath, ABSTFN+"c", **kwargs)
625
626            # Test using relative path as well.
627            with os_helper.change_cwd(dirname(ABSTFN)):
628                self.assertRaises(OSError, realpath, basename(ABSTFN), **kwargs)
629        finally:
630            os_helper.unlink(ABSTFN)
631            os_helper.unlink(ABSTFN+"1")
632            os_helper.unlink(ABSTFN+"2")
633            os_helper.unlink(ABSTFN+"y")
634            os_helper.unlink(ABSTFN+"c")
635            os_helper.unlink(ABSTFN+"a")
636
637    @os_helper.skip_unless_symlink
638    @skip_if_ABSTFN_contains_backslash
639    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
640    def test_realpath_repeated_indirect_symlinks(self, kwargs):
641        # Issue #6975.
642        try:
643            os.mkdir(ABSTFN)
644            os.symlink('../' + basename(ABSTFN), ABSTFN + '/self')
645            os.symlink('self/self/self', ABSTFN + '/link')
646            self.assertEqual(realpath(ABSTFN + '/link', **kwargs), ABSTFN)
647        finally:
648            os_helper.unlink(ABSTFN + '/self')
649            os_helper.unlink(ABSTFN + '/link')
650            safe_rmdir(ABSTFN)
651
652    @os_helper.skip_unless_symlink
653    @skip_if_ABSTFN_contains_backslash
654    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
655    def test_realpath_deep_recursion(self, kwargs):
656        depth = 10
657        try:
658            os.mkdir(ABSTFN)
659            for i in range(depth):
660                os.symlink('/'.join(['%d' % i] * 10), ABSTFN + '/%d' % (i + 1))
661            os.symlink('.', ABSTFN + '/0')
662            self.assertEqual(realpath(ABSTFN + '/%d' % depth, **kwargs), ABSTFN)
663
664            # Test using relative path as well.
665            with os_helper.change_cwd(ABSTFN):
666                self.assertEqual(realpath('%d' % depth), ABSTFN)
667        finally:
668            for i in range(depth + 1):
669                os_helper.unlink(ABSTFN + '/%d' % i)
670            safe_rmdir(ABSTFN)
671
672    @os_helper.skip_unless_symlink
673    @skip_if_ABSTFN_contains_backslash
674    @_parameterize({}, {'strict': ALLOW_MISSING})
675    def test_realpath_resolve_parents(self, kwargs):
676        # We also need to resolve any symlinks in the parents of a relative
677        # path passed to realpath. E.g.: current working directory is
678        # /usr/doc with 'doc' being a symlink to /usr/share/doc. We call
679        # realpath("a"). This should return /usr/share/doc/a/.
680        try:
681            os.mkdir(ABSTFN)
682            os.mkdir(ABSTFN + "/y")
683            os.symlink(ABSTFN + "/y", ABSTFN + "/k")
684
685            with os_helper.change_cwd(ABSTFN + "/k"):
686                self.assertEqual(realpath("a", **kwargs),
687                                    ABSTFN + "/y/a")
688        finally:
689            os_helper.unlink(ABSTFN + "/k")
690            safe_rmdir(ABSTFN + "/y")
691            safe_rmdir(ABSTFN)
692
693    @os_helper.skip_unless_symlink
694    @skip_if_ABSTFN_contains_backslash
695    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
696    def test_realpath_resolve_before_normalizing(self, kwargs):
697        # Bug #990669: Symbolic links should be resolved before we
698        # normalize the path. E.g.: if we have directories 'a', 'k' and 'y'
699        # in the following hierarchy:
700        # a/k/y
701        #
702        # and a symbolic link 'link-y' pointing to 'y' in directory 'a',
703        # then realpath("link-y/..") should return 'k', not 'a'.
704        try:
705            os.mkdir(ABSTFN)
706            os.mkdir(ABSTFN + "/k")
707            os.mkdir(ABSTFN + "/k/y")
708            os.symlink(ABSTFN + "/k/y", ABSTFN + "/link-y")
709
710            # Absolute path.
711            self.assertEqual(realpath(ABSTFN + "/link-y/..", **kwargs), ABSTFN + "/k")
712            # Relative path.
713            with os_helper.change_cwd(dirname(ABSTFN)):
714                self.assertEqual(realpath(basename(ABSTFN) + "/link-y/..", **kwargs),
715                                 ABSTFN + "/k")
716        finally:
717            os_helper.unlink(ABSTFN + "/link-y")
718            safe_rmdir(ABSTFN + "/k/y")
719            safe_rmdir(ABSTFN + "/k")
720            safe_rmdir(ABSTFN)
721
722    @os_helper.skip_unless_symlink
723    @skip_if_ABSTFN_contains_backslash
724    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
725    def test_realpath_resolve_first(self, kwargs):
726        # Bug #1213894: The first component of the path, if not absolute,
727        # must be resolved too.
728
729        try:
730            os.mkdir(ABSTFN)
731            os.mkdir(ABSTFN + "/k")
732            os.symlink(ABSTFN, ABSTFN + "link")
733            with os_helper.change_cwd(dirname(ABSTFN)):
734                base = basename(ABSTFN)
735                self.assertEqual(realpath(base + "link", **kwargs), ABSTFN)
736                self.assertEqual(realpath(base + "link/k", **kwargs), ABSTFN + "/k")
737        finally:
738            os_helper.unlink(ABSTFN + "link")
739            safe_rmdir(ABSTFN + "/k")
740            safe_rmdir(ABSTFN)
741
742    @os_helper.skip_unless_symlink
743    @skip_if_ABSTFN_contains_backslash
744    @unittest.skipIf(os.chmod not in os.supports_follow_symlinks, "Can't set symlink permissions")
745    @unittest.skipIf(sys.platform != "darwin", "only macOS requires read permission to readlink()")
746    @_parameterize({'strict': True}, {'strict': ALLOW_MISSING})
747    def test_realpath_unreadable_symlink_strict(self, kwargs):
748        try:
749            os.symlink(ABSTFN+"1", ABSTFN)
750            os.chmod(ABSTFN, 0o000, follow_symlinks=False)
751            with self.assertRaises(PermissionError):
752                realpath(ABSTFN, **kwargs)
753            with self.assertRaises(PermissionError):
754                realpath(ABSTFN + '/foo', **kwargs),
755            with self.assertRaises(PermissionError):
756                realpath(ABSTFN + '/../foo', **kwargs)
757            with self.assertRaises(PermissionError):
758                realpath(ABSTFN + '/foo/..', **kwargs)
759        finally:
760            os.chmod(ABSTFN, 0o755, follow_symlinks=False)
761            os.unlink(ABSTFN)
762
763    @skip_if_ABSTFN_contains_backslash
764    @os_helper.skip_unless_symlink
765    def test_realpath_unreadable_directory(self):
766        try:
767            os.mkdir(ABSTFN)
768            os.mkdir(ABSTFN + '/k')
769            os.chmod(ABSTFN, 0o000)
770            self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN)
771            self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN)
772            self.assertEqual(realpath(ABSTFN, strict=ALLOW_MISSING), ABSTFN)
773
774            try:
775                os.stat(ABSTFN)
776            except PermissionError:
777                pass
778            else:
779                self.skipTest('Cannot block permissions')
780
781            self.assertEqual(realpath(ABSTFN + '/k', strict=False),
782                             ABSTFN + '/k')
783            self.assertRaises(PermissionError, realpath, ABSTFN + '/k',
784                              strict=True)
785            self.assertRaises(PermissionError, realpath, ABSTFN + '/k',
786                              strict=ALLOW_MISSING)
787
788            self.assertEqual(realpath(ABSTFN + '/missing', strict=False),
789                             ABSTFN + '/missing')
790            self.assertRaises(PermissionError, realpath, ABSTFN + '/missing',
791                              strict=True)
792            self.assertRaises(PermissionError, realpath, ABSTFN + '/missing',
793                              strict=ALLOW_MISSING)
794        finally:
795            os.chmod(ABSTFN, 0o755)
796            safe_rmdir(ABSTFN + '/k')
797            safe_rmdir(ABSTFN)
798
799    def test_relpath(self):
800        (real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
801        try:
802            curdir = os.path.split(os.getcwd())[-1]
803            self.assertRaises(ValueError, posixpath.relpath, "")
804            self.assertEqual(posixpath.relpath("a"), "a")
805            self.assertEqual(posixpath.relpath(posixpath.abspath("a")), "a")
806            self.assertEqual(posixpath.relpath("a/b"), "a/b")
807            self.assertEqual(posixpath.relpath("../a/b"), "../a/b")
808            self.assertEqual(posixpath.relpath("a", "../b"), "../"+curdir+"/a")
809            self.assertEqual(posixpath.relpath("a/b", "../c"),
810                             "../"+curdir+"/a/b")
811            self.assertEqual(posixpath.relpath("a", "b/c"), "../../a")
812            self.assertEqual(posixpath.relpath("a", "a"), ".")
813            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/x/y/z"), '../../../foo/bar/bat')
814            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/foo/bar"), 'bat')
815            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/"), 'foo/bar/bat')
816            self.assertEqual(posixpath.relpath("/", "/foo/bar/bat"), '../../..')
817            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/x"), '../foo/bar/bat')
818            self.assertEqual(posixpath.relpath("/x", "/foo/bar/bat"), '../../../x')
819            self.assertEqual(posixpath.relpath("/", "/"), '.')
820            self.assertEqual(posixpath.relpath("/a", "/a"), '.')
821            self.assertEqual(posixpath.relpath("/a/b", "/a/b"), '.')
822        finally:
823            os.getcwd = real_getcwd
824
825    def test_relpath_bytes(self):
826        (real_getcwdb, os.getcwdb) = (os.getcwdb, lambda: br"/home/user/bar")
827        try:
828            curdir = os.path.split(os.getcwdb())[-1]
829            self.assertRaises(ValueError, posixpath.relpath, b"")
830            self.assertEqual(posixpath.relpath(b"a"), b"a")
831            self.assertEqual(posixpath.relpath(posixpath.abspath(b"a")), b"a")
832            self.assertEqual(posixpath.relpath(b"a/b"), b"a/b")
833            self.assertEqual(posixpath.relpath(b"../a/b"), b"../a/b")
834            self.assertEqual(posixpath.relpath(b"a", b"../b"),
835                             b"../"+curdir+b"/a")
836            self.assertEqual(posixpath.relpath(b"a/b", b"../c"),
837                             b"../"+curdir+b"/a/b")
838            self.assertEqual(posixpath.relpath(b"a", b"b/c"), b"../../a")
839            self.assertEqual(posixpath.relpath(b"a", b"a"), b".")
840            self.assertEqual(posixpath.relpath(b"/foo/bar/bat", b"/x/y/z"), b'../../../foo/bar/bat')
841            self.assertEqual(posixpath.relpath(b"/foo/bar/bat", b"/foo/bar"), b'bat')
842            self.assertEqual(posixpath.relpath(b"/foo/bar/bat", b"/"), b'foo/bar/bat')
843            self.assertEqual(posixpath.relpath(b"/", b"/foo/bar/bat"), b'../../..')
844            self.assertEqual(posixpath.relpath(b"/foo/bar/bat", b"/x"), b'../foo/bar/bat')
845            self.assertEqual(posixpath.relpath(b"/x", b"/foo/bar/bat"), b'../../../x')
846            self.assertEqual(posixpath.relpath(b"/", b"/"), b'.')
847            self.assertEqual(posixpath.relpath(b"/a", b"/a"), b'.')
848            self.assertEqual(posixpath.relpath(b"/a/b", b"/a/b"), b'.')
849
850            self.assertRaises(TypeError, posixpath.relpath, b"bytes", "str")
851            self.assertRaises(TypeError, posixpath.relpath, "str", b"bytes")
852        finally:
853            os.getcwdb = real_getcwdb
854
855    def test_commonpath(self):
856        def check(paths, expected):
857            self.assertEqual(posixpath.commonpath(paths), expected)
858            self.assertEqual(posixpath.commonpath([os.fsencode(p) for p in paths]),
859                             os.fsencode(expected))
860        def check_error(exc, paths):
861            self.assertRaises(exc, posixpath.commonpath, paths)
862            self.assertRaises(exc, posixpath.commonpath,
863                              [os.fsencode(p) for p in paths])
864
865        self.assertRaises(ValueError, posixpath.commonpath, [])
866        check_error(ValueError, ['/usr', 'usr'])
867        check_error(ValueError, ['usr', '/usr'])
868
869        check(['/usr/local'], '/usr/local')
870        check(['/usr/local', '/usr/local'], '/usr/local')
871        check(['/usr/local/', '/usr/local'], '/usr/local')
872        check(['/usr/local/', '/usr/local/'], '/usr/local')
873        check(['/usr//local', '//usr/local'], '/usr/local')
874        check(['/usr/./local', '/./usr/local'], '/usr/local')
875        check(['/', '/dev'], '/')
876        check(['/usr', '/dev'], '/')
877        check(['/usr/lib/', '/usr/lib/python3'], '/usr/lib')
878        check(['/usr/lib/', '/usr/lib64/'], '/usr')
879
880        check(['/usr/lib', '/usr/lib64'], '/usr')
881        check(['/usr/lib/', '/usr/lib64'], '/usr')
882
883        check(['spam'], 'spam')
884        check(['spam', 'spam'], 'spam')
885        check(['spam', 'alot'], '')
886        check(['and/jam', 'and/spam'], 'and')
887        check(['and//jam', 'and/spam//'], 'and')
888        check(['and/./jam', './and/spam'], 'and')
889        check(['and/jam', 'and/spam', 'alot'], '')
890        check(['and/jam', 'and/spam', 'and'], 'and')
891
892        check([''], '')
893        check(['', 'spam/alot'], '')
894        check_error(ValueError, ['', '/spam/alot'])
895
896        self.assertRaises(TypeError, posixpath.commonpath,
897                          [b'/usr/lib/', '/usr/lib/python3'])
898        self.assertRaises(TypeError, posixpath.commonpath,
899                          [b'/usr/lib/', 'usr/lib/python3'])
900        self.assertRaises(TypeError, posixpath.commonpath,
901                          [b'usr/lib/', '/usr/lib/python3'])
902        self.assertRaises(TypeError, posixpath.commonpath,
903                          ['/usr/lib/', b'/usr/lib/python3'])
904        self.assertRaises(TypeError, posixpath.commonpath,
905                          ['/usr/lib/', b'usr/lib/python3'])
906        self.assertRaises(TypeError, posixpath.commonpath,
907                          ['usr/lib/', b'/usr/lib/python3'])
908
909
910class PosixCommonTest(test_genericpath.CommonTest, unittest.TestCase):
911    pathmodule = posixpath
912    attributes = ['relpath', 'samefile', 'sameopenfile', 'samestat']
913
914
915class PathLikeTests(unittest.TestCase):
916
917    path = posixpath
918
919    def setUp(self):
920        self.file_name = os_helper.TESTFN
921        self.file_path = FakePath(os_helper.TESTFN)
922        self.addCleanup(os_helper.unlink, self.file_name)
923        with open(self.file_name, 'xb', 0) as file:
924            file.write(b"test_posixpath.PathLikeTests")
925
926    def assertPathEqual(self, func):
927        self.assertEqual(func(self.file_path), func(self.file_name))
928
929    def test_path_normcase(self):
930        self.assertPathEqual(self.path.normcase)
931
932    def test_path_isabs(self):
933        self.assertPathEqual(self.path.isabs)
934
935    def test_path_join(self):
936        self.assertEqual(self.path.join('a', FakePath('b'), 'c'),
937                         self.path.join('a', 'b', 'c'))
938
939    def test_path_split(self):
940        self.assertPathEqual(self.path.split)
941
942    def test_path_splitext(self):
943        self.assertPathEqual(self.path.splitext)
944
945    def test_path_splitdrive(self):
946        self.assertPathEqual(self.path.splitdrive)
947
948    def test_path_basename(self):
949        self.assertPathEqual(self.path.basename)
950
951    def test_path_dirname(self):
952        self.assertPathEqual(self.path.dirname)
953
954    def test_path_islink(self):
955        self.assertPathEqual(self.path.islink)
956
957    def test_path_lexists(self):
958        self.assertPathEqual(self.path.lexists)
959
960    def test_path_ismount(self):
961        self.assertPathEqual(self.path.ismount)
962
963    def test_path_expanduser(self):
964        self.assertPathEqual(self.path.expanduser)
965
966    def test_path_expandvars(self):
967        self.assertPathEqual(self.path.expandvars)
968
969    def test_path_normpath(self):
970        self.assertPathEqual(self.path.normpath)
971
972    def test_path_abspath(self):
973        self.assertPathEqual(self.path.abspath)
974
975    @_parameterize({}, {'strict': True}, {'strict': ALLOW_MISSING})
976    def test_path_realpath(self, kwargs):
977        self.assertPathEqual(self.path.realpath)
978
979        self.assertPathEqual(partial(self.path.realpath, **kwargs))
980
981    def test_path_relpath(self):
982        self.assertPathEqual(self.path.relpath)
983
984    def test_path_commonpath(self):
985        common_path = self.path.commonpath([self.file_path, self.file_name])
986        self.assertEqual(common_path, self.file_name)
987
988
989if __name__=="__main__":
990    unittest.main()
991