1import unittest
2from test import test_support, test_genericpath
3from test import test_support as support
4
5import posixpath
6import os
7import sys
8from posixpath import realpath, abspath, dirname, basename
9
10try:
11    import posix
12except ImportError:
13    posix = None
14
15# An absolute path to a temporary filename for testing. We can't rely on TESTFN
16# being an absolute path, so we need this.
17
18ABSTFN = abspath(test_support.TESTFN)
19
20def skip_if_ABSTFN_contains_backslash(test):
21    """
22    On Windows, posixpath.abspath still returns paths with backslashes
23    instead of posix forward slashes. If this is the case, several tests
24    fail, so skip them.
25    """
26    found_backslash = '\\' in ABSTFN
27    msg = "ABSTFN is not a posix path - tests fail"
28    return [test, unittest.skip(msg)(test)][found_backslash]
29
30def safe_rmdir(dirname):
31    try:
32        os.rmdir(dirname)
33    except OSError:
34        pass
35
36class PosixPathTest(unittest.TestCase):
37
38    def setUp(self):
39        self.tearDown()
40
41    def tearDown(self):
42        for suffix in ["", "1", "2"]:
43            test_support.unlink(test_support.TESTFN + suffix)
44            safe_rmdir(test_support.TESTFN + suffix)
45
46    def test_join(self):
47        self.assertEqual(posixpath.join("/foo", "bar", "/bar", "baz"), "/bar/baz")
48        self.assertEqual(posixpath.join("/foo", "bar", "baz"), "/foo/bar/baz")
49        self.assertEqual(posixpath.join("/foo/", "bar/", "baz/"), "/foo/bar/baz/")
50
51    def test_split(self):
52        self.assertEqual(posixpath.split("/foo/bar"), ("/foo", "bar"))
53        self.assertEqual(posixpath.split("/"), ("/", ""))
54        self.assertEqual(posixpath.split("foo"), ("", "foo"))
55        self.assertEqual(posixpath.split("////foo"), ("////", "foo"))
56        self.assertEqual(posixpath.split("//foo//bar"), ("//foo", "bar"))
57
58    def splitextTest(self, path, filename, ext):
59        self.assertEqual(posixpath.splitext(path), (filename, ext))
60        self.assertEqual(posixpath.splitext("/" + path), ("/" + filename, ext))
61        self.assertEqual(posixpath.splitext("abc/" + path), ("abc/" + filename, ext))
62        self.assertEqual(posixpath.splitext("abc.def/" + path), ("abc.def/" + filename, ext))
63        self.assertEqual(posixpath.splitext("/abc.def/" + path), ("/abc.def/" + filename, ext))
64        self.assertEqual(posixpath.splitext(path + "/"), (filename + ext + "/", ""))
65
66    def test_splitext(self):
67        self.splitextTest("foo.bar", "foo", ".bar")
68        self.splitextTest("foo.boo.bar", "foo.boo", ".bar")
69        self.splitextTest("foo.boo.biff.bar", "foo.boo.biff", ".bar")
70        self.splitextTest(".csh.rc", ".csh", ".rc")
71        self.splitextTest("nodots", "nodots", "")
72        self.splitextTest(".cshrc", ".cshrc", "")
73        self.splitextTest("...manydots", "...manydots", "")
74        self.splitextTest("...manydots.ext", "...manydots", ".ext")
75        self.splitextTest(".", ".", "")
76        self.splitextTest("..", "..", "")
77        self.splitextTest("........", "........", "")
78        self.splitextTest("", "", "")
79
80    def test_isabs(self):
81        self.assertIs(posixpath.isabs(""), False)
82        self.assertIs(posixpath.isabs("/"), True)
83        self.assertIs(posixpath.isabs("/foo"), True)
84        self.assertIs(posixpath.isabs("/foo/bar"), True)
85        self.assertIs(posixpath.isabs("foo/bar"), False)
86
87    def test_basename(self):
88        self.assertEqual(posixpath.basename("/foo/bar"), "bar")
89        self.assertEqual(posixpath.basename("/"), "")
90        self.assertEqual(posixpath.basename("foo"), "foo")
91        self.assertEqual(posixpath.basename("////foo"), "foo")
92        self.assertEqual(posixpath.basename("//foo//bar"), "bar")
93
94    def test_dirname(self):
95        self.assertEqual(posixpath.dirname("/foo/bar"), "/foo")
96        self.assertEqual(posixpath.dirname("/"), "/")
97        self.assertEqual(posixpath.dirname("foo"), "")
98        self.assertEqual(posixpath.dirname("////foo"), "////")
99        self.assertEqual(posixpath.dirname("//foo//bar"), "//foo")
100
101    def test_islink(self):
102        self.assertIs(posixpath.islink(test_support.TESTFN + "1"), False)
103        f = open(test_support.TESTFN + "1", "wb")
104        try:
105            f.write("foo")
106            f.close()
107            self.assertIs(posixpath.islink(test_support.TESTFN + "1"), False)
108            if hasattr(os, 'symlink'):
109                os.symlink(test_support.TESTFN + "1", test_support.TESTFN + "2")
110                self.assertIs(posixpath.islink(test_support.TESTFN + "2"), True)
111                os.remove(test_support.TESTFN + "1")
112                self.assertIs(posixpath.islink(test_support.TESTFN + "2"), True)
113                self.assertIs(posixpath.exists(test_support.TESTFN + "2"), False)
114                self.assertIs(posixpath.lexists(test_support.TESTFN + "2"), True)
115        finally:
116            if not f.close():
117                f.close()
118
119    def test_samefile(self):
120        f = open(test_support.TESTFN + "1", "wb")
121        try:
122            f.write("foo")
123            f.close()
124            self.assertIs(
125                posixpath.samefile(
126                    test_support.TESTFN + "1",
127                    test_support.TESTFN + "1"
128                ),
129                True
130            )
131
132            # If we don't have links, assume that os.stat doesn't return
133            # reasonable inode information and thus, that samefile() doesn't
134            # work.
135            if hasattr(os, "symlink"):
136                os.symlink(
137                    test_support.TESTFN + "1",
138                    test_support.TESTFN + "2"
139                )
140                self.assertIs(
141                    posixpath.samefile(
142                        test_support.TESTFN + "1",
143                        test_support.TESTFN + "2"
144                    ),
145                    True
146                )
147                os.remove(test_support.TESTFN + "2")
148                f = open(test_support.TESTFN + "2", "wb")
149                f.write("bar")
150                f.close()
151                self.assertIs(
152                    posixpath.samefile(
153                        test_support.TESTFN + "1",
154                        test_support.TESTFN + "2"
155                    ),
156                    False
157                )
158        finally:
159            if not f.close():
160                f.close()
161
162    def test_samestat(self):
163        f = open(test_support.TESTFN + "1", "wb")
164        try:
165            f.write("foo")
166            f.close()
167            self.assertIs(
168                posixpath.samestat(
169                    os.stat(test_support.TESTFN + "1"),
170                    os.stat(test_support.TESTFN + "1")
171                ),
172                True
173            )
174            # If we don't have links, assume that os.stat() doesn't return
175            # reasonable inode information and thus, that samestat() doesn't
176            # work.
177            if hasattr(os, "symlink"):
178                os.symlink(test_support.TESTFN + "1", test_support.TESTFN + "2")
179                self.assertIs(
180                    posixpath.samestat(
181                        os.stat(test_support.TESTFN + "1"),
182                        os.stat(test_support.TESTFN + "2")
183                    ),
184                    True
185                )
186                os.remove(test_support.TESTFN + "2")
187                f = open(test_support.TESTFN + "2", "wb")
188                f.write("bar")
189                f.close()
190                self.assertIs(
191                    posixpath.samestat(
192                        os.stat(test_support.TESTFN + "1"),
193                        os.stat(test_support.TESTFN + "2")
194                    ),
195                    False
196                )
197        finally:
198            if not f.close():
199                f.close()
200
201    def test_ismount(self):
202        self.assertIs(posixpath.ismount("/"), True)
203
204    def test_ismount_non_existent(self):
205        # Non-existent mountpoint.
206        self.assertIs(posixpath.ismount(ABSTFN), False)
207        try:
208            os.mkdir(ABSTFN)
209            self.assertIs(posixpath.ismount(ABSTFN), False)
210        finally:
211            safe_rmdir(ABSTFN)
212
213    @unittest.skipUnless(hasattr(os, 'symlink'),
214                         'Requires functional symlink implementation')
215    def test_ismount_symlinks(self):
216        # Symlinks are never mountpoints.
217        try:
218            os.symlink("/", ABSTFN)
219            self.assertIs(posixpath.ismount(ABSTFN), False)
220        finally:
221            os.unlink(ABSTFN)
222
223    @unittest.skipIf(posix is None, "Test requires posix module")
224    def test_ismount_different_device(self):
225        # Simulate the path being on a different device from its parent by
226        # mocking out st_dev.
227        save_lstat = os.lstat
228        def fake_lstat(path):
229            st_ino = 0
230            st_dev = 0
231            if path == ABSTFN:
232                st_dev = 1
233                st_ino = 1
234            return posix.stat_result((0, st_ino, st_dev, 0, 0, 0, 0, 0, 0, 0))
235        try:
236            os.lstat = fake_lstat
237            self.assertIs(posixpath.ismount(ABSTFN), True)
238        finally:
239            os.lstat = save_lstat
240
241    @unittest.skipIf(posix is None, "Test requires posix module")
242    def test_ismount_directory_not_readable(self):
243        # issue #2466: Simulate ismount run on a directory that is not
244        # readable, which used to return False.
245        save_lstat = os.lstat
246        def fake_lstat(path):
247            st_ino = 0
248            st_dev = 0
249            if path.startswith(ABSTFN) and path != ABSTFN:
250                # ismount tries to read something inside the ABSTFN directory;
251                # simulate this being forbidden (no read permission).
252                raise OSError("Fake [Errno 13] Permission denied")
253            if path == ABSTFN:
254                st_dev = 1
255                st_ino = 1
256            return posix.stat_result((0, st_ino, st_dev, 0, 0, 0, 0, 0, 0, 0))
257        try:
258            os.lstat = fake_lstat
259            self.assertIs(posixpath.ismount(ABSTFN), True)
260        finally:
261            os.lstat = save_lstat
262
263    def test_expanduser(self):
264        self.assertEqual(posixpath.expanduser("foo"), "foo")
265
266    def test_expanduser_home_envvar(self):
267        with support.EnvironmentVarGuard() as env:
268            env['HOME'] = '/home/victor'
269            self.assertEqual(posixpath.expanduser("~"), "/home/victor")
270
271            # expanduser() strips trailing slash
272            env['HOME'] = '/home/victor/'
273            self.assertEqual(posixpath.expanduser("~"), "/home/victor")
274
275            for home in '/', '', '//', '///':
276                env['HOME'] = home
277                self.assertEqual(posixpath.expanduser("~"), "/")
278                self.assertEqual(posixpath.expanduser("~/"), "/")
279                self.assertEqual(posixpath.expanduser("~/foo"), "/foo")
280
281    def test_expanduser_pwd(self):
282        pwd = support.import_module('pwd')
283
284        self.assertIsInstance(posixpath.expanduser("~/"), str)
285
286        # if home directory == root directory, this test makes no sense
287        if posixpath.expanduser("~") != '/':
288            self.assertEqual(
289                posixpath.expanduser("~") + "/",
290                posixpath.expanduser("~/")
291            )
292        self.assertIsInstance(posixpath.expanduser("~root/"), str)
293        self.assertIsInstance(posixpath.expanduser("~foo/"), str)
294
295        with support.EnvironmentVarGuard() as env:
296            # expanduser should fall back to using the password database
297            del env['HOME']
298
299            home = pwd.getpwuid(os.getuid()).pw_dir
300            # $HOME can end with a trailing /, so strip it (see #17809)
301            home = home.rstrip("/") or '/'
302            self.assertEqual(posixpath.expanduser("~"), home)
303
304            # bpo-10496: If the HOME environment variable is not set and the
305            # user (current identifier or name in the path) doesn't exist in
306            # the password database (pwd.getuid() or pwd.getpwnam() fail),
307            # expanduser() must return the path unchanged.
308            def raise_keyerror(*args):
309                raise KeyError
310
311            with support.swap_attr(pwd, 'getpwuid', raise_keyerror), \
312                 support.swap_attr(pwd, 'getpwnam', raise_keyerror):
313                for path in ('~', '~/.local', '~vstinner/'):
314                    self.assertEqual(posixpath.expanduser(path), path)
315
316    def test_normpath(self):
317        self.assertEqual(posixpath.normpath(""), ".")
318        self.assertEqual(posixpath.normpath("/"), "/")
319        self.assertEqual(posixpath.normpath("//"), "//")
320        self.assertEqual(posixpath.normpath("///"), "/")
321        self.assertEqual(posixpath.normpath("///foo/.//bar//"), "/foo/bar")
322        self.assertEqual(posixpath.normpath("///foo/.//bar//.//..//.//baz"), "/foo/baz")
323        self.assertEqual(posixpath.normpath("///..//./foo/.//bar"), "/foo/bar")
324
325    @skip_if_ABSTFN_contains_backslash
326    def test_realpath_curdir(self):
327        self.assertEqual(realpath('.'), os.getcwd())
328        self.assertEqual(realpath('./.'), os.getcwd())
329        self.assertEqual(realpath('/'.join(['.'] * 100)), os.getcwd())
330
331    @skip_if_ABSTFN_contains_backslash
332    def test_realpath_pardir(self):
333        self.assertEqual(realpath('..'), dirname(os.getcwd()))
334        self.assertEqual(realpath('../..'), dirname(dirname(os.getcwd())))
335        self.assertEqual(realpath('/'.join(['..'] * 100)), '/')
336
337    if hasattr(os, "symlink"):
338        def test_realpath_basic(self):
339            # Basic operation.
340            try:
341                os.symlink(ABSTFN+"1", ABSTFN)
342                self.assertEqual(realpath(ABSTFN), ABSTFN+"1")
343            finally:
344                test_support.unlink(ABSTFN)
345
346        def test_realpath_symlink_loops(self):
347            # Bug #930024, return the path unchanged if we get into an infinite
348            # symlink loop.
349            try:
350                os.symlink(ABSTFN, ABSTFN)
351                self.assertEqual(realpath(ABSTFN), ABSTFN)
352
353                os.symlink(ABSTFN+"1", ABSTFN+"2")
354                os.symlink(ABSTFN+"2", ABSTFN+"1")
355                self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1")
356                self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2")
357
358                self.assertEqual(realpath(ABSTFN+"1/x"), ABSTFN+"1/x")
359                self.assertEqual(realpath(ABSTFN+"1/.."), dirname(ABSTFN))
360                self.assertEqual(realpath(ABSTFN+"1/../x"), dirname(ABSTFN) + "/x")
361                os.symlink(ABSTFN+"x", ABSTFN+"y")
362                self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "y"),
363                                ABSTFN + "y")
364                self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "1"),
365                                ABSTFN + "1")
366
367                os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a")
368                self.assertEqual(realpath(ABSTFN+"a"), ABSTFN+"a/b")
369
370                os.symlink("../" + basename(dirname(ABSTFN)) + "/" +
371                        basename(ABSTFN) + "c", ABSTFN+"c")
372                self.assertEqual(realpath(ABSTFN+"c"), ABSTFN+"c")
373
374                # Test using relative path as well.
375                with support.change_cwd(dirname(ABSTFN)):
376                    self.assertEqual(realpath(basename(ABSTFN)), ABSTFN)
377            finally:
378                test_support.unlink(ABSTFN)
379                test_support.unlink(ABSTFN+"1")
380                test_support.unlink(ABSTFN+"2")
381                test_support.unlink(ABSTFN+"y")
382                test_support.unlink(ABSTFN+"c")
383                test_support.unlink(ABSTFN+"a")
384
385        def test_realpath_repeated_indirect_symlinks(self):
386            # Issue #6975.
387            try:
388                os.mkdir(ABSTFN)
389                os.symlink('../' + basename(ABSTFN), ABSTFN + '/self')
390                os.symlink('self/self/self', ABSTFN + '/link')
391                self.assertEqual(realpath(ABSTFN + '/link'), ABSTFN)
392            finally:
393                test_support.unlink(ABSTFN + '/self')
394                test_support.unlink(ABSTFN + '/link')
395                safe_rmdir(ABSTFN)
396
397        def test_realpath_deep_recursion(self):
398            depth = 10
399            try:
400                os.mkdir(ABSTFN)
401                for i in range(depth):
402                    os.symlink('/'.join(['%d' % i] * 10), ABSTFN + '/%d' % (i + 1))
403                os.symlink('.', ABSTFN + '/0')
404                self.assertEqual(realpath(ABSTFN + '/%d' % depth), ABSTFN)
405
406                # Test using relative path as well.
407                with support.change_cwd(ABSTFN):
408                    self.assertEqual(realpath('%d' % depth), ABSTFN)
409            finally:
410                for i in range(depth + 1):
411                    test_support.unlink(ABSTFN + '/%d' % i)
412                safe_rmdir(ABSTFN)
413
414        def test_realpath_resolve_parents(self):
415            # We also need to resolve any symlinks in the parents of a relative
416            # path passed to realpath. E.g.: current working directory is
417            # /usr/doc with 'doc' being a symlink to /usr/share/doc. We call
418            # realpath("a"). This should return /usr/share/doc/a/.
419            try:
420                os.mkdir(ABSTFN)
421                os.mkdir(ABSTFN + "/y")
422                os.symlink(ABSTFN + "/y", ABSTFN + "/k")
423
424                with support.change_cwd(ABSTFN + "/k"):
425                    self.assertEqual(realpath("a"), ABSTFN + "/y/a")
426            finally:
427                test_support.unlink(ABSTFN + "/k")
428                safe_rmdir(ABSTFN + "/y")
429                safe_rmdir(ABSTFN)
430
431        def test_realpath_resolve_before_normalizing(self):
432            # Bug #990669: Symbolic links should be resolved before we
433            # normalize the path. E.g.: if we have directories 'a', 'k' and 'y'
434            # in the following hierarchy:
435            # a/k/y
436            #
437            # and a symbolic link 'link-y' pointing to 'y' in directory 'a',
438            # then realpath("link-y/..") should return 'k', not 'a'.
439            try:
440                os.mkdir(ABSTFN)
441                os.mkdir(ABSTFN + "/k")
442                os.mkdir(ABSTFN + "/k/y")
443                os.symlink(ABSTFN + "/k/y", ABSTFN + "/link-y")
444
445                # Absolute path.
446                self.assertEqual(realpath(ABSTFN + "/link-y/.."), ABSTFN + "/k")
447                # Relative path.
448                with support.change_cwd(dirname(ABSTFN)):
449                    self.assertEqual(realpath(basename(ABSTFN) + "/link-y/.."),
450                                     ABSTFN + "/k")
451            finally:
452                test_support.unlink(ABSTFN + "/link-y")
453                safe_rmdir(ABSTFN + "/k/y")
454                safe_rmdir(ABSTFN + "/k")
455                safe_rmdir(ABSTFN)
456
457        def test_realpath_resolve_first(self):
458            # Bug #1213894: The first component of the path, if not absolute,
459            # must be resolved too.
460
461            try:
462                os.mkdir(ABSTFN)
463                os.mkdir(ABSTFN + "/k")
464                os.symlink(ABSTFN, ABSTFN + "link")
465                with support.change_cwd(dirname(ABSTFN)):
466                    base = basename(ABSTFN)
467                    self.assertEqual(realpath(base + "link"), ABSTFN)
468                    self.assertEqual(realpath(base + "link/k"), ABSTFN + "/k")
469            finally:
470                test_support.unlink(ABSTFN + "link")
471                safe_rmdir(ABSTFN + "/k")
472                safe_rmdir(ABSTFN)
473
474    def test_relpath(self):
475        (real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
476        try:
477            curdir = os.path.split(os.getcwd())[-1]
478            self.assertRaises(ValueError, posixpath.relpath, "")
479            self.assertEqual(posixpath.relpath("a"), "a")
480            self.assertEqual(posixpath.relpath(posixpath.abspath("a")), "a")
481            self.assertEqual(posixpath.relpath("a/b"), "a/b")
482            self.assertEqual(posixpath.relpath("../a/b"), "../a/b")
483            self.assertEqual(posixpath.relpath("a", "../b"), "../"+curdir+"/a")
484            self.assertEqual(posixpath.relpath("a/b", "../c"), "../"+curdir+"/a/b")
485            self.assertEqual(posixpath.relpath("a", "b/c"), "../../a")
486            self.assertEqual(posixpath.relpath("a", "a"), ".")
487            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/x/y/z"), '../../../foo/bar/bat')
488            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/foo/bar"), 'bat')
489            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/"), 'foo/bar/bat')
490            self.assertEqual(posixpath.relpath("/", "/foo/bar/bat"), '../../..')
491            self.assertEqual(posixpath.relpath("/foo/bar/bat", "/x"), '../foo/bar/bat')
492            self.assertEqual(posixpath.relpath("/x", "/foo/bar/bat"), '../../../x')
493            self.assertEqual(posixpath.relpath("/", "/"), '.')
494            self.assertEqual(posixpath.relpath("/a", "/a"), '.')
495            self.assertEqual(posixpath.relpath("/a/b", "/a/b"), '.')
496        finally:
497            os.getcwd = real_getcwd
498
499    @unittest.skipUnless(test_support.FS_NONASCII, 'need test_support.FS_NONASCII')
500    def test_expandvars_nonascii_word(self):
501        encoding = sys.getfilesystemencoding()
502        uwnonascii = test_support.FS_NONASCII
503        swnonascii = uwnonascii.encode(encoding)
504        if not swnonascii:
505            self.skipTest('Needs non-ASCII word characters')
506        with test_support.EnvironmentVarGuard() as env:
507            env.clear()
508            env[swnonascii] = 'baz' + swnonascii
509            self.assertEqual(posixpath.expandvars(u'$%s bar' % uwnonascii),
510                             u'baz%s bar' % uwnonascii)
511
512
513class PosixCommonTest(test_genericpath.CommonTest):
514    pathmodule = posixpath
515    attributes = ['relpath', 'samefile', 'sameopenfile', 'samestat']
516
517
518def test_main():
519    test_support.run_unittest(PosixPathTest, PosixCommonTest)
520
521
522if __name__=="__main__":
523    test_main()
524