source: _test_real_ftp.py @ 858:39a3da8880a7

Last change on this file since 858:39a3da8880a7 was 858:39a3da8880a7, checked in by Stefan Schwarzer <sschwarzer@…>, 11 years ago
Added test and fix for ticket #46 (thanks go to Steve Steiner).
File size: 24.8 KB
Line 
1# Copyright (C) 2003-2010, Stefan Schwarzer
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8# - Redistributions of source code must retain the above copyright
9#   notice, this list of conditions and the following disclaimer.
10#
11# - Redistributions in binary form must reproduce the above copyright
12#   notice, this list of conditions and the following disclaimer in the
13#   documentation and/or other materials provided with the distribution.
14#
15# - Neither the name of the above author nor the names of the
16#   contributors to the software may be used to endorse or promote
17#   products derived from this software without specific prior written
18#   permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
24# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32# Execute a test on a real FTP server (other tests use a mock server)
33
34import getpass
35import operator
36import os
37import time
38import unittest
39import stat
40import sys
41
42import ftputil
43from ftputil import ftp_error
44from ftputil import ftp_stat
45
46
47def get_login_data():
48    """
49    Return a three-element tuple consisting of server name, user id
50    and password. The data - used to be - requested interactively.
51    """
52    #server = raw_input("Server: ")
53    #user = raw_input("User: ")
54    #password = getpass.getpass()
55    #return server, user, password
56    return "localhost", 'ftptest', 'd605581757de5eb56d568a4419f4126e'
57
58def utc_local_time_shift():
59    """
60    Return the expected time shift in seconds assuming the server
61    uses UTC in its listings and the client uses local time.
62
63    This is needed because Pure-FTPd meanwhile seems to insist that
64    the displayed time for files is in UTC.
65    """
66    utc_tuple = time.gmtime()
67    localtime_tuple = time.localtime()
68    # to calculate the correct times shift, we need to ignore the
69    #  DST component in the localtime tuple, i. e. set it to 0
70    localtime_tuple = localtime_tuple[:-1] + (0,)
71    time_shift_in_seconds = time.mktime(utc_tuple) - \
72                            time.mktime(localtime_tuple)
73    # to be safe, round the above value to units of 3600 s (1 hour)
74    return round(time_shift_in_seconds / 3600.0) * 3600
75
76# difference between local times of server and client; if 0.0, server
77#  and client use the same timezone
78#EXPECTED_TIME_SHIFT = utc_local_time_shift()
79# Pure-FTPd seems to have changed its mind (see docstring of
80#  `utc_local_time_shift`
81EXPECTED_TIME_SHIFT = 0.0
82
83
84class Cleaner(object):
85    """This class helps to remove directories and files which
86    might be left behind if a test fails in unexpected ways.
87    """
88
89    def __init__(self, host):
90        # the test class (probably `RealFTPTest`) and the helper
91        #  class share the same `FTPHost` object
92        self._host = host
93        self._ftp_items = []
94
95    def add_dir(self, path):
96        """Schedule a directory with path `path` for removal."""
97        self._ftp_items.append(('d', self._host.path.abspath(path)))
98
99    def add_file(self, path):
100        """Schedule a file with path `path` for removal."""
101        self._ftp_items.append(('f', self._host.path.abspath(path)))
102
103    def clean(self):
104        """Remove the directories and files previously remembered.
105        The removal works in reverse order of the scheduling with
106        `add_dir` and `add_file`.
107
108        Errors due to a removal are ignored.
109        """
110        self._host.chdir("/")
111        # code should work with Python 2.3
112        self._ftp_items.reverse()
113        for type_, path in self._ftp_items:
114            try:
115                if type_ == 'd':
116                    # if something goes wrong in `rmtree` we might
117                    #  leave a mess behind
118                    self._host.rmtree(path)
119                elif type_ == 'f':
120                    # minor mess if `remove` fails
121                    self._host.remove(path)
122            except ftp_error.FTPError:
123                pass
124
125
126class RealFTPTest(unittest.TestCase):
127    def setUp(self):
128        self.host = ftputil.FTPHost(server, user, password)
129        self.cleaner = Cleaner(self.host)
130
131    def tearDown(self):
132        self.cleaner.clean()
133        self.host.close()
134
135    #
136    # helper methods
137    #
138    def make_file(self, path):
139        self.cleaner.add_file(path)
140        file_ = self.host.file(path, 'wb')
141        # write something; otherwise the FTP server might not update
142        #  the time of last modification if the file existed before
143        file_.write("\n")
144        file_.close()
145
146    def make_local_file(self):
147        fobj = file('_local_file_', 'wb')
148        fobj.write("abc\x12\x34def\t")
149        fobj.close()
150
151    #
152    # `mkdir`, `makedirs`, `rmdir` and `rmtree`
153    #
154    def test_mkdir_rmdir(self):
155        host = self.host
156        dir_name = "_testdir_"
157        file_name = host.path.join(dir_name, "_nonempty_")
158        self.cleaner.add_dir(dir_name)
159        # make dir and check if it's there
160        host.mkdir(dir_name)
161        files = host.listdir(host.curdir)
162        self.failIf(dir_name not in files)
163        # try to remove non-empty directory
164        self.cleaner.add_file(file_name)
165        non_empty = host.file(file_name, "w")
166        non_empty.close()
167        self.assertRaises(ftp_error.PermanentError, host.rmdir, dir_name)
168        # remove file
169        host.unlink(file_name)
170        # `remove` on a directory should fail
171        try:
172            try:
173                host.remove(dir_name)
174            except ftp_error.PermanentError, exc:
175                self.failUnless(str(exc).startswith(
176                                "remove/unlink can only delete files"))
177            else:
178                self.failIf(True, "we shouldn't have come here")
179        finally:
180            # delete empty directory
181            host.rmdir(dir_name)
182        files = host.listdir(host.curdir)
183        self.failIf(dir_name in files)
184
185    def test_makedirs_without_existing_dirs(self):
186        host = self.host
187        # no `_dir1_` yet
188        self.failIf('_dir1_' in host.listdir(host.curdir))
189        # vanilla case, all should go well
190        host.makedirs('_dir1_/dir2/dir3/dir4')
191        self.cleaner.add_dir('_dir1_')
192        # check host
193        self.failUnless(host.path.isdir('_dir1_'))
194        self.failUnless(host.path.isdir('_dir1_/dir2'))
195        self.failUnless(host.path.isdir('_dir1_/dir2/dir3'))
196        self.failUnless(host.path.isdir('_dir1_/dir2/dir3/dir4'))
197
198    def test_makedirs_from_non_root_directory(self):
199        # this is a testcase for issue #22, see
200        #  http://ftputil.sschwarzer.net/trac/ticket/22
201        host = self.host
202        # no `_dir1_` and `_dir2_` yet
203        self.failIf('_dir1_' in host.listdir(host.curdir))
204        self.failIf('_dir2_' in host.listdir(host.curdir))
205        # part 1: try to make directories starting from `_dir1_`
206        # make and change to non-root directory
207        self.cleaner.add_dir("_dir1_")
208        host.mkdir('_dir1_')
209        host.chdir('_dir1_')
210        host.makedirs('_dir2_/_dir3_')
211        # test for expected directory hierarchy
212        self.failUnless(host.path.isdir('/_dir1_'))
213        self.failUnless(host.path.isdir('/_dir1_/_dir2_'))
214        self.failUnless(host.path.isdir('/_dir1_/_dir2_/_dir3_'))
215        self.failIf(host.path.isdir('/_dir1_/_dir1_'))
216        # remove all but the directory we're in
217        host.rmdir('/_dir1_/_dir2_/_dir3_')
218        host.rmdir('/_dir1_/_dir2_')
219        # part 2: try to make directories starting from root
220        self.cleaner.add_dir("/_dir2_")
221        host.makedirs('/_dir2_/_dir3_')
222        # test for expected directory hierarchy
223        self.failUnless(host.path.isdir('/_dir2_'))
224        self.failUnless(host.path.isdir('/_dir2_/_dir3_'))
225        self.failIf(host.path.isdir('/_dir1_/_dir2_'))
226
227    def test_makedirs_from_non_root_directory_fake_windows_os(self):
228        saved_sep = os.sep
229        os.sep = '\\'
230        try:
231            self.test_makedirs_from_non_root_directory()
232        finally:
233            os.sep = saved_sep
234
235    def test_makedirs_of_existing_directory(self):
236        host = self.host
237        # the (chrooted) login directory
238        host.makedirs('/')
239
240    def test_makedirs_with_file_in_the_way(self):
241        host = self.host
242        self.cleaner.add_dir('_dir1_')
243        host.mkdir('_dir1_')
244        self.make_file('_dir1_/file1')
245        # try it
246        self.assertRaises(ftp_error.PermanentError, host.makedirs,
247                          '_dir1_/file1')
248        self.assertRaises(ftp_error.PermanentError, host.makedirs,
249                          '_dir1_/file1/dir2')
250
251    def test_makedirs_with_existing_directory(self):
252        host = self.host
253        self.cleaner.add_dir("_dir1_")
254        host.mkdir('_dir1_')
255        host.makedirs('_dir1_/dir2')
256        # check
257        self.failUnless(host.path.isdir('_dir1_'))
258        self.failUnless(host.path.isdir('_dir1_/dir2'))
259
260    def test_makedirs_in_non_writable_directory(self):
261        host = self.host
262        # preparation: `rootdir1` exists but is only writable by root
263        self.assertRaises(ftp_error.PermanentError, host.makedirs,
264                          'rootdir1/dir2')
265
266    def test_makedirs_with_writable_directory_at_end(self):
267        host = self.host
268        self.cleaner.add_dir('rootdir2/dir2')
269        # preparation: `rootdir2` exists but is only writable by root;
270        #  `dir2` is writable by regular ftp user
271        # these both should work
272        host.makedirs('rootdir2/dir2')
273        host.makedirs('rootdir2/dir2/dir3')
274
275    def test_rmtree_without_error_handler(self):
276        host = self.host
277        # build a tree
278        self.cleaner.add_dir('_dir1_')
279        host.makedirs('_dir1_/dir2')
280        self.make_file('_dir1_/file1')
281        self.make_file('_dir1_/file2')
282        self.make_file('_dir1_/dir2/file3')
283        self.make_file('_dir1_/dir2/file4')
284        # try to remove a _file_ with `rmtree`
285        self.assertRaises(ftp_error.PermanentError, host.rmtree, '_dir1_/file2')
286        # remove dir2
287        host.rmtree('_dir1_/dir2')
288        self.failIf(host.path.exists('_dir1_/dir2'))
289        self.failUnless(host.path.exists('_dir1_/file2'))
290        # remake dir2 and remove _dir1_
291        host.mkdir('_dir1_/dir2')
292        self.make_file('_dir1_/dir2/file3')
293        self.make_file('_dir1_/dir2/file4')
294        host.rmtree('_dir1_')
295        self.failIf(host.path.exists('_dir1_'))
296
297    def test_rmtree_with_error_handler(self):
298        host = self.host
299        self.cleaner.add_dir('_dir1_')
300        host.mkdir('_dir1_')
301        self.make_file('_dir1_/file1')
302        # prepare error "handler"
303        log = []
304        def error_handler(*args):
305            log.append(args)
306        # try to remove a file as root "directory"
307        host.rmtree('_dir1_/file1', ignore_errors=True, onerror=error_handler)
308        self.assertEqual(log, [])
309        host.rmtree('_dir1_/file1', ignore_errors=False, onerror=error_handler)
310        self.assertEqual(log[0][0], host.listdir)
311        self.assertEqual(log[0][1], '_dir1_/file1')
312        self.assertEqual(log[1][0], host.rmdir)
313        self.assertEqual(log[1][1], '_dir1_/file1')
314        host.rmtree('_dir1_')
315        # try to remove a non-existent directory
316        del log[:]
317        host.rmtree('_dir1_', ignore_errors=False, onerror=error_handler)
318        self.assertEqual(log[0][0], host.listdir)
319        self.assertEqual(log[0][1], '_dir1_')
320        self.assertEqual(log[1][0], host.rmdir)
321        self.assertEqual(log[1][1], '_dir1_')
322
323    #
324    # directory tree walking
325    #
326    def test_walk_topdown(self):
327        # preparation: build tree in directory `walk_test`
328        host = self.host
329        expected = [
330          ('walk_test', ['dir1', 'dir2', 'dir3'], ['file4']),
331          ('walk_test/dir1', ['dir11', 'dir12'], []),
332          ('walk_test/dir1/dir11', [], []),
333          ('walk_test/dir1/dir12', ['dir123'], ['file121', 'file122']),
334          ('walk_test/dir1/dir12/dir123', [], ['file1234']),
335          ('walk_test/dir2', [], []),
336          ('walk_test/dir3', ['dir33'], ['file31', 'file32']),
337          ('walk_test/dir3/dir33', [], []),
338          ]
339        # collect data, using `walk`
340        actual = []
341        for items in host.walk('walk_test'):
342            actual.append(items)
343        # compare with expected results
344        self.assertEqual(len(actual), len(expected))
345        for index in range(len(actual)):
346            self.assertEqual(actual[index], expected[index])
347
348    def test_walk_depth_first(self):
349        # preparation: build tree in directory `walk_test`
350        host = self.host
351        expected = [
352          ('walk_test/dir1/dir11', [], []),
353          ('walk_test/dir1/dir12/dir123', [], ['file1234']),
354          ('walk_test/dir1/dir12', ['dir123'], ['file121', 'file122']),
355          ('walk_test/dir1', ['dir11', 'dir12'], []),
356          ('walk_test/dir2', [], []),
357          ('walk_test/dir3/dir33', [], []),
358          ('walk_test/dir3', ['dir33'], ['file31', 'file32']),
359          ('walk_test', ['dir1', 'dir2', 'dir3'], ['file4'])
360          ]
361        # collect data, using `walk`
362        actual = []
363        for items in host.walk('walk_test', topdown=False):
364            actual.append(items)
365        # compare with expected results
366        self.assertEqual(len(actual), len(expected))
367        for index in range(len(actual)):
368            self.assertEqual(actual[index], expected[index])
369
370    #
371    # renaming
372    #
373    def test_rename(self):
374        host = self.host
375        # make sure the target of the renaming operation is removed
376        self.cleaner.add_file('_testfile2_')
377        self.make_file("_testfile1_")
378        host.rename('_testfile1_', '_testfile2_')
379        self.failIf(host.path.exists('_testfile1_'))
380        self.failUnless(host.path.exists('_testfile2_'))
381        host.remove('_testfile2_')
382
383    def test_rename_with_spaces_in_directory(self):
384        host = self.host
385        dir_name = "_dir with spaces_"
386        self.cleaner.add_dir(dir_name)
387        host.mkdir(dir_name)
388        self.make_file(dir_name + "/testfile1")
389        host.rename(dir_name + "/testfile1", dir_name + "/testfile2")
390        self.failIf(host.path.exists(dir_name + "/testfile1"))
391        self.failUnless(host.path.exists(dir_name + "/testfile2"))
392
393    #
394    # stat'ing
395    #
396    def test_stat(self):
397        host = self.host
398        dir_name = "_testdir_"
399        file_name = host.path.join(dir_name, "_nonempty_")
400        # make a directory and a file in it
401        self.cleaner.add_dir(dir_name)
402        host.mkdir(dir_name)
403        fobj = host.file(file_name, "wb")
404        fobj.write("abc\x12\x34def\t")
405        fobj.close()
406        # do some stats
407        # - dir
408        self.assertEqual(host.listdir(dir_name), ["_nonempty_"])
409        self.assertEqual(bool(host.path.isdir(dir_name)), True)
410        self.assertEqual(bool(host.path.isfile(dir_name)), False)
411        self.assertEqual(bool(host.path.islink(dir_name)), False)
412        # - file
413        self.assertEqual(bool(host.path.isdir(file_name)), False)
414        self.assertEqual(bool(host.path.isfile(file_name)), True)
415        self.assertEqual(bool(host.path.islink(file_name)), False)
416        self.assertEqual(host.path.getsize(file_name), 9)
417        # - file's modification time; allow up to two minutes difference
418        host.synchronize_times()
419        server_mtime = host.path.getmtime(file_name)
420        client_mtime = time.mktime(time.localtime())
421        calculated_time_shift = server_mtime - client_mtime
422        self.failIf(abs(calculated_time_shift-host.time_shift()) > 120)
423
424#    def test_special_broken_link(self):
425#        # test for ticket #39
426#        # this test currently fails; I guess I'll postpone it until
427#        #  at least ftputil 2.5
428#        host = self.host
429#        broken_link_name = os.path.join("dir_with_broken_link", "nonexistent")
430#        self.assertEqual(host.lstat(broken_link_name)._st_target,
431#                         "../nonexistent/nonexistent")
432#        self.assertEqual(bool(host.path.isdir(broken_link_name)), False)
433#        self.assertEqual(bool(host.path.isfile(broken_link_name)), False)
434#        self.assertEqual(bool(host.path.islink(broken_link_name)), True)
435
436    def test_concurrent_access(self):
437        self.make_file("_testfile_")
438        host1 = ftputil.FTPHost(server, user, password)
439        host2 = ftputil.FTPHost(server, user, password)
440        stat_result1 = host1.stat("_testfile_")
441        stat_result2 = host2.stat("_testfile_")
442        self.assertEqual(stat_result1, stat_result2)
443        host2.remove("_testfile_")
444        # can still get the result via `host1`
445        stat_result1 = host1.stat("_testfile_")
446        self.assertEqual(stat_result1, stat_result2)
447        # stat'ing on `host2` gives an exception
448        self.assertRaises(ftp_error.PermanentError, host2.stat, "_testfile_")
449        # stat'ing on `host1` after invalidation
450        absolute_path = host1.path.join(host1.getcwd(), "_testfile_")
451        host1.stat_cache.invalidate(absolute_path)
452        self.assertRaises(ftp_error.PermanentError, host1.stat, "_testfile_")
453
454    #
455    # `upload` (including time shift test)
456    #
457    def test_time_shift(self):
458        self.host.synchronize_times()
459        self.assertEqual(self.host.time_shift(), EXPECTED_TIME_SHIFT)
460
461    def test_upload(self):
462        host = self.host
463        host.synchronize_times()
464        local_file = '_local_file_'
465        remote_file = '_remote_file_'
466        # make local file to upload
467        self.make_local_file()
468        # wait; else small time differences between client and server
469        #  actually could trigger the update
470        time.sleep(65)
471        try:
472            self.cleaner.add_file(remote_file)
473            host.upload(local_file, remote_file, 'b')
474            # retry; shouldn't be uploaded
475            uploaded = host.upload_if_newer(local_file, remote_file, 'b')
476            self.assertEqual(uploaded, False)
477            # rewrite the local file
478            self.make_local_file()
479            # retry; should be uploaded now
480            uploaded = host.upload_if_newer(local_file, remote_file, 'b')
481            self.assertEqual(uploaded, True)
482        finally:
483            # clean up
484            os.unlink(local_file)
485
486    def test_download(self):
487        host = self.host
488        host.synchronize_times()
489        local_file = '_local_file_'
490        remote_file = '_remote_file_'
491        # make a remote file
492        self.make_file(remote_file)
493        # file should be downloaded as it's not present yet
494        downloaded = host.download_if_newer(remote_file, local_file, 'b')
495        self.assertEqual(downloaded, True)
496        try:
497            # local file is present and newer, so shouldn't download
498            downloaded = host.download_if_newer(remote_file, local_file, 'b')
499            self.assertEqual(downloaded, False)
500            # wait; else small time differences between client and server
501            #  actually could trigger the update
502            time.sleep(65)
503            # re-make the remote file
504            self.make_file(remote_file)
505            # local file is present but older, so should download
506            downloaded = host.download_if_newer(remote_file, local_file, 'b')
507            self.assertEqual(downloaded, True)
508        finally:
509            # clean up
510            os.unlink(local_file)
511
512    #
513    # remove/unlink
514    #
515    def test_remove_non_existent_item(self):
516        host = self.host
517        self.assertRaises(ftp_error.PermanentError, host.remove, "nonexistent")
518
519    def test_remove_existent_file(self):
520        self.cleaner.add_file('_testfile_')
521        self.make_file('_testfile_')
522        host = self.host
523        self.failUnless(host.path.isfile('_testfile_'))
524        host.remove('_testfile_')
525        self.failIf(host.path.exists('_testfile_'))
526
527    #
528    # `chmod`
529    #
530    def assert_mode(self, path, expected_mode):
531        """Return an integer containing the allowed bits in the
532        mode change command.
533
534        The `FTPHost` object to test against is `self.host`.
535        """
536        full_mode = self.host.stat(path).st_mode
537        # remove flags we can't set via `chmod`
538        # allowed flags according to Python documentation
539        #  http://docs.python.org/lib/os-file-dir.html
540        allowed_flags = [stat.S_ISUID, stat.S_ISGID, stat.S_ENFMT,
541          stat.S_ISVTX, stat.S_IREAD, stat.S_IWRITE, stat.S_IEXEC,
542          stat.S_IRWXU, stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR,
543          stat.S_IRWXG, stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP,
544          stat.S_IRWXO, stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH]
545        allowed_mask = reduce(operator.or_, allowed_flags)
546        mode = full_mode & allowed_mask
547        self.assertEqual(mode, expected_mode,
548                         "mode %s != %s" % (oct(mode), oct(expected_mode)))
549
550    def test_chmod_existing_directory(self):
551        host = self.host
552        host.mkdir("_test dir_")
553        self.cleaner.add_dir("_test dir_")
554        # set/get mode of a directory
555        host.chmod("_test dir_", 0757)
556        self.assert_mode("_test dir_", 0757)
557        # set/get mode in nested directory
558        host.mkdir("_test dir_/nested_dir")
559        self.cleaner.add_dir("_test dir_/nested_dir")
560        # set/get mode of a directory
561        host.chmod("_test dir_/nested_dir", 0757)
562        self.assert_mode("_test dir_/nested_dir", 0757)
563
564    def test_chmod_existing_file(self):
565        host = self.host
566        host.mkdir("_test dir_")
567        self.cleaner.add_dir("_test dir_")
568        # set/get mode on a file
569        file_name = host.path.join("_test dir_", "_testfile_")
570        self.make_file(file_name)
571        host.chmod(file_name, 0646)
572        self.assert_mode(file_name, 0646)
573
574    def test_chmod_nonexistent_path(self):
575        # set/get mode of a directory
576        self.assertRaises(ftp_error.PermanentError, self.host.chmod,
577                          "nonexistent", 0757)
578
579    def test_cache_invalidation(self):
580        host = self.host
581        host.mkdir("_test dir_")
582        self.cleaner.add_dir("_test dir_")
583        # make sure the mode is in the cache
584        unused_stat_result = host.stat("_test dir_")
585        # set/get mode of a directory
586        host.chmod("_test dir_", 0757)
587        self.assert_mode("_test dir_", 0757)
588        # set/get mode on a file
589        file_name = host.path.join("_test dir_", "_testfile_")
590        self.make_file(file_name)
591        # make sure the mode is in the cache
592        unused_stat_result = host.stat(file_name)
593        host.chmod(file_name, 0646)
594        self.assert_mode(file_name, 0646)
595
596    #
597    # other tests
598    #
599    def test_open_for_reading(self):
600        # test for issue #17, http://ftputil.sschwarzer.net/trac/ticket/17
601        file1 = self.host.file("debian-keyring.tar.gz", 'rb')
602        file1.close()
603        # make sure that there are no problems if the connection is reused
604        file2 = self.host.file("debian-keyring.tar.gz", 'rb')
605        file2.close()
606        self.failUnless(file1._session is file2._session)
607
608    def test_names_with_spaces(self):
609        # test if directories and files with spaces in their names
610        #  can be used
611        host = self.host
612        self.failUnless(host.path.isdir("dir with spaces"))
613        self.assertEqual(host.listdir("dir with spaces"),
614                         ['second dir', 'some file', 'some_file'])
615        self.failUnless(host.path.isdir("dir with spaces/second dir"))
616        self.failUnless(host.path.isfile("dir with spaces/some_file"))
617        self.failUnless(host.path.isfile("dir with spaces/some file"))
618
619    def test_synchronize_times_without_write_access(self):
620        """Test failing synchronization because of non-writable directory."""
621        host = self.host
622        # this isn't writable by the ftp account the tests are run under
623        host.chdir("rootdir1")
624        self.assertRaises(ftp_error.TimeShiftError, host.synchronize_times)
625
626
627if __name__ == '__main__':
628    print """\
629Test for real FTP access.
630
631This test writes some files and directories on the local client and the
632remote server. Thus, you may want to skip this test by pressing [Ctrl-C].
633If the test should run, enter the login data for the remote server. You
634need write access in the login directory. This test can last a few minutes
635because it has to wait to test the timezone calculation.
636"""
637    try:
638        raw_input("[Return] to continue, or [Ctrl-C] to skip test. ")
639    except KeyboardInterrupt:
640        print "\nTest aborted."
641        sys.exit()
642    # get login data only once, not for each test
643    server, user, password = get_login_data()
644    unittest.main()
645
Note: See TracBrowser for help on using the repository browser.