source: test/mock_ftplib.py @ 1139:31a1b5455047

Last change on this file since 1139:31a1b5455047 was 1139:31a1b5455047, checked in by Stefan Schwarzer <sschwarzer@…>, 8 years ago
Don't raise `PermanentError` for `host.path.isdir("/dir/subdir")` if even the directory doesn't exist. The fix was rather simple, but now ftputil does a lot more behind the scenes; it traverses up from the path given as argument and does a stat call on each of these traversals. This means that in `mock_ftplib.MockSession`, we can no longer have mixed directory strings for Unix and MS format, so some refactoring of `MockSession` and changing of some dependent tests was necessary.
File size: 7.5 KB
Line 
1# Copyright (C) 2003-2009, Stefan Schwarzer <sschwarzer@sschwarzer.net>
2# See the file LICENSE for licensing terms.
3
4"""
5This module implements a mock version of the standard library's
6`ftplib.py` module. Some code is taken from there.
7
8Not all functionality is implemented, only that what is used to
9run the unit tests.
10"""
11
12import ftplib
13import posixpath
14import StringIO
15
16DEBUG = 0
17
18# Use a global dictionary of the form `{path: mock_file, ...}` to
19# make "volatile" mock files accessible. This is used for testing
20# the contents of a file after an `FTPHost.upload` call.
21mock_files = {}
22
23def content_of(path):
24    return mock_files[path].getvalue()
25
26
27class MockFile(StringIO.StringIO, object):
28    """
29    Mock class for the file objects _contained in_ `_FTPFile` objects
30    (not `_FTPFile` objects themselves!).
31
32    Contrary to `StringIO.StringIO` instances, `MockFile` objects can
33    be queried for their contents after they have been closed.
34    """
35    def __init__(self, path, content=''):
36        global mock_files
37        mock_files[path] = self
38        super(MockFile, self).__init__(content)
39
40    def getvalue(self):
41        if not self.closed:
42            return super(MockFile, self).getvalue()
43        else:
44            return self._value_after_close
45
46    def close(self):
47        if not self.closed:
48            self._value_after_close = super(MockFile, self).getvalue()
49        super(MockFile, self).close()
50
51
52class MockSocket(object):
53    """
54    Mock class which is used to return something from
55    `MockSession.transfercmd`.
56    """
57    def __init__(self, path, mock_file_content=''):
58        if DEBUG:
59            print 'File content: *%s*' % mock_file_content
60        self.file_path = path
61        self.mock_file_content = mock_file_content
62        self._timeout = 60
63
64    def makefile(self, mode):
65        return MockFile(self.file_path, self.mock_file_content)
66
67    def close(self):
68        pass
69
70    def gettimeout(self):
71        return self._timeout
72
73    def settimeout(self, timeout):
74        self._timeout = timeout
75
76
77class MockSession(object):
78    """
79    Mock class which works like `ftplib.FTP` for the purpose of the
80    unit tests.
81    """
82    # Used by `MockSession.cwd` and `MockSession.pwd`
83    current_dir = '/home/sschwarzer'
84
85    # Used by `MockSession.dir`
86    dir_contents = {
87      '/': """\
88drwxr-xr-x   2 45854    200           512 May  4  2000 home""",
89
90      '/home': """\
91drwxr-sr-x   2 45854    200           512 May  4  2000 sschwarzer
92-rw-r--r--   1 45854    200          4605 Jan 19  1970 older
93-rw-r--r--   1 45854    200          4605 Jan 19  2020 newer
94lrwxrwxrwx   1 45854    200            21 Jan 19  2002 link -> sschwarzer/index.html
95lrwxrwxrwx   1 45854    200            15 Jan 19  2002 bad_link -> python/bad_link
96drwxr-sr-x   2 45854    200           512 May  4  2000 dir with spaces""",
97
98      '/home/python': """\
99lrwxrwxrwx   1 45854    200             7 Jan 19  2002 link_link -> ../link
100lrwxrwxrwx   1 45854    200            14 Jan 19  2002 bad_link -> /home/bad_link""",
101
102      '/home/sschwarzer': """\
103total 14
104drwxr-sr-x   2 45854    200           512 May  4  2000 chemeng
105drwxr-sr-x   2 45854    200           512 Jan  3 17:17 download
106drwxr-sr-x   2 45854    200           512 Jul 30 17:14 image
107-rw-r--r--   1 45854    200          4604 Jan 19 23:11 index.html
108drwxr-sr-x   2 45854    200           512 May 29  2000 os2
109lrwxrwxrwx   2 45854    200             6 May 29  2000 osup -> ../os2
110drwxr-sr-x   2 45854    200           512 May 25  2000 publications
111drwxr-sr-x   2 45854    200           512 Jan 20 16:12 python
112drwxr-sr-x   6 45854    200           512 Sep 20  1999 scios2""",
113
114      '/home/dir with spaces': """\
115total 1
116-rw-r--r--   1 45854    200          4604 Jan 19 23:11 file with spaces""",
117
118      # Fail when trying to write to this directory (the content isn't
119      # relevant).
120      'sschwarzer': "",
121    }
122
123    # File content to be used (indirectly) with `transfercmd`.
124    mock_file_content = ''
125
126    def __init__(self, host='', user='', password=''):
127        self.closed = 0
128        # Count successful `transfercmd` invocations to ensure that
129        # each has a corresponding `voidresp`.
130        self._transfercmds = 0
131        # Dummy, only for getting/setting timeout in `_FTPFile.close`
132        self.sock = MockSocket("", "")
133
134    def voidcmd(self, cmd):
135        if DEBUG:
136            print cmd
137        if cmd == 'STAT':
138            return 'MockSession server awaiting your commands ;-)'
139        elif cmd.startswith('TYPE '):
140            return
141        elif cmd.startswith('SITE CHMOD'):
142            raise ftplib.error_perm("502 command not implemented")
143        else:
144            raise ftplib.error_perm
145
146    def pwd(self):
147        return self.current_dir
148
149    def _remove_trailing_slash(self, path):
150        if path != '/' and path.endswith('/'):
151            path = path[:-1]
152        return path
153
154    def _transform_path(self, path):
155        return posixpath.normpath(posixpath.join(self.pwd(), path))
156
157    def cwd(self, path):
158        self.current_dir = self._transform_path(path)
159
160    def dir(self, *args):
161        """Provide a callback function for processing each line of
162        a directory listing. Return nothing.
163        """
164        if callable(args[-1]):
165            callback = args[-1]
166            args = args[:-1]
167        else:
168            callback = None
169        # Everything before the path argument are options.
170        path = args[-1]
171        if DEBUG:
172            print 'dir: %s' % path
173        path = self._transform_path(path)
174        if not self.dir_contents.has_key(path):
175            raise ftplib.error_perm
176        dir_lines = self.dir_contents[path].split('\n')
177        for line in dir_lines:
178            if callback is None:
179                print line
180            else:
181                callback(line)
182
183    def voidresp(self):
184        assert self._transfercmds == 1
185        self._transfercmds = self._transfercmds - 1
186        return '2xx'
187
188    def transfercmd(self, cmd):
189        """
190        Return a `MockSocket` object whose `makefile` method will
191        return a mock file object.
192        """
193        if DEBUG:
194            print cmd
195        # Fail if attempting to read from/write to a directory
196        cmd, path = cmd.split()
197        path = self._remove_trailing_slash(path)
198        if self.dir_contents.has_key(path):
199            raise ftplib.error_perm
200        # Fail if path isn't available (this name is hard-coded here
201        # and has to be used for the corresponding tests).
202        if (cmd, path) == ('RETR', 'notthere'):
203            raise ftplib.error_perm
204        assert self._transfercmds == 0
205        self._transfercmds = self._transfercmds + 1
206        return MockSocket(path, self.mock_file_content)
207
208    def close(self):
209        if not self.closed:
210            self.closed = 1
211            assert self._transfercmds == 0
212
213
214class MockMSFormatSession(MockSession):
215
216    dir_contents = {
217      '/': """\
21810-23-01  03:25PM       <DIR>          home""",
219
220      '/home': """\
22110-23-01  03:25PM       <DIR>          msformat""",
222
223      '/home/msformat': """\
22410-23-01  03:25PM       <DIR>          WindowsXP
22512-07-01  02:05PM       <DIR>          XPLaunch
22607-17-00  02:08PM             12266720 abcd.exe
22707-17-00  02:08PM                89264 O2KKeys.exe""",
228
229      '/home/msformat/XPLaunch': """\
23010-23-01  03:25PM       <DIR>          WindowsXP
23112-07-01  02:05PM       <DIR>          XPLaunch
23212-07-01  02:05PM       <DIR>          empty
23307-17-00  02:08PM             12266720 abcd.exe
23407-17-00  02:08PM                89264 O2KKeys.exe""",
235
236      '/home/msformat/XPLaunch/empty': "total 0",
237    }
Note: See TracBrowser for help on using the repository browser.