source: test/test_path.py

Last change on this file was 1935:38acf28f3905, checked in by Stefan Schwarzer <sschwarzer@…>, 6 weeks ago
Format docstrings and comments Reformat docstrings and comments to 80 characters. Traditionally, the margin for the code was at 79 columns, so I chose a bit less for the comments. With the switch to Black for formatting the right margin is now at nearly 90 columns, so it makes sense to also allow a greater right margin for docstrings and comments. Lines still don't get too long because the formatted text often starts only in column 5 or 9.
File size: 24.4 KB
Line 
1# Copyright (C) 2003-2020, Stefan Schwarzer <sschwarzer@sschwarzer.net>
2# and ftputil contributors (see `doc/contributors.txt`)
3# See the file LICENSE for licensing terms.
4
5import datetime
6import ftplib
7import time
8
9import pytest
10
11import ftputil
12import ftputil.error
13import ftputil.tool
14
15from test import test_base
16from test import scripted_session
17
18
19Call = scripted_session.Call
20
21
22def as_bytes(string):
23    return string.encode(ftputil.tool.LOSSLESS_ENCODING)
24
25
26class TestPath:
27    """Test operations in `FTPHost.path`."""
28
29    # TODO: Add unit tests for changes for ticket #113 (commits [b4c9b089b6b8]
30    # and [4027740cdd2d]).
31    def test_regular_isdir_isfile_islink(self):
32        """
33        Test regular `FTPHost._Path.isdir/isfile/islink`.
34        """
35        # Test a path which isn't there.
36        script = [
37            Call("__init__"),
38            Call("pwd", result="/"),
39            # `isdir` call
40            Call("cwd", args=("/",)),
41            Call("cwd", args=("/",)),
42            Call("dir", args=("",), result=""),
43            Call("cwd", args=("/",)),
44            # `isfile` call
45            Call("cwd", args=("/",)),
46            Call("cwd", args=("/",)),
47            Call("dir", args=("",), result=""),
48            Call("cwd", args=("/",)),
49            # `islink` call
50            Call("cwd", args=("/",)),
51            Call("cwd", args=("/",)),
52            Call("dir", args=("",), result=""),
53            Call("cwd", args=("/",)),
54            Call("close"),
55        ]
56        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
57            host.stat_cache.disable()
58            assert not host.path.isdir("notthere")
59            assert not host.path.isfile("notthere")
60            assert not host.path.islink("notthere")
61        # This checks additional code (see ticket #66).
62        script = [
63            Call("__init__"),
64            Call("pwd", result="/"),
65            # `isdir` call
66            Call("cwd", args=("/",)),
67            Call("cwd", args=("/",)),
68            Call("dir", args=("",), result=""),
69            Call("cwd", args=("/",)),
70            # `isfile` call
71            Call("cwd", args=("/",)),
72            Call("cwd", args=("/",)),
73            Call("dir", args=("",), result=""),
74            Call("cwd", args=("/",)),
75            # `islink` call
76            Call("cwd", args=("/",)),
77            Call("cwd", args=("/",)),
78            Call("dir", args=("",), result=""),
79            Call("cwd", args=("/",)),
80            Call("close"),
81        ]
82        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
83            host.stat_cache.disable()
84            assert not host.path.isdir("/notthere/notthere")
85            assert not host.path.isfile("/notthere/notthere")
86            assert not host.path.islink("/notthere/notthere")
87        # Test a directory.
88        test_dir = "/some_dir"
89        dir_line = test_base.dir_line(
90            mode_string="dr-xr-xr-x",
91            datetime_=datetime.datetime.now(),
92            name=test_dir.lstrip("/"),
93        )
94        script = [
95            Call("__init__"),
96            Call("pwd", result="/"),
97            # `isdir` call
98            Call("cwd", args=("/",)),
99            Call("cwd", args=("/",)),
100            Call("dir", args=("",), result=dir_line),
101            Call("cwd", args=("/",)),
102            # `isfile` call
103            Call("cwd", args=("/",)),
104            Call("cwd", args=("/",)),
105            Call("dir", args=("",), result=dir_line),
106            Call("cwd", args=("/",)),
107            # `islink` call
108            Call("cwd", args=("/",)),
109            Call("cwd", args=("/",)),
110            Call("dir", args=("",), result=dir_line),
111            Call("cwd", args=("/",)),
112            Call("close"),
113        ]
114        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
115            host.stat_cache.disable()
116            assert host.path.isdir(test_dir)
117            assert not host.path.isfile(test_dir)
118            assert not host.path.islink(test_dir)
119        # Test a file.
120        test_file = "/some_file"
121        dir_line = test_base.dir_line(
122            datetime_=datetime.datetime.now(), name=test_file.lstrip("/")
123        )
124        script = [
125            Call("__init__"),
126            Call("pwd", result="/"),
127            # `isdir` call
128            Call("cwd", args=("/",)),
129            Call("cwd", args=("/",)),
130            Call("dir", args=("",), result=dir_line),
131            Call("cwd", args=("/",)),
132            # `isfile` call
133            Call("cwd", args=("/",)),
134            Call("cwd", args=("/",)),
135            Call("dir", args=("",), result=dir_line),
136            Call("cwd", args=("/",)),
137            # `islink` call
138            Call("cwd", args=("/",)),
139            Call("cwd", args=("/",)),
140            Call("dir", args=("",), result=dir_line),
141            Call("cwd", args=("/",)),
142            Call("close"),
143        ]
144        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
145            host.stat_cache.disable()
146            assert not host.path.isdir(test_file)
147            assert host.path.isfile(test_file)
148            assert not host.path.islink(test_file)
149        # Test a link. Since the link target doesn't exist, neither
150        # `isdir` nor `isfile` return `True`.
151        test_link = "/some_link"
152        dir_line = test_base.dir_line(
153            mode_string="lrwxrwxrwx",
154            datetime_=datetime.datetime.now(),
155            name=test_link.lstrip("/"),
156            link_target="nonexistent",
157        )
158        script = [
159            Call("__init__"),
160            Call("pwd", result="/"),
161            # `isdir` call
162            Call("cwd", args=("/",)),
163            Call("cwd", args=("/",)),
164            #  Look for `/some_link`
165            Call("dir", args=("",), result=dir_line),
166            Call("cwd", args=("/",)),
167            Call("cwd", args=("/",)),
168            Call("cwd", args=("/",)),
169            #  Look for `/nonexistent`
170            Call("dir", args=("",), result=dir_line),
171            Call("cwd", args=("/",)),
172            # `isfile` call
173            Call("cwd", args=("/",)),
174            Call("cwd", args=("/",)),
175            #  Look for `/some_link`
176            Call("dir", args=("",), result=dir_line),
177            Call("cwd", args=("/",)),
178            Call("cwd", args=("/",)),
179            Call("cwd", args=("/",)),
180            #  Look for `/nonexistent`
181            Call("dir", args=("",), result=dir_line),
182            Call("cwd", args=("/",)),
183            # `islink` call
184            Call("cwd", args=("/",)),
185            Call("cwd", args=("/",)),
186            #  Look for `/some_link`. `islink` doesn't try to dereference
187            #  the link.
188            Call("dir", args=("",), result=dir_line),
189            Call("cwd", args=("/",)),
190            Call("close"),
191        ]
192        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
193            host.stat_cache.disable()
194            assert not host.path.isdir(test_link)
195            assert not host.path.isfile(test_link)
196            assert host.path.islink(test_link)
197
198    def test_workaround_for_spaces(self):
199        """
200        Test whether the workaround for space-containing paths is used.
201        """
202        # Test a file name containing spaces.
203        test_file = "/home/dir with spaces/file with spaces"
204        dir_line1 = test_base.dir_line(
205            mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="home"
206        )
207        dir_line2 = test_base.dir_line(
208            mode_string="dr-xr-xr-x",
209            datetime_=datetime.datetime.now(),
210            name="dir with spaces",
211        )
212        dir_line3 = test_base.dir_line(
213            mode_string="-r--r--r--",
214            datetime_=datetime.datetime.now(),
215            name="file with spaces",
216        )
217        script = [
218            Call("__init__"),
219            Call("pwd", result="/"),
220            # `isdir` call
221            Call("cwd", args=("/",)),
222            Call("cwd", args=("/",)),
223            Call("dir", args=("",), result=dir_line1),
224            Call("cwd", args=("/",)),
225            Call("cwd", args=("/",)),
226            Call("cwd", args=("/home",)),
227            Call("dir", args=("",), result=dir_line2),
228            Call("cwd", args=("/",)),
229            Call("cwd", args=("/",)),
230            Call("cwd", args=("/home/dir with spaces",)),
231            Call("dir", args=("",), result=dir_line3),
232            Call("cwd", args=("/",)),
233            # `isfile` call
234            Call("cwd", args=("/",)),
235            Call("cwd", args=("/",)),
236            Call("dir", args=("",), result=dir_line1),
237            Call("cwd", args=("/",)),
238            Call("cwd", args=("/",)),
239            Call("cwd", args=("/home",)),
240            Call("dir", args=("",), result=dir_line2),
241            Call("cwd", args=("/",)),
242            Call("cwd", args=("/",)),
243            Call("cwd", args=("/home/dir with spaces",)),
244            Call("dir", args=("",), result=dir_line3),
245            Call("cwd", args=("/",)),
246            # `islink` call
247            Call("cwd", args=("/",)),
248            Call("cwd", args=("/",)),
249            Call("dir", args=("",), result=dir_line1),
250            Call("cwd", args=("/",)),
251            Call("cwd", args=("/",)),
252            Call("cwd", args=("/home",)),
253            Call("dir", args=("",), result=dir_line2),
254            Call("cwd", args=("/",)),
255            Call("cwd", args=("/",)),
256            Call("cwd", args=("/home/dir with spaces",)),
257            Call("dir", args=("",), result=dir_line3),
258            Call("cwd", args=("/",)),
259            Call("close"),
260        ]
261        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
262            host.stat_cache.disable()
263            assert not host.path.isdir(test_file)
264            assert host.path.isfile(test_file)
265            assert not host.path.islink(test_file)
266
267    def test_inaccessible_home_directory_and_whitespace_workaround(self):
268        """
269        Test combination of inaccessible home directory + whitespace in path.
270        """
271        script = [
272            Call("__init__"),
273            Call("pwd", result="/"),
274            Call("cwd", result=ftplib.error_perm),
275            Call("close"),
276        ]
277        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
278            with pytest.raises(ftputil.error.InaccessibleLoginDirError):
279                host._dir("/home dir")
280
281    def test_isdir_isfile_islink_with_dir_failure(self):
282        """
283        Test failing `FTPHost._Path.isdir/isfile/islink` because of failing
284        `_dir` call.
285        """
286        script = [
287            Call("__init__"),
288            Call("pwd", result="/"),
289            Call("cwd", args=("/",)),
290            Call("cwd", args=("/",)),
291            Call("dir", args=("",), result=ftplib.error_perm),
292            Call("cwd", args=("/",)),
293            Call("close"),
294        ]
295        FTPOSError = ftputil.error.FTPOSError
296        # Test if exceptions are propagated.
297        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
298            with pytest.raises(FTPOSError):
299                host.path.isdir("index.html")
300        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
301            with pytest.raises(FTPOSError):
302                host.path.isfile("index.html")
303        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
304            with pytest.raises(FTPOSError):
305                host.path.islink("index.html")
306
307    def test_isdir_isfile_with_infinite_link_chain(self):
308        """
309        Test if `isdir` and `isfile` return `False` if they encounter an
310        infinite link chain.
311        """
312        # `/home/bad_link` links to `/home/subdir/bad_link`, which links back
313        # to `/home/bad_link` etc.
314        dir_line1 = test_base.dir_line(
315            mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="home"
316        )
317        dir_line2 = test_base.dir_line(
318            mode_string="lrwxrwxrwx",
319            datetime_=datetime.datetime.now(),
320            name="bad_link",
321            link_target="subdir/bad_link",
322        )
323        dir_line3 = test_base.dir_line(
324            mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="subdir"
325        )
326        dir_line4 = test_base.dir_line(
327            mode_string="lrwxrwxrwx",
328            datetime_=datetime.datetime.now(),
329            name="bad_link",
330            link_target="/home/bad_link",
331        )
332        script = [
333            Call("__init__"),
334            Call("pwd", result="/"),
335            Call("cwd", args=("/",)),
336            Call("cwd", args=("/",)),
337            Call("dir", args=("",), result=dir_line1),
338            Call("cwd", args=("/",)),
339            Call("cwd", args=("/",)),
340            Call("cwd", args=("/home",)),
341            Call("dir", args=("",), result=dir_line2 + "\n" + dir_line3),
342            Call("cwd", args=("/",)),
343            Call("cwd", args=("/",)),
344            Call("cwd", args=("/home/subdir",)),
345            Call("dir", args=("",), result=dir_line4),
346            Call("cwd", args=("/",)),
347            Call("close"),
348        ]
349        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
350            assert host.path.isdir("/home/bad_link") is False
351        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
352            assert host.path.isfile("/home/bad_link") is False
353
354    def test_exists(self):
355        """
356        Test `FTPHost.path.exists`.
357        """
358        # Regular use of `exists`
359        dir_line1 = test_base.dir_line(
360            datetime_=datetime.datetime.now(), name="some_file"
361        )
362        script = [
363            Call("__init__"),
364            Call("pwd", result="/"),
365            # `exists("some_file")`
366            Call("cwd", args=("/",)),
367            Call("cwd", args=("/",)),
368            Call("dir", args=("",), result=dir_line1),
369            Call("cwd", args=("/",)),
370            # `exists("notthere")`
371            Call("cwd", args=("/",)),
372            Call("cwd", args=("/",)),
373            Call("dir", args=("",), result=dir_line1),
374            Call("cwd", args=("/",)),
375            # `exists` with failing `dir` call
376            Call("cwd", args=("/",)),
377            Call("cwd", args=("/",)),
378            Call("dir", args=("",), result=ftplib.error_perm),
379            Call("cwd", args=("/",)),
380            Call("close"),
381        ]
382        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
383            host.stat_cache.disable()
384            assert host.path.exists("some_file")
385            assert not host.path.exists("notthere")
386            # Test if exceptions are propagated.
387            with pytest.raises(ftputil.error.FTPOSError):
388                host.path.exists("some_file")
389
390
391class TestAcceptEitherBytesOrStr:
392
393    # Use path arguments directly
394    path_converter = staticmethod(lambda path: path)
395
396    def _test_method_string_types(self, method, path):
397        expected_type = type(path)
398        path_converter = self.path_converter
399        assert isinstance(method(path_converter(path)), expected_type)
400
401    def test_methods_that_take_and_return_one_string(self):
402        """
403        Test whether the same string type as for the argument is returned.
404        """
405        method_names = [
406            "abspath",
407            "basename",
408            "dirname",
409            "join",
410            "normcase",
411            "normpath",
412        ]
413        script = [Call("__init__"), Call("pwd", result="/"), Call("close")]
414        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
415            for method_name in method_names:
416                method = getattr(host.path, method_name)
417                self._test_method_string_types(method, "/")
418                self._test_method_string_types(method, ".")
419                self._test_method_string_types(method, b"/")
420                self._test_method_string_types(method, b".")
421
422    def test_methods_that_take_a_string_and_return_a_bool(self):
423        """
424        Test whether the methods accept byte and unicode strings.
425        """
426        path_converter = self.path_converter
427        script = [
428            Call("__init__"),
429            Call("pwd", result="/"),
430            # `exists` test 1
431            Call("cwd", args=("/",)),
432            Call("cwd", args=("/",)),
433            Call(
434                "dir",
435                args=("",),
436                result=test_base.dir_line(name="ä", datetime_=datetime.datetime.now()),
437            ),
438            Call("cwd", args=("/",)),
439            # `exists` test 2
440            Call("cwd", args=("/",)),
441            Call("cwd", args=("/",)),
442            Call(
443                "dir",
444                args=("",),
445                result=test_base.dir_line(name="ä", datetime_=datetime.datetime.now()),
446            ),
447            Call("cwd", args=("/",)),
448            # `isdir` test 1
449            Call("cwd", args=("/",)),
450            Call("cwd", args=("/",)),
451            Call(
452                "dir",
453                args=("",),
454                result=test_base.dir_line(
455                    mode_string="dr-xr-xr-x",
456                    name="ä",
457                    datetime_=datetime.datetime.now(),
458                ),
459            ),
460            Call("cwd", args=("/",)),
461            # `isdir` test 2
462            Call("cwd", args=("/",)),
463            Call("cwd", args=("/",)),
464            Call(
465                "dir",
466                args=("",),
467                result=test_base.dir_line(
468                    mode_string="dr-xr-xr-x",
469                    name="ä",
470                    datetime_=datetime.datetime.now(),
471                ),
472            ),
473            Call("cwd", args=("/",)),
474            # `isfile` test 1
475            Call("cwd", args=("/",)),
476            Call("cwd", args=("/",)),
477            Call(
478                "dir",
479                args=("",),
480                result=test_base.dir_line(name="ö", datetime_=datetime.datetime.now()),
481            ),
482            Call("cwd", args=("/",)),
483            # `isfile` test 2
484            Call("cwd", args=("/",)),
485            Call("cwd", args=("/",)),
486            Call(
487                "dir",
488                args=("",),
489                result=test_base.dir_line(name="ö", datetime_=datetime.datetime.now()),
490            ),
491            Call("cwd", args=("/",)),
492            # `islink` test 1
493            Call("cwd", args=("/",)),
494            Call("cwd", args=("/",)),
495            Call(
496                "dir",
497                args=("",),
498                result=test_base.dir_line(
499                    mode_string="lrwxrwxrwx",
500                    name="ü",
501                    datetime_=datetime.datetime.now(),
502                    link_target="unimportant",
503                ),
504            ),
505            Call("cwd", args=("/",)),
506            # `islink` test 2
507            Call("cwd", args=("/",)),
508            Call("cwd", args=("/",)),
509            Call(
510                "dir",
511                args=("",),
512                result=test_base.dir_line(
513                    mode_string="lrwxrwxrwx",
514                    name="ü",
515                    datetime_=datetime.datetime.now(),
516                    link_target="unimportant",
517                ),
518            ),
519            Call("cwd", args=("/",)),
520            Call("close"),
521        ]
522        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
523            host.stat_cache.disable()
524            # `isabs`
525            assert not host.path.isabs("ä")
526            assert not host.path.isabs(path_converter(as_bytes("ä")))
527            # `exists`
528            assert host.path.exists(path_converter("ä"))
529            assert host.path.exists(path_converter(as_bytes("ä")))
530            # `isdir`, `isfile`, `islink`
531            assert host.path.isdir(path_converter("ä"))
532            assert host.path.isdir(path_converter(as_bytes("ä")))
533            assert host.path.isfile(path_converter("ö"))
534            assert host.path.isfile(path_converter(as_bytes("ö")))
535            assert host.path.islink(path_converter("ü"))
536            assert host.path.islink(path_converter(as_bytes("ü")))
537
538    def test_getmtime(self):
539        """
540        Test whether `FTPHost.path.getmtime` accepts byte and unicode paths.
541        """
542        path_converter = self.path_converter
543        now = datetime.datetime.utcnow()
544        script = [
545            Call("__init__"),
546            Call("pwd", result="/"),
547            # `getmtime` call 1
548            Call("cwd", args=("/",)),
549            Call("cwd", args=("/",)),
550            Call("dir", args=("",), result=test_base.dir_line(name="ä", datetime_=now)),
551            Call("cwd", args=("/",)),
552            # `getmtime` call 2
553            Call("cwd", args=("/",)),
554            Call("cwd", args=("/",)),
555            Call("dir", args=("",), result=test_base.dir_line(name="ä", datetime_=now)),
556            Call("cwd", args=("/",)),
557            Call("close"),
558        ]
559        expected_mtime = now.timestamp()
560        # We don't care about the _exact_ time, so don't bother with timezone
561        # differences. Instead, do a simple sanity check.
562        day = 24 * 60 * 60  # seconds
563        mtime_makes_sense = (
564            lambda mtime: expected_mtime - day <= mtime <= expected_mtime + day
565        )
566        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
567            host.stat_cache.disable()
568            assert mtime_makes_sense(host.path.getmtime(path_converter(("ä"))))
569            assert mtime_makes_sense(host.path.getmtime(path_converter(as_bytes("ä"))))
570
571    def test_getsize(self):
572        """
573        Test whether `FTPHost.path.getsize` accepts byte and unicode paths.
574        """
575        path_converter = self.path_converter
576        now = datetime.datetime.now()
577        script = [
578            Call("__init__"),
579            Call("pwd", result="/"),
580            # `getsize` call 1
581            Call("cwd", args=("/",)),
582            Call("cwd", args=("/",)),
583            Call(
584                "dir",
585                args=("",),
586                result=test_base.dir_line(name="ä", size=512, datetime_=now),
587            ),
588            Call("cwd", args=("/",)),
589            # `getsize` call 2
590            Call("cwd", args=("/",)),
591            Call("cwd", args=("/",)),
592            Call(
593                "dir",
594                args=("",),
595                result=test_base.dir_line(name="ä", size=512, datetime_=now),
596            ),
597            Call("cwd", args=("/",)),
598            Call("close"),
599        ]
600        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
601            host.stat_cache.disable()
602            assert host.path.getsize(path_converter("ä")) == 512
603            assert host.path.getsize(path_converter(as_bytes("ä"))) == 512
604
605    def test_walk(self):
606        """
607        Test whether `FTPHost.path.walk` accepts bytes and unicode paths.
608        """
609        path_converter = self.path_converter
610        now = datetime.datetime.now()
611        script = [
612            Call("__init__"),
613            Call("pwd", result="/"),
614            # `walk` call 1
615            Call("cwd", args=("/",)),
616            Call("cwd", args=("/",)),
617            Call(
618                "dir",
619                args=("",),
620                result=test_base.dir_line(
621                    mode_string="dr-xr-xr-x", name="ä", size=512, datetime_=now
622                ),
623            ),
624            Call("cwd", args=("/",)),
625            Call("cwd", args=("/",)),
626            Call("cwd", args=("/ä",)),
627            #  Assume directory `ä` is empty.
628            Call("dir", args=("",), result=""),
629            Call("cwd", args=("/",)),
630            # `walk` call 2
631            Call("cwd", args=("/",)),
632            Call("cwd", args=("/",)),
633            Call(
634                "dir",
635                args=("",),
636                result=test_base.dir_line(
637                    mode_string="dr-xr-xr-x", name="ä", size=512, datetime_=now
638                ),
639            ),
640            Call("cwd", args=("/",)),
641            Call("cwd", args=("/",)),
642            Call("cwd", args=("/ä",)),
643            #  Assume directory `ä` is empty.
644            Call("dir", args=("",), result=""),
645            Call("cwd", args=("/",)),
646            Call("close"),
647        ]
648
649        def noop(arg, top, names):
650            del names[:]
651
652        with test_base.ftp_host_factory(scripted_session.factory(script)) as host:
653            host.stat_cache.disable()
654            host.path.walk(path_converter("ä"), func=noop, arg=None)
655            host.path.walk(path_converter(as_bytes("ä")), func=noop, arg=None)
656
657
658class Path:
659    def __init__(self, path):
660        self.path = path
661
662    def __fspath__(self):
663        return self.path
664
665
666class TestAcceptEitherBytesOrStrFromPath(TestAcceptEitherBytesOrStr):
667
668    # Take path arguments from `Path(...)` objects
669    path_converter = Path
Note: See TracBrowser for help on using the repository browser.