source: test/mock_ftplib.py @ 1224:619a05c768be

Last change on this file since 1224:619a05c768be was 1224:619a05c768be, checked in by Stefan Schwarzer <sschwarzer@…>, 7 years ago
Make `from __future__ import unicode_literals` work for all modules. In some cases this required some changes which will also benefit Python 3 compatibility.
File size: 7.8 KB
Line 
1# Copyright (C) 2003-2013, 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
12from __future__ import unicode_literals
13
14import io
15import collections
16import ftplib
17import posixpath
18
19DEBUG = 0
20
21# Use a global dictionary of the form `{path: mock_file, ...}` to
22# make "volatile" mock files accessible. This is used for testing
23# the contents of a file after an `FTPHost.upload` call.
24mock_files = {}
25
26def content_of(path):
27    return mock_files[path].getvalue()
28
29
30class MockFile(io.BytesIO, object):
31    """
32    Mock class for the file objects _contained in_ `_FTPFile` objects
33    (not `_FTPFile` objects themselves!).
34
35    Contrary to `StringIO.StringIO` instances, `MockFile` objects can
36    be queried for their contents after they have been closed.
37    """
38    def __init__(self, path, content=b""):
39        global mock_files
40        mock_files[path] = self
41        super(MockFile, self).__init__(content)
42
43    def getvalue(self):
44        if not self.closed:
45            return super(MockFile, self).getvalue()
46        else:
47            return self._value_after_close
48
49    def close(self):
50        if not self.closed:
51            self._value_after_close = super(MockFile, self).getvalue()
52        super(MockFile, self).close()
53
54
55class MockSocket(object):
56    """
57    Mock class which is used to return something from
58    `MockSession.transfercmd`.
59    """
60    def __init__(self, path, mock_file_content=b""):
61        if DEBUG:
62            print("File content: *{0}*".format(mock_file_content))
63        self.file_path = path
64        self.mock_file_content = mock_file_content
65        self._timeout = 60
66
67    def makefile(self, mode):
68        return MockFile(self.file_path, self.mock_file_content)
69
70    def close(self):
71        pass
72
73    def gettimeout(self):
74        return self._timeout
75
76    def settimeout(self, timeout):
77        self._timeout = timeout
78
79
80class MockSession(object):
81    """
82    Mock class which works like `ftplib.FTP` for the purpose of the
83    unit tests.
84    """
85    # Used by `MockSession.cwd` and `MockSession.pwd`
86    current_dir = '/home/sschwarzer'
87
88    # Used by `MockSession.dir`. This is a mapping from absolute path
89    # to the multi-line string that would show up in an FTP
90    # command-line client for this directory.
91    dir_contents = {}
92
93    # File content to be used (indirectly) with `transfercmd`.
94    mock_file_content = b''
95
96    def __init__(self, host='', user='', password=''):
97        self.closed = 0
98        # Count successful `transfercmd` invocations to ensure that
99        # each has a corresponding `voidresp`.
100        self._transfercmds = 0
101        # Dummy, only for getting/setting timeout in `_FTPFile.close`
102        self.sock = MockSocket("", "")
103
104    def voidcmd(self, cmd):
105        if DEBUG:
106            print(cmd)
107        if cmd == 'STAT':
108            return 'MockSession server awaiting your commands ;-)'
109        elif cmd.startswith('TYPE '):
110            return
111        elif cmd.startswith('SITE CHMOD'):
112            raise ftplib.error_perm("502 command not implemented")
113        else:
114            raise ftplib.error_perm
115
116    def pwd(self):
117        return self.current_dir
118
119    def _remove_trailing_slash(self, path):
120        if path != '/' and path.endswith('/'):
121            path = path[:-1]
122        return path
123
124    def _transform_path(self, path):
125        return posixpath.normpath(posixpath.join(self.pwd(), path))
126
127    def cwd(self, path):
128        self.current_dir = self._transform_path(path)
129
130    def dir(self, *args):
131        """Provide a callback function for processing each line of
132        a directory listing. Return nothing.
133        """
134        if isinstance(args[-1], collections.Callable):
135            callback = args[-1]
136            args = args[:-1]
137        else:
138            callback = None
139        # Everything before the path argument are options.
140        path = args[-1]
141        if DEBUG:
142            print("dir: {0}".format(path))
143        path = self._transform_path(path)
144        if path not in self.dir_contents:
145            raise ftplib.error_perm
146        dir_lines = self.dir_contents[path].split('\n')
147        for line in dir_lines:
148            if callback is None:
149                print(line)
150            else:
151                callback(line)
152
153    def voidresp(self):
154        assert self._transfercmds == 1
155        self._transfercmds = self._transfercmds - 1
156        return '2xx'
157
158    def transfercmd(self, cmd):
159        """
160        Return a `MockSocket` object whose `makefile` method will
161        return a mock file object.
162        """
163        if DEBUG:
164            print(cmd)
165        # Fail if attempting to read from/write to a directory
166        cmd, path = cmd.split()
167        path = self._remove_trailing_slash(path)
168        if path in self.dir_contents:
169            raise ftplib.error_perm
170        # Fail if path isn't available (this name is hard-coded here
171        # and has to be used for the corresponding tests).
172        if (cmd, path) == ('RETR', 'notthere'):
173            raise ftplib.error_perm
174        assert self._transfercmds == 0
175        self._transfercmds = self._transfercmds + 1
176        return MockSocket(path, self.mock_file_content)
177
178    def close(self):
179        if not self.closed:
180            self.closed = 1
181            assert self._transfercmds == 0
182
183
184class MockUnixFormatSession(MockSession):
185
186    dir_contents = {
187      '/': """\
188drwxr-xr-x   2 45854    200           512 May  4  2000 home""",
189
190      '/home': """\
191drwxr-sr-x   2 45854    200           512 May  4  2000 sschwarzer
192-rw-r--r--   1 45854    200          4605 Jan 19  1970 older
193-rw-r--r--   1 45854    200          4605 Jan 19  2020 newer
194lrwxrwxrwx   1 45854    200            21 Jan 19  2002 link -> sschwarzer/index.html
195lrwxrwxrwx   1 45854    200            15 Jan 19  2002 bad_link -> python/bad_link
196drwxr-sr-x   2 45854    200           512 May  4  2000 dir with spaces""",
197
198      '/home/python': """\
199lrwxrwxrwx   1 45854    200             7 Jan 19  2002 link_link -> ../link
200lrwxrwxrwx   1 45854    200            14 Jan 19  2002 bad_link -> /home/bad_link""",
201
202      '/home/sschwarzer': """\
203total 14
204drwxr-sr-x   2 45854    200           512 May  4  2000 chemeng
205drwxr-sr-x   2 45854    200           512 Jan  3 17:17 download
206drwxr-sr-x   2 45854    200           512 Jul 30 17:14 image
207-rw-r--r--   1 45854    200          4604 Jan 19 23:11 index.html
208drwxr-sr-x   2 45854    200           512 May 29  2000 os2
209lrwxrwxrwx   2 45854    200             6 May 29  2000 osup -> ../os2
210drwxr-sr-x   2 45854    200           512 May 25  2000 publications
211drwxr-sr-x   2 45854    200           512 Jan 20 16:12 python
212drwxr-sr-x   6 45854    200           512 Sep 20  1999 scios2""",
213
214      '/home/dir with spaces': """\
215total 1
216-rw-r--r--   1 45854    200          4604 Jan 19 23:11 file with spaces""",
217
218      # Fail when trying to write to this directory (the content isn't
219      # relevant).
220      'sschwarzer': "",
221    }
222
223
224class MockMSFormatSession(MockSession):
225
226    dir_contents = {
227      '/': """\
22810-23-01  03:25PM       <DIR>          home""",
229
230      '/home': """\
23110-23-01  03:25PM       <DIR>          msformat""",
232
233      '/home/msformat': """\
23410-23-01  03:25PM       <DIR>          WindowsXP
23512-07-01  02:05PM       <DIR>          XPLaunch
23607-17-00  02:08PM             12266720 abcd.exe
23707-17-00  02:08PM                89264 O2KKeys.exe""",
238
239      '/home/msformat/XPLaunch': """\
24010-23-01  03:25PM       <DIR>          WindowsXP
24112-07-01  02:05PM       <DIR>          XPLaunch
24212-07-01  02:05PM       <DIR>          empty
24307-17-00  02:08PM             12266720 abcd.exe
24407-17-00  02:08PM                89264 O2KKeys.exe""",
245
246      '/home/msformat/XPLaunch/empty': "total 0",
247    }
Note: See TracBrowser for help on using the repository browser.