source: test/test_path.py @ 1682:ec059f9d9dc8

Last change on this file since 1682:ec059f9d9dc8 was 1682:ec059f9d9dc8, checked in by Stefan Schwarzer <sschwarzer@…>, 2 years ago
Handle infinite link chains in `isdir` and `isfile` If `FTPHost.path.isdir` and `FTPHost.path.isfile` run into an infinite link chain, return `False`, as the corresponding functions in `os.path` do. Before, `isdir` and `isfile` would raise a `PermanentError` with the information that a recursive link chain was detected. This behavior, in turn, led to a failure in `FTPHost.walk` (see ticket #107). ticket: 107
File size: 8.6 KB
Line 
1# encoding: utf-8
2# Copyright (C) 2003-2016, Stefan Schwarzer <sschwarzer@sschwarzer.net>
3# and ftputil contributors (see `doc/contributors.txt`)
4# See the file LICENSE for licensing terms.
5
6from __future__ import unicode_literals
7
8import ftplib
9import time
10
11import pytest
12
13import ftputil
14import ftputil.compat
15import ftputil.error
16import ftputil.tool
17
18from test import mock_ftplib
19from test import test_base
20
21
22class FailingFTPHost(ftputil.FTPHost):
23
24    def _dir(self, path):
25        raise ftputil.error.FTPOSError("simulate a failure, e. g. timeout")
26
27
28# Mock session, used for testing an inaccessible login directory
29class SessionWithInaccessibleLoginDirectory(mock_ftplib.MockSession):
30
31    def cwd(self, dir):
32        # Assume that `dir` is the inaccessible login directory.
33        raise ftplib.error_perm("can't change into this directory")
34
35
36class TestPath(object):
37    """Test operations in `FTPHost.path`."""
38
39    # TODO: Add unit tests for changes for ticket #113
40    # (commits [b4c9b089b6b8] and [4027740cdd2d]).
41    def test_regular_isdir_isfile_islink(self):
42        """Test regular `FTPHost._Path.isdir/isfile/islink`."""
43        host = test_base.ftp_host_factory()
44        testdir = "/home/sschwarzer"
45        host.chdir(testdir)
46        # Test a path which isn't there.
47        assert not host.path.isdir("notthere")
48        assert not host.path.isfile("notthere")
49        assert not host.path.islink("notthere")
50        #  This checks additional code (see ticket #66).
51        assert not host.path.isdir("/notthere/notthere")
52        assert not host.path.isfile("/notthere/notthere")
53        assert not host.path.islink("/notthere/notthere")
54        # Test a directory.
55        assert host.path.isdir(testdir)
56        assert not host.path.isfile(testdir)
57        assert not host.path.islink(testdir)
58        # Test a file.
59        testfile = "/home/sschwarzer/index.html"
60        assert not host.path.isdir(testfile)
61        assert host.path.isfile(testfile)
62        assert not host.path.islink(testfile)
63        # Test a link. Since the link target of `osup` doesn't exist,
64        # neither `isdir` nor `isfile` return `True`.
65        testlink = "/home/sschwarzer/osup"
66        assert not host.path.isdir(testlink)
67        assert not host.path.isfile(testlink)
68        assert host.path.islink(testlink)
69
70    def test_workaround_for_spaces(self):
71        """Test whether the workaround for space-containing paths is used."""
72        host = test_base.ftp_host_factory()
73        testdir = "/home/sschwarzer"
74        host.chdir(testdir)
75        # Test a file name containing spaces.
76        testfile = "/home/dir with spaces/file with spaces"
77        assert not host.path.isdir(testfile)
78        assert host.path.isfile(testfile)
79        assert not host.path.islink(testfile)
80
81    def test_inaccessible_home_directory_and_whitespace_workaround(self):
82        "Test combination of inaccessible home directory + whitespace in path."
83        host = test_base.ftp_host_factory(
84               session_factory=SessionWithInaccessibleLoginDirectory)
85        with pytest.raises(ftputil.error.InaccessibleLoginDirError):
86            host._dir("/home dir")
87
88    def test_isdir_isfile_islink_with_dir_failure(self):
89        """
90        Test failing `FTPHost._Path.isdir/isfile/islink` because of
91        failing `_dir` call.
92        """
93        host = test_base.ftp_host_factory(ftp_host_class=FailingFTPHost)
94        testdir = "/home/sschwarzer"
95        host.chdir(testdir)
96        # Test if exceptions are propagated.
97        FTPOSError = ftputil.error.FTPOSError
98        with pytest.raises(FTPOSError):
99            host.path.isdir("index.html")
100        with pytest.raises(FTPOSError):
101            host.path.isfile("index.html")
102        with pytest.raises(FTPOSError):
103            host.path.islink("index.html")
104
105    def test_isdir_isfile_with_infinite_link_chain(self):
106        """
107        Test if `isdir` and `isfile` return `False` if they encounter
108        an infinite link chain.
109        """
110        host = test_base.ftp_host_factory()
111        assert host.path.isdir("/home/bad_link") is False
112        assert host.path.isfile("/home/bad_link") is False
113
114    def test_exists(self):
115        """Test `FTPHost.path.exists`."""
116        # Regular use of `exists`
117        host = test_base.ftp_host_factory()
118        testdir = "/home/sschwarzer"
119        host.chdir(testdir)
120        assert host.path.exists("index.html")
121        assert not host.path.exists("notthere")
122        # Test if exceptions are propagated.
123        host = test_base.ftp_host_factory(ftp_host_class=FailingFTPHost)
124        with pytest.raises(ftputil.error.FTPOSError):
125            host.path.exists("index.html")
126
127
128class TestAcceptEitherBytesOrUnicode(object):
129
130    def setup_method(self, method):
131        self.host = test_base.ftp_host_factory()
132
133    def _test_method_string_types(self, method, path):
134        expected_type = type(path)
135        assert isinstance(method(path), expected_type)
136
137    def test_methods_that_take_and_return_one_string(self):
138        """
139        Test whether the same string type as for the argument is returned.
140        """
141        bytes_type = ftputil.compat.bytes_type
142        unicode_type = ftputil.compat.unicode_type
143        method_names = ("abspath basename dirname join normcase normpath".
144                        split())
145        for method_name in method_names:
146            method = getattr(self.host.path, method_name)
147            self._test_method_string_types(method, "/")
148            self._test_method_string_types(method, ".")
149            self._test_method_string_types(method, b"/")
150            self._test_method_string_types(method, b".")
151
152    def test_methods_that_take_a_string_and_return_a_bool(self):
153        """Test whether the methods accept byte and unicode strings."""
154        host = self.host
155        as_bytes = ftputil.tool.as_bytes
156        host.chdir("/home/file_name_test")
157        # `isabs`
158        assert not host.path.isabs("ä")
159        assert not host.path.isabs(as_bytes("ä"))
160        # `exists`
161        assert host.path.exists("ä")
162        assert host.path.exists(as_bytes("ä"))
163        # `isdir`, `isfile`, `islink`
164        assert host.path.isdir("ä")
165        assert host.path.isdir(as_bytes("ä"))
166        assert host.path.isfile("ö")
167        assert host.path.isfile(as_bytes("ö"))
168        assert host.path.islink("ü")
169        assert host.path.islink(as_bytes("ü"))
170
171    def test_join(self):
172        """
173        Test whether `FTPHost.path.join` accepts only arguments of
174        the same string type and returns the same string type.
175        """
176        join = self.host.path.join
177        as_bytes = ftputil.tool.as_bytes
178        # Only unicode
179        parts = list("äöü")
180        result = join(*parts)
181        assert result == "ä/ö/ü"
182        #  Need explicit type check for Python 2
183        assert isinstance(result, ftputil.compat.unicode_type)
184        # Only bytes
185        parts = [as_bytes(s) for s in "äöü"]
186        result = join(*parts)
187        assert result == as_bytes("ä/ö/ü")
188        #  Need explicit type check for Python 2
189        assert isinstance(result, ftputil.compat.bytes_type)
190        # Mixture of unicode and bytes
191        parts = ["ä", as_bytes("ö")]
192        with pytest.raises(TypeError):
193            join(*parts)
194        parts = [as_bytes("ä"), as_bytes("ö"), "ü"]
195        with pytest.raises(TypeError):
196            join(*parts)
197
198    def test_getmtime(self):
199        """
200        Test whether `FTPHost.path.getmtime` accepts byte and unicode
201        paths.
202        """
203        host = self.host
204        as_bytes = ftputil.tool.as_bytes
205        host.chdir("/home/file_name_test")
206        # We don't care about the _exact_ time, so don't bother with
207        # timezone differences. Instead, do a simple sanity check.
208        day = 24 * 60 * 60  # seconds
209        expected_mtime = time.mktime((2000, 5, 29, 0, 0, 0, 0, 0, 0))
210        mtime_makes_sense = (lambda mtime: expected_mtime - day <= mtime <=
211                                           expected_mtime + day)
212        assert mtime_makes_sense(host.path.getmtime("ä"))
213        assert mtime_makes_sense(host.path.getmtime(as_bytes("ä")))
214
215    def test_getsize(self):
216        """
217        Test whether `FTPHost.path.getsize` accepts byte and unicode paths.
218        """
219        host = self.host
220        as_bytes = ftputil.tool.as_bytes
221        host.chdir("/home/file_name_test")
222        assert host.path.getsize("ä") == 512
223        assert host.path.getsize(as_bytes("ä")) == 512
224
225    def test_walk(self):
226        """Test whether `FTPHost.path.walk` accepts bytes and unicode paths."""
227        host = self.host
228        as_bytes = ftputil.tool.as_bytes
229        def noop(arg, top, names):
230            del names[:]
231        host.path.walk("ä", noop, None)
232        host.path.walk(as_bytes("ä"), noop, None)
Note: See TracBrowser for help on using the repository browser.