root/trunk/_mock_ftplib.py

Revision 740, 8.0 kB (checked in by schwa, 3 weeks ago)
Added a new exception `CommandNotImplementedError`, derived from
`PermanentError`, to indicate an unsupported command. This spares
the user to look at the status code which I consider rather
low-level.

Updated tests and documentation accordingly.

Beware: The name of the exception may still change until the next
release.
  • Property svn:mime-type set to text/x-python
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1 # Copyright (C) 2003-2008, 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 # $Id$
33
34 """
35 This module implements a mock version of the standard library's
36 `ftplib.py` module. Some code is taken from there.
37
38 Not all functionality is implemented, only that what is used to
39 run the unit tests.
40 """
41
42 import ftplib
43 import StringIO
44
45 DEBUG = 0
46
47 # Use a global dictionary of the form `{path: mock_file, ...}` to
48 #  make "volatile" mock files accessible. This is used for testing
49 #  the contents of a file after an `FTPHost.upload` call.
50 mock_files = {}
51
52 def content_of(path):
53     return mock_files[path].getvalue()
54
55
56 class MockFile(StringIO.StringIO):
57     """
58     Mock class for the file objects _contained in_ `_FTPFile` objects
59     (not `_FTPFile` objects themselves!).
60
61     Unless `StringIO.StringIO` instances, `MockFile` objects can be
62     queried for their contents after they have been closed.
63     """
64     def __init__(self, path, content=''):
65         global mock_files
66         mock_files[path] = self
67         StringIO.StringIO.__init__(self, content)
68
69     def getvalue(self):
70         if not self.closed:
71             return StringIO.StringIO.getvalue(self)
72         else:
73             return self._value_after_close
74
75     def close(self):
76         if not self.closed:
77             self._value_after_close = StringIO.StringIO.getvalue(self)
78         StringIO.StringIO.close(self)
79
80
81 class MockSocket(object):
82     """
83     Mock class which is used to return something from
84     `MockSession.transfercmd`.
85     """
86     def __init__(self, path, mock_file_content=''):
87         if DEBUG:
88             print 'File content: *%s*' % mock_file_content
89         self.file_path = path
90         self.mock_file_content = mock_file_content
91
92     def makefile(self, mode):
93         return MockFile(self.file_path, self.mock_file_content)
94
95     def close(self):
96         pass
97
98
99 class MockSession(object):
100     """
101     Mock class which works like `ftplib.FTP` for the purpose of the
102     unit tests.
103     """
104     # used by MockSession.cwd and MockSession.pwd
105     current_dir = '/home/sschwarzer'
106
107     # used by MockSession.dir
108     dir_contents = {
109       '/': """\
110 drwxr-xr-x   2 45854    200           512 May  4  2000 home""",
111
112       '/home': """\
113 drwxr-sr-x   2 45854    200           512 May  4  2000 sschwarzer
114 -rw-r--r--   1 45854    200          4605 Jan 19  1970 older
115 -rw-r--r--   1 45854    200          4605 Jan 19  2020 newer
116 lrwxrwxrwx   1 45854    200            21 Jan 19  2002 link -> sschwarzer/index.html
117 lrwxrwxrwx   1 45854    200            15 Jan 19  2002 bad_link -> python/bad_link""",
118
119       '/home/python': """\
120 lrwxrwxrwx   1 45854    200             7 Jan 19  2002 link_link -> ../link
121 lrwxrwxrwx   1 45854    200            14 Jan 19  2002 bad_link -> /home/bad_link""",
122
123       '/home/sschwarzer': """\
124 total 14
125 drwxr-sr-x   2 45854    200           512 May  4  2000 chemeng
126 drwxr-sr-x   2 45854    200           512 Jan  3 17:17 download
127 drwxr-sr-x   2 45854    200           512 Jul 30 17:14 image
128 -rw-r--r--   1 45854    200          4604 Jan 19 23:11 index.html
129 drwxr-sr-x   2 45854    200           512 May 29  2000 os2
130 lrwxrwxrwx   2 45854    200             6 May 29  2000 osup -> ../os2
131 drwxr-sr-x   2 45854    200           512 May 25  2000 publications
132 drwxr-sr-x   2 45854    200           512 Jan 20 16:12 python
133 drwxr-sr-x   6 45854    200           512 Sep 20  1999 scios2""",
134
135       # = /home/dir with spaces
136       '.': """\
137 total 1
138 -rw-r--r--   1 45854    200          4604 Jan 19 23:11 file with spaces""",
139
140       # fail when trying to write to this directory (the content isn't
141       #  relevant)
142       'sschwarzer': "",
143
144       '/home/msformat': """\
145 10-23-01  03:25PM       <DIR>          WindowsXP
146 12-07-01  02:05PM       <DIR>          XPLaunch
147 07-17-00  02:08PM             12266720 abcd.exe
148 07-17-00  02:08PM                89264 O2KKeys.exe""",
149
150       '/home/msformat/XPLaunch': """\
151 10-23-01  03:25PM       <DIR>          WindowsXP
152 12-07-01  02:05PM       <DIR>          XPLaunch
153 12-07-01  02:05PM       <DIR>          empty
154 07-17-00  02:08PM             12266720 abcd.exe
155 07-17-00  02:08PM                89264 O2KKeys.exe""",
156
157       '/home/msformat/XPLaunch/empty': "total 0",
158     }
159
160     # file content to be used (indirectly) with transfercmd
161     mock_file_content = ''
162
163     def __init__(self, host='', user='', password=''):
164         self.closed = 0
165         # count successful `transfercmd` invocations to ensure that
166         #  each has a corresponding `voidresp`
167         self._transfercmds = 0
168
169     def _remove_trailing_slash(self, path):
170         if path != '/' and path.endswith('/'):
171             path = path[:-1]
172         return path
173
174     def voidcmd(self, cmd):
175         if DEBUG:
176             print cmd
177         if cmd == 'STAT':
178             return 'MockSession server awaiting your commands ;-)'
179         elif cmd.startswith('TYPE '):
180             return
181         elif cmd.startswith('SITE CHMOD'):
182             raise ftplib.error_perm("502 command not implemented")
183         else:
184             raise ftplib.error_perm
185
186     def pwd(self):
187         return self.current_dir
188
189     def cwd(self, path):
190         path = self._remove_trailing_slash(path)
191         self.current_dir = path
192
193     def dir(self, path, callback=None):
194         "Provide a callback function with each line of a directory listing."
195         if DEBUG:
196             print 'dir: %s' % path
197         path = self._remove_trailing_slash(path)
198         if not self.dir_contents.has_key(path):
199             raise ftplib.error_perm
200         dir_lines = self.dir_contents[path].split('\n')
201         for line in dir_lines:
202             if callback is None:
203                 print line
204             else:
205                 callback(line)
206
207     def voidresp(self):
208         assert self._transfercmds == 1
209         self._transfercmds = self._transfercmds - 1
210         return '2xx'
211
212     def transfercmd(self, cmd):
213         """
214         Return a `MockSocket` object whose `makefile` method will
215         return a mock file object.
216         """
217         if DEBUG:
218             print cmd
219         # fail if attempting to read from/write to a directory
220         cmd, path = cmd.split()
221         path = self._remove_trailing_slash(path)
222         if self.dir_contents.has_key(path):
223             raise ftplib.error_perm
224         # fail if path isn't available (this name is hard-coded here
225         #  and has to be used for the corresponding tests)
226         if (cmd, path) == ('RETR', 'notthere'):
227             raise ftplib.error_perm
228         assert self._transfercmds == 0
229         self._transfercmds = self._transfercmds + 1
230         return MockSocket(path, self.mock_file_content)
231
232     def close(self):
233         if not self.closed:
234             self.closed = 1
235             assert self._transfercmds == 0
236
Note: See TracBrowser for help on using the browser.