Merge lp:~statik/desktopcouch/dc-load-design-docs into lp:desktopcouch
- dc-load-design-docs
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Elliot Murphy |
Proposed branch: | lp:~statik/desktopcouch/dc-load-design-docs |
Merge into: | lp:desktopcouch |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~statik/desktopcouch/dc-load-design-docs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn (community) | Needs Fixing | ||
Review via email: mp+9951@code.launchpad.net |
Commit message
Description of the change
Elliot Murphy (statik) wrote : | # |
Eric Casteleijn (thisfred) wrote : | # |
I get these errors:
=======
[ERROR]: desktopcouch.
Traceback (most recent call last):
Failure: twisted.
DelayedCalls: (set twisted.
<DelayedCall 168000268 [0.995854854584s] called=0 cancelled=0 LoopingCall<
<DelayedCall 168000044 [1.56556606293s] called=0 cancelled=0 reconnector()>
<DelayedCall 167997580 [28.9958992004s] called=0 cancelled=0 exit_on_timeout()>
=======
[ERROR]: desktopcouch.
Traceback (most recent call last):
File "/usr/lib/
self.setUp()
File "/home/
self.database = CouchDatabase(
File "/home/
if database not in self._server:
File "/usr/lib/
self.
File "/usr/lib/
return self._request(
File "/usr/lib/
resp, data = _make_request()
File "/usr/lib/
body=body, headers=headers)
File "/usr/lib/
(response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
File "/usr/lib/
(response, content) = self._conn_
File "/usr/lib/
response = conn.getresponse()
File "/usr/lib/
response.
File "/usr/lib/
version, status, reason = self._read_status()
File "/usr/lib/
line = self.fp.readline()
File "/usr/lib/
data = recv(1)
socket.error: [Errno 4] Interrupted system call
=======
[ERROR]: desktopcouch.
Traceback (most recent call last):
File "/home/
mocker.verify()
File "/home/
Unmerged revisions
- 35. By Elliot Murphy
-
include contrib/*.py in the generated release tarball.
- 34. By Elliot Murphy
-
Merged from trunk.
Preview Diff
1 | === modified file 'MANIFEST.in' | |||
2 | --- MANIFEST.in 2009-08-05 01:05:01 +0000 | |||
3 | +++ MANIFEST.in 2009-08-10 21:21:51 +0000 | |||
4 | @@ -1,5 +1,6 @@ | |||
5 | 1 | include COPYING COPYING.LESSER README | 1 | include COPYING COPYING.LESSER README |
6 | 2 | recursive-include data *.tmpl | 2 | recursive-include data *.tmpl |
7 | 3 | recursive-include contrib *.py | ||
8 | 3 | include desktopcouch-pair.desktop.in | 4 | include desktopcouch-pair.desktop.in |
9 | 4 | include po/desktopcouch.pot | 5 | include po/desktopcouch.pot |
10 | 5 | include start-desktop-couchdb.sh | 6 | include start-desktop-couchdb.sh |
11 | 6 | 7 | ||
12 | === modified file 'README' | |||
13 | --- README 2009-07-07 15:15:36 +0000 | |||
14 | +++ README 2009-08-07 12:43:11 +0000 | |||
15 | @@ -1,1 +1,35 @@ | |||
17 | 1 | This is desktopcouch. | 1 | This is Desktop Couch, an infrastructure to place a CouchDB on every desktop |
18 | 2 | and provide APIs and management tools for applications to store data within | ||
19 | 3 | it and share that data between computers and to the cloud. | ||
20 | 4 | |||
21 | 5 | = Technical notes = | ||
22 | 6 | |||
23 | 7 | == Creating databases, design documents, and views == | ||
24 | 8 | |||
25 | 9 | Desktop Couch will automatically create databases and design documents for you | ||
26 | 10 | on startup by inspecting the filesystem, meaning that you do not need to check | ||
27 | 11 | in your application whether your views exist. It is of course possible to create | ||
28 | 12 | views directly through the Records API, but having them created via the | ||
29 | 13 | filesystem allows managing of the view definitions more easily (they are | ||
30 | 14 | in separately editable files, which helps with version control) and packaging | ||
31 | 15 | (the files can be separately stored in a distribution package and installed | ||
32 | 16 | into the system-level XDG_DATA_DIR rather than the user-level folder). | ||
33 | 17 | |||
34 | 18 | === To create a database === | ||
35 | 19 | |||
36 | 20 | Create a file $XDG_DATA_DIR/desktop-couch/databases/YOUR_DB_NAME/database.cfg | ||
37 | 21 | |||
38 | 22 | This file can currently be empty (in future it may contain database setup and | ||
39 | 23 | configuration information). | ||
40 | 24 | |||
41 | 25 | === To create a design document === | ||
42 | 26 | |||
43 | 27 | Create a file | ||
44 | 28 | $XDG_DATA_DIR/desktop-couch/databases/YOUR_DB_NAME/_design/DESIGN_DOC_NAME/views/VIEW_NAME/map.js | ||
45 | 29 | containing the map function from your view. | ||
46 | 30 | If you also require a reduce function for your view, create a file | ||
47 | 31 | $XDG_DATA_DIR/desktop-couch/databases/YOUR_DB_NAME/_design/DESIGN_DOC_NAME/views/VIEW_NAME/reduce.js. | ||
48 | 32 | |||
49 | 33 | This is compatible with the filesystem view structure from CouchApp and | ||
50 | 34 | CouchDBKit. | ||
51 | 35 | |||
52 | 2 | 36 | ||
53 | === added directory 'contrib' | |||
54 | === added file 'contrib/mocker.py' | |||
55 | --- contrib/mocker.py 1970-01-01 00:00:00 +0000 | |||
56 | +++ contrib/mocker.py 2009-08-06 13:57:30 +0000 | |||
57 | @@ -0,0 +1,2068 @@ | |||
58 | 1 | """ | ||
59 | 2 | Copyright (c) 2007 Gustavo Niemeyer <gustavo@niemeyer.net> | ||
60 | 3 | |||
61 | 4 | Graceful platform for test doubles in Python (mocks, stubs, fakes, and dummies). | ||
62 | 5 | """ | ||
63 | 6 | import __builtin__ | ||
64 | 7 | import tempfile | ||
65 | 8 | import unittest | ||
66 | 9 | import inspect | ||
67 | 10 | import shutil | ||
68 | 11 | import types | ||
69 | 12 | import sys | ||
70 | 13 | import os | ||
71 | 14 | import gc | ||
72 | 15 | |||
73 | 16 | |||
74 | 17 | if sys.version_info < (2, 4): | ||
75 | 18 | from sets import Set as set # pragma: nocover | ||
76 | 19 | |||
77 | 20 | |||
78 | 21 | __all__ = ["Mocker", "expect", "IS", "CONTAINS", "IN", "MATCH", | ||
79 | 22 | "ANY", "ARGS", "KWARGS"] | ||
80 | 23 | |||
81 | 24 | |||
82 | 25 | __author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>" | ||
83 | 26 | __license__ = "PSF License" | ||
84 | 27 | __version__ = "0.10.1" | ||
85 | 28 | |||
86 | 29 | |||
87 | 30 | ERROR_PREFIX = "[Mocker] " | ||
88 | 31 | |||
89 | 32 | |||
90 | 33 | # -------------------------------------------------------------------- | ||
91 | 34 | # Exceptions | ||
92 | 35 | |||
93 | 36 | class MatchError(AssertionError): | ||
94 | 37 | """Raised when an unknown expression is seen in playback mode.""" | ||
95 | 38 | |||
96 | 39 | |||
97 | 40 | # -------------------------------------------------------------------- | ||
98 | 41 | # Helper for chained-style calling. | ||
99 | 42 | |||
100 | 43 | class expect(object): | ||
101 | 44 | """This is a simple helper that allows a different call-style. | ||
102 | 45 | |||
103 | 46 | With this class one can comfortably do chaining of calls to the | ||
104 | 47 | mocker object responsible by the object being handled. For instance:: | ||
105 | 48 | |||
106 | 49 | expect(obj.attr).result(3).count(1, 2) | ||
107 | 50 | |||
108 | 51 | Is the same as:: | ||
109 | 52 | |||
110 | 53 | obj.attr | ||
111 | 54 | mocker.result(3) | ||
112 | 55 | mocker.count(1, 2) | ||
113 | 56 | |||
114 | 57 | """ | ||
115 | 58 | |||
116 | 59 | def __init__(self, mock, attr=None): | ||
117 | 60 | self._mock = mock | ||
118 | 61 | self._attr = attr | ||
119 | 62 | |||
120 | 63 | def __getattr__(self, attr): | ||
121 | 64 | return self.__class__(self._mock, attr) | ||
122 | 65 | |||
123 | 66 | def __call__(self, *args, **kwargs): | ||
124 | 67 | getattr(self._mock.__mocker__, self._attr)(*args, **kwargs) | ||
125 | 68 | return self | ||
126 | 69 | |||
127 | 70 | |||
128 | 71 | # -------------------------------------------------------------------- | ||
129 | 72 | # Extensions to Python's unittest. | ||
130 | 73 | |||
131 | 74 | class MockerTestCase(unittest.TestCase): | ||
132 | 75 | """unittest.TestCase subclass with Mocker support. | ||
133 | 76 | |||
134 | 77 | @ivar mocker: The mocker instance. | ||
135 | 78 | |||
136 | 79 | This is a convenience only. Mocker may easily be used with the | ||
137 | 80 | standard C{unittest.TestCase} class if wanted. | ||
138 | 81 | |||
139 | 82 | Test methods have a Mocker instance available on C{self.mocker}. | ||
140 | 83 | At the end of each test method, expectations of the mocker will | ||
141 | 84 | be verified, and any requested changes made to the environment | ||
142 | 85 | will be restored. | ||
143 | 86 | |||
144 | 87 | In addition to the integration with Mocker, this class provides | ||
145 | 88 | a few additional helper methods. | ||
146 | 89 | """ | ||
147 | 90 | |||
148 | 91 | expect = expect | ||
149 | 92 | |||
150 | 93 | def __init__(self, methodName="runTest"): | ||
151 | 94 | # So here is the trick: we take the real test method, wrap it on | ||
152 | 95 | # a function that do the job we have to do, and insert it in the | ||
153 | 96 | # *instance* dictionary, so that getattr() will return our | ||
154 | 97 | # replacement rather than the class method. | ||
155 | 98 | test_method = getattr(self, methodName, None) | ||
156 | 99 | if test_method is not None: | ||
157 | 100 | def test_method_wrapper(): | ||
158 | 101 | try: | ||
159 | 102 | result = test_method() | ||
160 | 103 | except: | ||
161 | 104 | raise | ||
162 | 105 | else: | ||
163 | 106 | if (self.mocker.is_recording() and | ||
164 | 107 | self.mocker.get_events()): | ||
165 | 108 | raise RuntimeError("Mocker must be put in replay " | ||
166 | 109 | "mode with self.mocker.replay()") | ||
167 | 110 | if (hasattr(result, "addCallback") and | ||
168 | 111 | hasattr(result, "addErrback")): | ||
169 | 112 | def verify(result): | ||
170 | 113 | self.mocker.verify() | ||
171 | 114 | return result | ||
172 | 115 | result.addCallback(verify) | ||
173 | 116 | else: | ||
174 | 117 | self.mocker.verify() | ||
175 | 118 | return result | ||
176 | 119 | # Copy all attributes from the original method.. | ||
177 | 120 | for attr in dir(test_method): | ||
178 | 121 | # .. unless they're present in our wrapper already. | ||
179 | 122 | if not hasattr(test_method_wrapper, attr) or attr == "__doc__": | ||
180 | 123 | setattr(test_method_wrapper, attr, | ||
181 | 124 | getattr(test_method, attr)) | ||
182 | 125 | setattr(self, methodName, test_method_wrapper) | ||
183 | 126 | |||
184 | 127 | # We could overload run() normally, but other well-known testing | ||
185 | 128 | # frameworks do it as well, and some of them won't call the super, | ||
186 | 129 | # which might mean that cleanup wouldn't happen. With that in mind, | ||
187 | 130 | # we make integration easier by using the following trick. | ||
188 | 131 | run_method = self.run | ||
189 | 132 | def run_wrapper(*args, **kwargs): | ||
190 | 133 | try: | ||
191 | 134 | return run_method(*args, **kwargs) | ||
192 | 135 | finally: | ||
193 | 136 | self.__cleanup() | ||
194 | 137 | self.run = run_wrapper | ||
195 | 138 | |||
196 | 139 | self.mocker = Mocker() | ||
197 | 140 | |||
198 | 141 | self.__cleanup_funcs = [] | ||
199 | 142 | self.__cleanup_paths = [] | ||
200 | 143 | |||
201 | 144 | super(MockerTestCase, self).__init__(methodName) | ||
202 | 145 | |||
203 | 146 | def __cleanup(self): | ||
204 | 147 | for path in self.__cleanup_paths: | ||
205 | 148 | if os.path.isfile(path): | ||
206 | 149 | os.unlink(path) | ||
207 | 150 | elif os.path.isdir(path): | ||
208 | 151 | shutil.rmtree(path) | ||
209 | 152 | self.mocker.restore() | ||
210 | 153 | for func, args, kwargs in self.__cleanup_funcs: | ||
211 | 154 | func(*args, **kwargs) | ||
212 | 155 | |||
213 | 156 | def addCleanup(self, func, *args, **kwargs): | ||
214 | 157 | self.__cleanup_funcs.append((func, args, kwargs)) | ||
215 | 158 | |||
216 | 159 | def makeFile(self, content=None, suffix="", prefix="tmp", basename=None, | ||
217 | 160 | dirname=None, path=None): | ||
218 | 161 | """Create a temporary file and return the path to it. | ||
219 | 162 | |||
220 | 163 | @param content: Initial content for the file. | ||
221 | 164 | @param suffix: Suffix to be given to the file's basename. | ||
222 | 165 | @param prefix: Prefix to be given to the file's basename. | ||
223 | 166 | @param basename: Full basename for the file. | ||
224 | 167 | @param dirname: Put file inside this directory. | ||
225 | 168 | |||
226 | 169 | The file is removed after the test runs. | ||
227 | 170 | """ | ||
228 | 171 | if path is not None: | ||
229 | 172 | self.__cleanup_paths.append(path) | ||
230 | 173 | elif basename is not None: | ||
231 | 174 | if dirname is None: | ||
232 | 175 | dirname = tempfile.mkdtemp() | ||
233 | 176 | self.__cleanup_paths.append(dirname) | ||
234 | 177 | path = os.path.join(dirname, basename) | ||
235 | 178 | else: | ||
236 | 179 | fd, path = tempfile.mkstemp(suffix, prefix, dirname) | ||
237 | 180 | self.__cleanup_paths.append(path) | ||
238 | 181 | os.close(fd) | ||
239 | 182 | if content is None: | ||
240 | 183 | os.unlink(path) | ||
241 | 184 | if content is not None: | ||
242 | 185 | file = open(path, "w") | ||
243 | 186 | file.write(content) | ||
244 | 187 | file.close() | ||
245 | 188 | return path | ||
246 | 189 | |||
247 | 190 | def makeDir(self, suffix="", prefix="tmp", dirname=None, path=None): | ||
248 | 191 | """Create a temporary directory and return the path to it. | ||
249 | 192 | |||
250 | 193 | @param suffix: Suffix to be given to the file's basename. | ||
251 | 194 | @param prefix: Prefix to be given to the file's basename. | ||
252 | 195 | @param dirname: Put directory inside this parent directory. | ||
253 | 196 | |||
254 | 197 | The directory is removed after the test runs. | ||
255 | 198 | """ | ||
256 | 199 | if path is not None: | ||
257 | 200 | os.makedirs(path) | ||
258 | 201 | else: | ||
259 | 202 | path = tempfile.mkdtemp(suffix, prefix, dirname) | ||
260 | 203 | self.__cleanup_paths.append(path) | ||
261 | 204 | return path | ||
262 | 205 | |||
263 | 206 | def failUnlessIs(self, first, second, msg=None): | ||
264 | 207 | """Assert that C{first} is the same object as C{second}.""" | ||
265 | 208 | if first is not second: | ||
266 | 209 | raise self.failureException(msg or "%r is not %r" % (first, second)) | ||
267 | 210 | |||
268 | 211 | def failIfIs(self, first, second, msg=None): | ||
269 | 212 | """Assert that C{first} is not the same object as C{second}.""" | ||
270 | 213 | if first is second: | ||
271 | 214 | raise self.failureException(msg or "%r is %r" % (first, second)) | ||
272 | 215 | |||
273 | 216 | def failUnlessIn(self, first, second, msg=None): | ||
274 | 217 | """Assert that C{first} is contained in C{second}.""" | ||
275 | 218 | if first not in second: | ||
276 | 219 | raise self.failureException(msg or "%r not in %r" % (first, second)) | ||
277 | 220 | |||
278 | 221 | def failUnlessStartsWith(self, first, second, msg=None): | ||
279 | 222 | """Assert that C{first} starts with C{second}.""" | ||
280 | 223 | if first[:len(second)] != second: | ||
281 | 224 | raise self.failureException(msg or "%r doesn't start with %r" % | ||
282 | 225 | (first, second)) | ||
283 | 226 | |||
284 | 227 | def failIfStartsWith(self, first, second, msg=None): | ||
285 | 228 | """Assert that C{first} doesn't start with C{second}.""" | ||
286 | 229 | if first[:len(second)] == second: | ||
287 | 230 | raise self.failureException(msg or "%r starts with %r" % | ||
288 | 231 | (first, second)) | ||
289 | 232 | |||
290 | 233 | def failUnlessEndsWith(self, first, second, msg=None): | ||
291 | 234 | """Assert that C{first} starts with C{second}.""" | ||
292 | 235 | if first[len(first)-len(second):] != second: | ||
293 | 236 | raise self.failureException(msg or "%r doesn't end with %r" % | ||
294 | 237 | (first, second)) | ||
295 | 238 | |||
296 | 239 | def failIfEndsWith(self, first, second, msg=None): | ||
297 | 240 | """Assert that C{first} doesn't start with C{second}.""" | ||
298 | 241 | if first[len(first)-len(second):] == second: | ||
299 | 242 | raise self.failureException(msg or "%r ends with %r" % | ||
300 | 243 | (first, second)) | ||
301 | 244 | |||
302 | 245 | def failIfIn(self, first, second, msg=None): | ||
303 | 246 | """Assert that C{first} is not contained in C{second}.""" | ||
304 | 247 | if first in second: | ||
305 | 248 | raise self.failureException(msg or "%r in %r" % (first, second)) | ||
306 | 249 | |||
307 | 250 | def failUnlessApproximates(self, first, second, tolerance, msg=None): | ||
308 | 251 | """Assert that C{first} is near C{second} by at most C{tolerance}.""" | ||
309 | 252 | if abs(first - second) > tolerance: | ||
310 | 253 | raise self.failureException(msg or "abs(%r - %r) > %r" % | ||
311 | 254 | (first, second, tolerance)) | ||
312 | 255 | |||
313 | 256 | def failIfApproximates(self, first, second, tolerance, msg=None): | ||
314 | 257 | """Assert that C{first} is far from C{second} by at least C{tolerance}. | ||
315 | 258 | """ | ||
316 | 259 | if abs(first - second) <= tolerance: | ||
317 | 260 | raise self.failureException(msg or "abs(%r - %r) <= %r" % | ||
318 | 261 | (first, second, tolerance)) | ||
319 | 262 | |||
320 | 263 | def failUnlessMethodsMatch(self, first, second): | ||
321 | 264 | """Assert that public methods in C{first} are present in C{second}. | ||
322 | 265 | |||
323 | 266 | This method asserts that all public methods found in C{first} are also | ||
324 | 267 | present in C{second} and accept the same arguments. C{first} may | ||
325 | 268 | have its own private methods, though, and may not have all methods | ||
326 | 269 | found in C{second}. Note that if a private method in C{first} matches | ||
327 | 270 | the name of one in C{second}, their specification is still compared. | ||
328 | 271 | |||
329 | 272 | This is useful to verify if a fake or stub class have the same API as | ||
330 | 273 | the real class being simulated. | ||
331 | 274 | """ | ||
332 | 275 | first_methods = dict(inspect.getmembers(first, inspect.ismethod)) | ||
333 | 276 | second_methods = dict(inspect.getmembers(second, inspect.ismethod)) | ||
334 | 277 | for name, first_method in first_methods.items(): | ||
335 | 278 | first_argspec = inspect.getargspec(first_method) | ||
336 | 279 | first_formatted = inspect.formatargspec(*first_argspec) | ||
337 | 280 | |||
338 | 281 | second_method = second_methods.get(name) | ||
339 | 282 | if second_method is None: | ||
340 | 283 | if name[:1] == "_": | ||
341 | 284 | continue # First may have its own private methods. | ||
342 | 285 | raise self.failureException("%s.%s%s not present in %s" % | ||
343 | 286 | (first.__name__, name, first_formatted, second.__name__)) | ||
344 | 287 | |||
345 | 288 | second_argspec = inspect.getargspec(second_method) | ||
346 | 289 | if first_argspec != second_argspec: | ||
347 | 290 | second_formatted = inspect.formatargspec(*second_argspec) | ||
348 | 291 | raise self.failureException("%s.%s%s != %s.%s%s" % | ||
349 | 292 | (first.__name__, name, first_formatted, | ||
350 | 293 | second.__name__, name, second_formatted)) | ||
351 | 294 | |||
352 | 295 | |||
353 | 296 | assertIs = failUnlessIs | ||
354 | 297 | assertIsNot = failIfIs | ||
355 | 298 | assertIn = failUnlessIn | ||
356 | 299 | assertNotIn = failIfIn | ||
357 | 300 | assertStartsWith = failUnlessStartsWith | ||
358 | 301 | assertNotStartsWith = failIfStartsWith | ||
359 | 302 | assertEndsWith = failUnlessEndsWith | ||
360 | 303 | assertNotEndsWith = failIfEndsWith | ||
361 | 304 | assertApproximates = failUnlessApproximates | ||
362 | 305 | assertNotApproximates = failIfApproximates | ||
363 | 306 | assertMethodsMatch = failUnlessMethodsMatch | ||
364 | 307 | |||
365 | 308 | # The following are missing in Python < 2.4. | ||
366 | 309 | assertTrue = unittest.TestCase.failUnless | ||
367 | 310 | assertFalse = unittest.TestCase.failIf | ||
368 | 311 | |||
369 | 312 | # The following is provided for compatibility with Twisted's trial. | ||
370 | 313 | assertIdentical = assertIs | ||
371 | 314 | assertNotIdentical = assertIsNot | ||
372 | 315 | failUnlessIdentical = failUnlessIs | ||
373 | 316 | failIfIdentical = failIfIs | ||
374 | 317 | |||
375 | 318 | |||
376 | 319 | # -------------------------------------------------------------------- | ||
377 | 320 | # Mocker. | ||
378 | 321 | |||
379 | 322 | class classinstancemethod(object): | ||
380 | 323 | |||
381 | 324 | def __init__(self, method): | ||
382 | 325 | self.method = method | ||
383 | 326 | |||
384 | 327 | def __get__(self, obj, cls=None): | ||
385 | 328 | def bound_method(*args, **kwargs): | ||
386 | 329 | return self.method(cls, obj, *args, **kwargs) | ||
387 | 330 | return bound_method | ||
388 | 331 | |||
389 | 332 | |||
390 | 333 | class MockerBase(object): | ||
391 | 334 | """Controller of mock objects. | ||
392 | 335 | |||
393 | 336 | A mocker instance is used to command recording and replay of | ||
394 | 337 | expectations on any number of mock objects. | ||
395 | 338 | |||
396 | 339 | Expectations should be expressed for the mock object while in | ||
397 | 340 | record mode (the initial one) by using the mock object itself, | ||
398 | 341 | and using the mocker (and/or C{expect()} as a helper) to define | ||
399 | 342 | additional behavior for each event. For instance:: | ||
400 | 343 | |||
401 | 344 | mock = mocker.mock() | ||
402 | 345 | mock.hello() | ||
403 | 346 | mocker.result("Hi!") | ||
404 | 347 | mocker.replay() | ||
405 | 348 | assert mock.hello() == "Hi!" | ||
406 | 349 | mock.restore() | ||
407 | 350 | mock.verify() | ||
408 | 351 | |||
409 | 352 | In this short excerpt a mock object is being created, then an | ||
410 | 353 | expectation of a call to the C{hello()} method was recorded, and | ||
411 | 354 | when called the method should return the value C{10}. Then, the | ||
412 | 355 | mocker is put in replay mode, and the expectation is satisfied by | ||
413 | 356 | calling the C{hello()} method, which indeed returns 10. Finally, | ||
414 | 357 | a call to the L{restore()} method is performed to undo any needed | ||
415 | 358 | changes made in the environment, and the L{verify()} method is | ||
416 | 359 | called to ensure that all defined expectations were met. | ||
417 | 360 | |||
418 | 361 | The same logic can be expressed more elegantly using the | ||
419 | 362 | C{with mocker:} statement, as follows:: | ||
420 | 363 | |||
421 | 364 | mock = mocker.mock() | ||
422 | 365 | mock.hello() | ||
423 | 366 | mocker.result("Hi!") | ||
424 | 367 | with mocker: | ||
425 | 368 | assert mock.hello() == "Hi!" | ||
426 | 369 | |||
427 | 370 | Also, the MockerTestCase class, which integrates the mocker on | ||
428 | 371 | a unittest.TestCase subclass, may be used to reduce the overhead | ||
429 | 372 | of controlling the mocker. A test could be written as follows:: | ||
430 | 373 | |||
431 | 374 | class SampleTest(MockerTestCase): | ||
432 | 375 | |||
433 | 376 | def test_hello(self): | ||
434 | 377 | mock = self.mocker.mock() | ||
435 | 378 | mock.hello() | ||
436 | 379 | self.mocker.result("Hi!") | ||
437 | 380 | self.mocker.replay() | ||
438 | 381 | self.assertEquals(mock.hello(), "Hi!") | ||
439 | 382 | """ | ||
440 | 383 | |||
441 | 384 | _recorders = [] | ||
442 | 385 | |||
443 | 386 | # For convenience only. | ||
444 | 387 | on = expect | ||
445 | 388 | |||
446 | 389 | class __metaclass__(type): | ||
447 | 390 | def __init__(self, name, bases, dict): | ||
448 | 391 | # Make independent lists on each subclass, inheriting from parent. | ||
449 | 392 | self._recorders = list(getattr(self, "_recorders", ())) | ||
450 | 393 | |||
451 | 394 | def __init__(self): | ||
452 | 395 | self._recorders = self._recorders[:] | ||
453 | 396 | self._events = [] | ||
454 | 397 | self._recording = True | ||
455 | 398 | self._ordering = False | ||
456 | 399 | self._last_orderer = None | ||
457 | 400 | |||
458 | 401 | def is_recording(self): | ||
459 | 402 | """Return True if in recording mode, False if in replay mode. | ||
460 | 403 | |||
461 | 404 | Recording is the initial state. | ||
462 | 405 | """ | ||
463 | 406 | return self._recording | ||
464 | 407 | |||
465 | 408 | def replay(self): | ||
466 | 409 | """Change to replay mode, where recorded events are reproduced. | ||
467 | 410 | |||
468 | 411 | If already in replay mode, the mocker will be restored, with all | ||
469 | 412 | expectations reset, and then put again in replay mode. | ||
470 | 413 | |||
471 | 414 | An alternative and more comfortable way to replay changes is | ||
472 | 415 | using the 'with' statement, as follows:: | ||
473 | 416 | |||
474 | 417 | mocker = Mocker() | ||
475 | 418 | <record events> | ||
476 | 419 | with mocker: | ||
477 | 420 | <reproduce events> | ||
478 | 421 | |||
479 | 422 | The 'with' statement will automatically put mocker in replay | ||
480 | 423 | mode, and will also verify if all events were correctly reproduced | ||
481 | 424 | at the end (using L{verify()}), and also restore any changes done | ||
482 | 425 | in the environment (with L{restore()}). | ||
483 | 426 | |||
484 | 427 | Also check the MockerTestCase class, which integrates the | ||
485 | 428 | unittest.TestCase class with mocker. | ||
486 | 429 | """ | ||
487 | 430 | if not self._recording: | ||
488 | 431 | for event in self._events: | ||
489 | 432 | event.restore() | ||
490 | 433 | else: | ||
491 | 434 | self._recording = False | ||
492 | 435 | for event in self._events: | ||
493 | 436 | event.replay() | ||
494 | 437 | |||
495 | 438 | def restore(self): | ||
496 | 439 | """Restore changes in the environment, and return to recording mode. | ||
497 | 440 | |||
498 | 441 | This should always be called after the test is complete (succeeding | ||
499 | 442 | or not). There are ways to call this method automatically on | ||
500 | 443 | completion (e.g. using a C{with mocker:} statement, or using the | ||
501 | 444 | L{MockerTestCase} class. | ||
502 | 445 | """ | ||
503 | 446 | if not self._recording: | ||
504 | 447 | self._recording = True | ||
505 | 448 | for event in self._events: | ||
506 | 449 | event.restore() | ||
507 | 450 | |||
508 | 451 | def reset(self): | ||
509 | 452 | """Reset the mocker state. | ||
510 | 453 | |||
511 | 454 | This will restore environment changes, if currently in replay | ||
512 | 455 | mode, and then remove all events previously recorded. | ||
513 | 456 | """ | ||
514 | 457 | if not self._recording: | ||
515 | 458 | self.restore() | ||
516 | 459 | self.unorder() | ||
517 | 460 | del self._events[:] | ||
518 | 461 | |||
519 | 462 | def get_events(self): | ||
520 | 463 | """Return all recorded events.""" | ||
521 | 464 | return self._events[:] | ||
522 | 465 | |||
523 | 466 | def add_event(self, event): | ||
524 | 467 | """Add an event. | ||
525 | 468 | |||
526 | 469 | This method is used internally by the implementation, and | ||
527 | 470 | shouldn't be needed on normal mocker usage. | ||
528 | 471 | """ | ||
529 | 472 | self._events.append(event) | ||
530 | 473 | if self._ordering: | ||
531 | 474 | orderer = event.add_task(Orderer(event.path)) | ||
532 | 475 | if self._last_orderer: | ||
533 | 476 | orderer.add_dependency(self._last_orderer) | ||
534 | 477 | self._last_orderer = orderer | ||
535 | 478 | return event | ||
536 | 479 | |||
537 | 480 | def verify(self): | ||
538 | 481 | """Check if all expectations were met, and raise AssertionError if not. | ||
539 | 482 | |||
540 | 483 | The exception message will include a nice description of which | ||
541 | 484 | expectations were not met, and why. | ||
542 | 485 | """ | ||
543 | 486 | errors = [] | ||
544 | 487 | for event in self._events: | ||
545 | 488 | try: | ||
546 | 489 | event.verify() | ||
547 | 490 | except AssertionError, e: | ||
548 | 491 | error = str(e) | ||
549 | 492 | if not error: | ||
550 | 493 | raise RuntimeError("Empty error message from %r" | ||
551 | 494 | % event) | ||
552 | 495 | errors.append(error) | ||
553 | 496 | if errors: | ||
554 | 497 | message = [ERROR_PREFIX + "Unmet expectations:", ""] | ||
555 | 498 | for error in errors: | ||
556 | 499 | lines = error.splitlines() | ||
557 | 500 | message.append("=> " + lines.pop(0)) | ||
558 | 501 | message.extend([" " + line for line in lines]) | ||
559 | 502 | message.append("") | ||
560 | 503 | raise AssertionError(os.linesep.join(message)) | ||
561 | 504 | |||
562 | 505 | def mock(self, spec_and_type=None, spec=None, type=None, | ||
563 | 506 | name=None, count=True): | ||
564 | 507 | """Return a new mock object. | ||
565 | 508 | |||
566 | 509 | @param spec_and_type: Handy positional argument which sets both | ||
567 | 510 | spec and type. | ||
568 | 511 | @param spec: Method calls will be checked for correctness against | ||
569 | 512 | the given class. | ||
570 | 513 | @param type: If set, the Mock's __class__ attribute will return | ||
571 | 514 | the given type. This will make C{isinstance()} calls | ||
572 | 515 | on the object work. | ||
573 | 516 | @param name: Name for the mock object, used in the representation of | ||
574 | 517 | expressions. The name is rarely needed, as it's usually | ||
575 | 518 | guessed correctly from the variable name used. | ||
576 | 519 | @param count: If set to false, expressions may be executed any number | ||
577 | 520 | of times, unless an expectation is explicitly set using | ||
578 | 521 | the L{count()} method. By default, expressions are | ||
579 | 522 | expected once. | ||
580 | 523 | """ | ||
581 | 524 | if spec_and_type is not None: | ||
582 | 525 | spec = type = spec_and_type | ||
583 | 526 | return Mock(self, spec=spec, type=type, name=name, count=count) | ||
584 | 527 | |||
585 | 528 | def proxy(self, object, spec=True, type=True, name=None, count=True, | ||
586 | 529 | passthrough=True): | ||
587 | 530 | """Return a new mock object which proxies to the given object. | ||
588 | 531 | |||
589 | 532 | Proxies are useful when only part of the behavior of an object | ||
590 | 533 | is to be mocked. Unknown expressions may be passed through to | ||
591 | 534 | the real implementation implicitly (if the C{passthrough} argument | ||
592 | 535 | is True), or explicitly (using the L{passthrough()} method | ||
593 | 536 | on the event). | ||
594 | 537 | |||
595 | 538 | @param object: Real object to be proxied, and replaced by the mock | ||
596 | 539 | on replay mode. It may also be an "import path", | ||
597 | 540 | such as C{"time.time"}, in which case the object | ||
598 | 541 | will be the C{time} function from the C{time} module. | ||
599 | 542 | @param spec: Method calls will be checked for correctness against | ||
600 | 543 | the given object, which may be a class or an instance | ||
601 | 544 | where attributes will be looked up. Defaults to the | ||
602 | 545 | the C{object} parameter. May be set to None explicitly, | ||
603 | 546 | in which case spec checking is disabled. Checks may | ||
604 | 547 | also be disabled explicitly on a per-event basis with | ||
605 | 548 | the L{nospec()} method. | ||
606 | 549 | @param type: If set, the Mock's __class__ attribute will return | ||
607 | 550 | the given type. This will make C{isinstance()} calls | ||
608 | 551 | on the object work. Defaults to the type of the | ||
609 | 552 | C{object} parameter. May be set to None explicitly. | ||
610 | 553 | @param name: Name for the mock object, used in the representation of | ||
611 | 554 | expressions. The name is rarely needed, as it's usually | ||
612 | 555 | guessed correctly from the variable name used. | ||
613 | 556 | @param count: If set to false, expressions may be executed any number | ||
614 | 557 | of times, unless an expectation is explicitly set using | ||
615 | 558 | the L{count()} method. By default, expressions are | ||
616 | 559 | expected once. | ||
617 | 560 | @param passthrough: If set to False, passthrough of actions on the | ||
618 | 561 | proxy to the real object will only happen when | ||
619 | 562 | explicitly requested via the L{passthrough()} | ||
620 | 563 | method. | ||
621 | 564 | """ | ||
622 | 565 | if isinstance(object, basestring): | ||
623 | 566 | if name is None: | ||
624 | 567 | name = object | ||
625 | 568 | import_stack = object.split(".") | ||
626 | 569 | attr_stack = [] | ||
627 | 570 | while import_stack: | ||
628 | 571 | module_path = ".".join(import_stack) | ||
629 | 572 | try: | ||
630 | 573 | object = __import__(module_path, {}, {}, [""]) | ||
631 | 574 | except ImportError: | ||
632 | 575 | attr_stack.insert(0, import_stack.pop()) | ||
633 | 576 | if not import_stack: | ||
634 | 577 | raise | ||
635 | 578 | continue | ||
636 | 579 | else: | ||
637 | 580 | for attr in attr_stack: | ||
638 | 581 | object = getattr(object, attr) | ||
639 | 582 | break | ||
640 | 583 | if spec is True: | ||
641 | 584 | spec = object | ||
642 | 585 | if type is True: | ||
643 | 586 | type = __builtin__.type(object) | ||
644 | 587 | return Mock(self, spec=spec, type=type, object=object, | ||
645 | 588 | name=name, count=count, passthrough=passthrough) | ||
646 | 589 | |||
647 | 590 | def replace(self, object, spec=True, type=True, name=None, count=True, | ||
648 | 591 | passthrough=True): | ||
649 | 592 | """Create a proxy, and replace the original object with the mock. | ||
650 | 593 | |||
651 | 594 | On replay, the original object will be replaced by the returned | ||
652 | 595 | proxy in all dictionaries found in the running interpreter via | ||
653 | 596 | the garbage collecting system. This should cover module | ||
654 | 597 | namespaces, class namespaces, instance namespaces, and so on. | ||
655 | 598 | |||
656 | 599 | @param object: Real object to be proxied, and replaced by the mock | ||
657 | 600 | on replay mode. It may also be an "import path", | ||
658 | 601 | such as C{"time.time"}, in which case the object | ||
659 | 602 | will be the C{time} function from the C{time} module. | ||
660 | 603 | @param spec: Method calls will be checked for correctness against | ||
661 | 604 | the given object, which may be a class or an instance | ||
662 | 605 | where attributes will be looked up. Defaults to the | ||
663 | 606 | the C{object} parameter. May be set to None explicitly, | ||
664 | 607 | in which case spec checking is disabled. Checks may | ||
665 | 608 | also be disabled explicitly on a per-event basis with | ||
666 | 609 | the L{nospec()} method. | ||
667 | 610 | @param type: If set, the Mock's __class__ attribute will return | ||
668 | 611 | the given type. This will make C{isinstance()} calls | ||
669 | 612 | on the object work. Defaults to the type of the | ||
670 | 613 | C{object} parameter. May be set to None explicitly. | ||
671 | 614 | @param name: Name for the mock object, used in the representation of | ||
672 | 615 | expressions. The name is rarely needed, as it's usually | ||
673 | 616 | guessed correctly from the variable name used. | ||
674 | 617 | @param passthrough: If set to False, passthrough of actions on the | ||
675 | 618 | proxy to the real object will only happen when | ||
676 | 619 | explicitly requested via the L{passthrough()} | ||
677 | 620 | method. | ||
678 | 621 | """ | ||
679 | 622 | mock = self.proxy(object, spec, type, name, count, passthrough) | ||
680 | 623 | event = self._get_replay_restore_event() | ||
681 | 624 | event.add_task(ProxyReplacer(mock)) | ||
682 | 625 | return mock | ||
683 | 626 | |||
684 | 627 | def patch(self, object, spec=True): | ||
685 | 628 | """Patch an existing object to reproduce recorded events. | ||
686 | 629 | |||
687 | 630 | @param object: Class or instance to be patched. | ||
688 | 631 | @param spec: Method calls will be checked for correctness against | ||
689 | 632 | the given object, which may be a class or an instance | ||
690 | 633 | where attributes will be looked up. Defaults to the | ||
691 | 634 | the C{object} parameter. May be set to None explicitly, | ||
692 | 635 | in which case spec checking is disabled. Checks may | ||
693 | 636 | also be disabled explicitly on a per-event basis with | ||
694 | 637 | the L{nospec()} method. | ||
695 | 638 | |||
696 | 639 | The result of this method is still a mock object, which can be | ||
697 | 640 | used like any other mock object to record events. The difference | ||
698 | 641 | is that when the mocker is put on replay mode, the *real* object | ||
699 | 642 | will be modified to behave according to recorded expectations. | ||
700 | 643 | |||
701 | 644 | Patching works in individual instances, and also in classes. | ||
702 | 645 | When an instance is patched, recorded events will only be | ||
703 | 646 | considered on this specific instance, and other instances should | ||
704 | 647 | behave normally. When a class is patched, the reproduction of | ||
705 | 648 | events will be considered on any instance of this class once | ||
706 | 649 | created (collectively). | ||
707 | 650 | |||
708 | 651 | Observe that, unlike with proxies which catch only events done | ||
709 | 652 | through the mock object, *all* accesses to recorded expectations | ||
710 | 653 | will be considered; even these coming from the object itself | ||
711 | 654 | (e.g. C{self.hello()} is considered if this method was patched). | ||
712 | 655 | While this is a very powerful feature, and many times the reason | ||
713 | 656 | to use patches in the first place, it's important to keep this | ||
714 | 657 | behavior in mind. | ||
715 | 658 | |||
716 | 659 | Patching of the original object only takes place when the mocker | ||
717 | 660 | is put on replay mode, and the patched object will be restored | ||
718 | 661 | to its original state once the L{restore()} method is called | ||
719 | 662 | (explicitly, or implicitly with alternative conventions, such as | ||
720 | 663 | a C{with mocker:} block, or a MockerTestCase class). | ||
721 | 664 | """ | ||
722 | 665 | if spec is True: | ||
723 | 666 | spec = object | ||
724 | 667 | patcher = Patcher() | ||
725 | 668 | event = self._get_replay_restore_event() | ||
726 | 669 | event.add_task(patcher) | ||
727 | 670 | mock = Mock(self, object=object, patcher=patcher, | ||
728 | 671 | passthrough=True, spec=spec) | ||
729 | 672 | object.__mocker_mock__ = mock | ||
730 | 673 | return mock | ||
731 | 674 | |||
732 | 675 | def act(self, path): | ||
733 | 676 | """This is called by mock objects whenever something happens to them. | ||
734 | 677 | |||
735 | 678 | This method is part of the implementation between the mocker | ||
736 | 679 | and mock objects. | ||
737 | 680 | """ | ||
738 | 681 | if self._recording: | ||
739 | 682 | event = self.add_event(Event(path)) | ||
740 | 683 | for recorder in self._recorders: | ||
741 | 684 | recorder(self, event) | ||
742 | 685 | return Mock(self, path) | ||
743 | 686 | else: | ||
744 | 687 | # First run events that may run, then run unsatisfied events, then | ||
745 | 688 | # ones not previously run. We put the index in the ordering tuple | ||
746 | 689 | # instead of the actual event because we want a stable sort | ||
747 | 690 | # (ordering between 2 events is undefined). | ||
748 | 691 | events = self._events | ||
749 | 692 | order = [(events[i].satisfied()*2 + events[i].has_run(), i) | ||
750 | 693 | for i in range(len(events))] | ||
751 | 694 | order.sort() | ||
752 | 695 | postponed = None | ||
753 | 696 | for weight, i in order: | ||
754 | 697 | event = events[i] | ||
755 | 698 | if event.matches(path): | ||
756 | 699 | if event.may_run(path): | ||
757 | 700 | return event.run(path) | ||
758 | 701 | elif postponed is None: | ||
759 | 702 | postponed = event | ||
760 | 703 | if postponed is not None: | ||
761 | 704 | return postponed.run(path) | ||
762 | 705 | raise MatchError(ERROR_PREFIX + "Unexpected expression: %s" % path) | ||
763 | 706 | |||
764 | 707 | def get_recorders(cls, self): | ||
765 | 708 | """Return recorders associated with this mocker class or instance. | ||
766 | 709 | |||
767 | 710 | This method may be called on mocker instances and also on mocker | ||
768 | 711 | classes. See the L{add_recorder()} method for more information. | ||
769 | 712 | """ | ||
770 | 713 | return (self or cls)._recorders[:] | ||
771 | 714 | get_recorders = classinstancemethod(get_recorders) | ||
772 | 715 | |||
773 | 716 | def add_recorder(cls, self, recorder): | ||
774 | 717 | """Add a recorder to this mocker class or instance. | ||
775 | 718 | |||
776 | 719 | @param recorder: Callable accepting C{(mocker, event)} as parameters. | ||
777 | 720 | |||
778 | 721 | This is part of the implementation of mocker. | ||
779 | 722 | |||
780 | 723 | All registered recorders are called for translating events that | ||
781 | 724 | happen during recording into expectations to be met once the state | ||
782 | 725 | is switched to replay mode. | ||
783 | 726 | |||
784 | 727 | This method may be called on mocker instances and also on mocker | ||
785 | 728 | classes. When called on a class, the recorder will be used by | ||
786 | 729 | all instances, and also inherited on subclassing. When called on | ||
787 | 730 | instances, the recorder is added only to the given instance. | ||
788 | 731 | """ | ||
789 | 732 | (self or cls)._recorders.append(recorder) | ||
790 | 733 | return recorder | ||
791 | 734 | add_recorder = classinstancemethod(add_recorder) | ||
792 | 735 | |||
793 | 736 | def remove_recorder(cls, self, recorder): | ||
794 | 737 | """Remove the given recorder from this mocker class or instance. | ||
795 | 738 | |||
796 | 739 | This method may be called on mocker classes and also on mocker | ||
797 | 740 | instances. See the L{add_recorder()} method for more information. | ||
798 | 741 | """ | ||
799 | 742 | (self or cls)._recorders.remove(recorder) | ||
800 | 743 | remove_recorder = classinstancemethod(remove_recorder) | ||
801 | 744 | |||
802 | 745 | def result(self, value): | ||
803 | 746 | """Make the last recorded event return the given value on replay. | ||
804 | 747 | |||
805 | 748 | @param value: Object to be returned when the event is replayed. | ||
806 | 749 | """ | ||
807 | 750 | self.call(lambda *args, **kwargs: value) | ||
808 | 751 | |||
809 | 752 | def generate(self, sequence): | ||
810 | 753 | """Last recorded event will return a generator with the given sequence. | ||
811 | 754 | |||
812 | 755 | @param sequence: Sequence of values to be generated. | ||
813 | 756 | """ | ||
814 | 757 | def generate(*args, **kwargs): | ||
815 | 758 | for value in sequence: | ||
816 | 759 | yield value | ||
817 | 760 | self.call(generate) | ||
818 | 761 | |||
819 | 762 | def throw(self, exception): | ||
820 | 763 | """Make the last recorded event raise the given exception on replay. | ||
821 | 764 | |||
822 | 765 | @param exception: Class or instance of exception to be raised. | ||
823 | 766 | """ | ||
824 | 767 | def raise_exception(*args, **kwargs): | ||
825 | 768 | raise exception | ||
826 | 769 | self.call(raise_exception) | ||
827 | 770 | |||
828 | 771 | def call(self, func): | ||
829 | 772 | """Make the last recorded event cause the given function to be called. | ||
830 | 773 | |||
831 | 774 | @param func: Function to be called. | ||
832 | 775 | |||
833 | 776 | The result of the function will be used as the event result. | ||
834 | 777 | """ | ||
835 | 778 | self._events[-1].add_task(FunctionRunner(func)) | ||
836 | 779 | |||
837 | 780 | def count(self, min, max=False): | ||
838 | 781 | """Last recorded event must be replayed between min and max times. | ||
839 | 782 | |||
840 | 783 | @param min: Minimum number of times that the event must happen. | ||
841 | 784 | @param max: Maximum number of times that the event must happen. If | ||
842 | 785 | not given, it defaults to the same value of the C{min} | ||
843 | 786 | parameter. If set to None, there is no upper limit, and | ||
844 | 787 | the expectation is met as long as it happens at least | ||
845 | 788 | C{min} times. | ||
846 | 789 | """ | ||
847 | 790 | event = self._events[-1] | ||
848 | 791 | for task in event.get_tasks(): | ||
849 | 792 | if isinstance(task, RunCounter): | ||
850 | 793 | event.remove_task(task) | ||
851 | 794 | event.add_task(RunCounter(min, max)) | ||
852 | 795 | |||
853 | 796 | def is_ordering(self): | ||
854 | 797 | """Return true if all events are being ordered. | ||
855 | 798 | |||
856 | 799 | See the L{order()} method. | ||
857 | 800 | """ | ||
858 | 801 | return self._ordering | ||
859 | 802 | |||
860 | 803 | def unorder(self): | ||
861 | 804 | """Disable the ordered mode. | ||
862 | 805 | |||
863 | 806 | See the L{order()} method for more information. | ||
864 | 807 | """ | ||
865 | 808 | self._ordering = False | ||
866 | 809 | self._last_orderer = None | ||
867 | 810 | |||
868 | 811 | def order(self, *path_holders): | ||
869 | 812 | """Create an expectation of order between two or more events. | ||
870 | 813 | |||
871 | 814 | @param path_holders: Objects returned as the result of recorded events. | ||
872 | 815 | |||
873 | 816 | By default, mocker won't force events to happen precisely in | ||
874 | 817 | the order they were recorded. Calling this method will change | ||
875 | 818 | this behavior so that events will only match if reproduced in | ||
876 | 819 | the correct order. | ||
877 | 820 | |||
878 | 821 | There are two ways in which this method may be used. Which one | ||
879 | 822 | is used in a given occasion depends only on convenience. | ||
880 | 823 | |||
881 | 824 | If no arguments are passed, the mocker will be put in a mode where | ||
882 | 825 | all the recorded events following the method call will only be met | ||
883 | 826 | if they happen in order. When that's used, the mocker may be put | ||
884 | 827 | back in unordered mode by calling the L{unorder()} method, or by | ||
885 | 828 | using a 'with' block, like so:: | ||
886 | 829 | |||
887 | 830 | with mocker.ordered(): | ||
888 | 831 | <record events> | ||
889 | 832 | |||
890 | 833 | In this case, only expressions in <record events> will be ordered, | ||
891 | 834 | and the mocker will be back in unordered mode after the 'with' block. | ||
892 | 835 | |||
893 | 836 | The second way to use it is by specifying precisely which events | ||
894 | 837 | should be ordered. As an example:: | ||
895 | 838 | |||
896 | 839 | mock = mocker.mock() | ||
897 | 840 | expr1 = mock.hello() | ||
898 | 841 | expr2 = mock.world | ||
899 | 842 | expr3 = mock.x.y.z | ||
900 | 843 | mocker.order(expr1, expr2, expr3) | ||
901 | 844 | |||
902 | 845 | This method of ordering only works when the expression returns | ||
903 | 846 | another object. | ||
904 | 847 | |||
905 | 848 | Also check the L{after()} and L{before()} methods, which are | ||
906 | 849 | alternative ways to perform this. | ||
907 | 850 | """ | ||
908 | 851 | if not path_holders: | ||
909 | 852 | self._ordering = True | ||
910 | 853 | return OrderedContext(self) | ||
911 | 854 | |||
912 | 855 | last_orderer = None | ||
913 | 856 | for path_holder in path_holders: | ||
914 | 857 | if type(path_holder) is Path: | ||
915 | 858 | path = path_holder | ||
916 | 859 | else: | ||
917 | 860 | path = path_holder.__mocker_path__ | ||
918 | 861 | for event in self._events: | ||
919 | 862 | if event.path is path: | ||
920 | 863 | for task in event.get_tasks(): | ||
921 | 864 | if isinstance(task, Orderer): | ||
922 | 865 | orderer = task | ||
923 | 866 | break | ||
924 | 867 | else: | ||
925 | 868 | orderer = Orderer(path) | ||
926 | 869 | event.add_task(orderer) | ||
927 | 870 | if last_orderer: | ||
928 | 871 | orderer.add_dependency(last_orderer) | ||
929 | 872 | last_orderer = orderer | ||
930 | 873 | break | ||
931 | 874 | |||
932 | 875 | def after(self, *path_holders): | ||
933 | 876 | """Last recorded event must happen after events referred to. | ||
934 | 877 | |||
935 | 878 | @param path_holders: Objects returned as the result of recorded events | ||
936 | 879 | which should happen before the last recorded event | ||
937 | 880 | |||
938 | 881 | As an example, the idiom:: | ||
939 | 882 | |||
940 | 883 | expect(mock.x).after(mock.y, mock.z) | ||
941 | 884 | |||
942 | 885 | is an alternative way to say:: | ||
943 | 886 | |||
944 | 887 | expr_x = mock.x | ||
945 | 888 | expr_y = mock.y | ||
946 | 889 | expr_z = mock.z | ||
947 | 890 | mocker.order(expr_y, expr_x) | ||
948 | 891 | mocker.order(expr_z, expr_x) | ||
949 | 892 | |||
950 | 893 | See L{order()} for more information. | ||
951 | 894 | """ | ||
952 | 895 | last_path = self._events[-1].path | ||
953 | 896 | for path_holder in path_holders: | ||
954 | 897 | self.order(path_holder, last_path) | ||
955 | 898 | |||
956 | 899 | def before(self, *path_holders): | ||
957 | 900 | """Last recorded event must happen before events referred to. | ||
958 | 901 | |||
959 | 902 | @param path_holders: Objects returned as the result of recorded events | ||
960 | 903 | which should happen after the last recorded event | ||
961 | 904 | |||
962 | 905 | As an example, the idiom:: | ||
963 | 906 | |||
964 | 907 | expect(mock.x).before(mock.y, mock.z) | ||
965 | 908 | |||
966 | 909 | is an alternative way to say:: | ||
967 | 910 | |||
968 | 911 | expr_x = mock.x | ||
969 | 912 | expr_y = mock.y | ||
970 | 913 | expr_z = mock.z | ||
971 | 914 | mocker.order(expr_x, expr_y) | ||
972 | 915 | mocker.order(expr_x, expr_z) | ||
973 | 916 | |||
974 | 917 | See L{order()} for more information. | ||
975 | 918 | """ | ||
976 | 919 | last_path = self._events[-1].path | ||
977 | 920 | for path_holder in path_holders: | ||
978 | 921 | self.order(last_path, path_holder) | ||
979 | 922 | |||
980 | 923 | def nospec(self): | ||
981 | 924 | """Don't check method specification of real object on last event. | ||
982 | 925 | |||
983 | 926 | By default, when using a mock created as the result of a call to | ||
984 | 927 | L{proxy()}, L{replace()}, and C{patch()}, or when passing the spec | ||
985 | 928 | attribute to the L{mock()} method, method calls on the given object | ||
986 | 929 | are checked for correctness against the specification of the real | ||
987 | 930 | object (or the explicitly provided spec). | ||
988 | 931 | |||
989 | 932 | This method will disable that check specifically for the last | ||
990 | 933 | recorded event. | ||
991 | 934 | """ | ||
992 | 935 | event = self._events[-1] | ||
993 | 936 | for task in event.get_tasks(): | ||
994 | 937 | if isinstance(task, SpecChecker): | ||
995 | 938 | event.remove_task(task) | ||
996 | 939 | |||
997 | 940 | def passthrough(self, result_callback=None): | ||
998 | 941 | """Make the last recorded event run on the real object once seen. | ||
999 | 942 | |||
1000 | 943 | @param result_callback: If given, this function will be called with | ||
1001 | 944 | the result of the *real* method call as the only argument. | ||
1002 | 945 | |||
1003 | 946 | This can only be used on proxies, as returned by the L{proxy()} | ||
1004 | 947 | and L{replace()} methods, or on mocks representing patched objects, | ||
1005 | 948 | as returned by the L{patch()} method. | ||
1006 | 949 | """ | ||
1007 | 950 | event = self._events[-1] | ||
1008 | 951 | if event.path.root_object is None: | ||
1009 | 952 | raise TypeError("Mock object isn't a proxy") | ||
1010 | 953 | event.add_task(PathExecuter(result_callback)) | ||
1011 | 954 | |||
1012 | 955 | def __enter__(self): | ||
1013 | 956 | """Enter in a 'with' context. This will run replay().""" | ||
1014 | 957 | self.replay() | ||
1015 | 958 | return self | ||
1016 | 959 | |||
1017 | 960 | def __exit__(self, type, value, traceback): | ||
1018 | 961 | """Exit from a 'with' context. | ||
1019 | 962 | |||
1020 | 963 | This will run restore() at all times, but will only run verify() | ||
1021 | 964 | if the 'with' block itself hasn't raised an exception. Exceptions | ||
1022 | 965 | in that block are never swallowed. | ||
1023 | 966 | """ | ||
1024 | 967 | self.restore() | ||
1025 | 968 | if type is None: | ||
1026 | 969 | self.verify() | ||
1027 | 970 | return False | ||
1028 | 971 | |||
1029 | 972 | def _get_replay_restore_event(self): | ||
1030 | 973 | """Return unique L{ReplayRestoreEvent}, creating if needed. | ||
1031 | 974 | |||
1032 | 975 | Some tasks only want to replay/restore. When that's the case, | ||
1033 | 976 | they shouldn't act on other events during replay. Also, they | ||
1034 | 977 | can all be put in a single event when that's the case. Thus, | ||
1035 | 978 | we add a single L{ReplayRestoreEvent} as the first element of | ||
1036 | 979 | the list. | ||
1037 | 980 | """ | ||
1038 | 981 | if not self._events or type(self._events[0]) != ReplayRestoreEvent: | ||
1039 | 982 | self._events.insert(0, ReplayRestoreEvent()) | ||
1040 | 983 | return self._events[0] | ||
1041 | 984 | |||
1042 | 985 | |||
1043 | 986 | class OrderedContext(object): | ||
1044 | 987 | |||
1045 | 988 | def __init__(self, mocker): | ||
1046 | 989 | self._mocker = mocker | ||
1047 | 990 | |||
1048 | 991 | def __enter__(self): | ||
1049 | 992 | return None | ||
1050 | 993 | |||
1051 | 994 | def __exit__(self, type, value, traceback): | ||
1052 | 995 | self._mocker.unorder() | ||
1053 | 996 | |||
1054 | 997 | |||
1055 | 998 | class Mocker(MockerBase): | ||
1056 | 999 | __doc__ = MockerBase.__doc__ | ||
1057 | 1000 | |||
1058 | 1001 | # Decorator to add recorders on the standard Mocker class. | ||
1059 | 1002 | recorder = Mocker.add_recorder | ||
1060 | 1003 | |||
1061 | 1004 | |||
1062 | 1005 | # -------------------------------------------------------------------- | ||
1063 | 1006 | # Mock object. | ||
1064 | 1007 | |||
1065 | 1008 | class Mock(object): | ||
1066 | 1009 | |||
1067 | 1010 | def __init__(self, mocker, path=None, name=None, spec=None, type=None, | ||
1068 | 1011 | object=None, passthrough=False, patcher=None, count=True): | ||
1069 | 1012 | self.__mocker__ = mocker | ||
1070 | 1013 | self.__mocker_path__ = path or Path(self, object) | ||
1071 | 1014 | self.__mocker_name__ = name | ||
1072 | 1015 | self.__mocker_spec__ = spec | ||
1073 | 1016 | self.__mocker_object__ = object | ||
1074 | 1017 | self.__mocker_passthrough__ = passthrough | ||
1075 | 1018 | self.__mocker_patcher__ = patcher | ||
1076 | 1019 | self.__mocker_replace__ = False | ||
1077 | 1020 | self.__mocker_type__ = type | ||
1078 | 1021 | self.__mocker_count__ = count | ||
1079 | 1022 | |||
1080 | 1023 | def __mocker_act__(self, kind, args=(), kwargs={}, object=None): | ||
1081 | 1024 | if self.__mocker_name__ is None: | ||
1082 | 1025 | self.__mocker_name__ = find_object_name(self, 2) | ||
1083 | 1026 | action = Action(kind, args, kwargs, self.__mocker_path__) | ||
1084 | 1027 | path = self.__mocker_path__ + action | ||
1085 | 1028 | if object is not None: | ||
1086 | 1029 | path.root_object = object | ||
1087 | 1030 | try: | ||
1088 | 1031 | return self.__mocker__.act(path) | ||
1089 | 1032 | except MatchError, exception: | ||
1090 | 1033 | root_mock = path.root_mock | ||
1091 | 1034 | if (path.root_object is not None and | ||
1092 | 1035 | root_mock.__mocker_passthrough__): | ||
1093 | 1036 | return path.execute(path.root_object) | ||
1094 | 1037 | # Reinstantiate to show raise statement on traceback, and | ||
1095 | 1038 | # also to make the traceback shown shorter. | ||
1096 | 1039 | raise MatchError(str(exception)) | ||
1097 | 1040 | except AssertionError, e: | ||
1098 | 1041 | lines = str(e).splitlines() | ||
1099 | 1042 | message = [ERROR_PREFIX + "Unmet expectation:", ""] | ||
1100 | 1043 | message.append("=> " + lines.pop(0)) | ||
1101 | 1044 | message.extend([" " + line for line in lines]) | ||
1102 | 1045 | message.append("") | ||
1103 | 1046 | raise AssertionError(os.linesep.join(message)) | ||
1104 | 1047 | |||
1105 | 1048 | def __getattribute__(self, name): | ||
1106 | 1049 | if name.startswith("__mocker_"): | ||
1107 | 1050 | return super(Mock, self).__getattribute__(name) | ||
1108 | 1051 | if name == "__class__": | ||
1109 | 1052 | if self.__mocker__.is_recording() or self.__mocker_type__ is None: | ||
1110 | 1053 | return type(self) | ||
1111 | 1054 | return self.__mocker_type__ | ||
1112 | 1055 | return self.__mocker_act__("getattr", (name,)) | ||
1113 | 1056 | |||
1114 | 1057 | def __setattr__(self, name, value): | ||
1115 | 1058 | if name.startswith("__mocker_"): | ||
1116 | 1059 | return super(Mock, self).__setattr__(name, value) | ||
1117 | 1060 | return self.__mocker_act__("setattr", (name, value)) | ||
1118 | 1061 | |||
1119 | 1062 | def __delattr__(self, name): | ||
1120 | 1063 | return self.__mocker_act__("delattr", (name,)) | ||
1121 | 1064 | |||
1122 | 1065 | def __call__(self, *args, **kwargs): | ||
1123 | 1066 | return self.__mocker_act__("call", args, kwargs) | ||
1124 | 1067 | |||
1125 | 1068 | def __contains__(self, value): | ||
1126 | 1069 | return self.__mocker_act__("contains", (value,)) | ||
1127 | 1070 | |||
1128 | 1071 | def __getitem__(self, key): | ||
1129 | 1072 | return self.__mocker_act__("getitem", (key,)) | ||
1130 | 1073 | |||
1131 | 1074 | def __setitem__(self, key, value): | ||
1132 | 1075 | return self.__mocker_act__("setitem", (key, value)) | ||
1133 | 1076 | |||
1134 | 1077 | def __delitem__(self, key): | ||
1135 | 1078 | return self.__mocker_act__("delitem", (key,)) | ||
1136 | 1079 | |||
1137 | 1080 | def __len__(self): | ||
1138 | 1081 | # MatchError is turned on an AttributeError so that list() and | ||
1139 | 1082 | # friends act properly when trying to get length hints on | ||
1140 | 1083 | # something that doesn't offer them. | ||
1141 | 1084 | try: | ||
1142 | 1085 | result = self.__mocker_act__("len") | ||
1143 | 1086 | except MatchError, e: | ||
1144 | 1087 | raise AttributeError(str(e)) | ||
1145 | 1088 | if type(result) is Mock: | ||
1146 | 1089 | return 0 | ||
1147 | 1090 | return result | ||
1148 | 1091 | |||
1149 | 1092 | def __nonzero__(self): | ||
1150 | 1093 | try: | ||
1151 | 1094 | return self.__mocker_act__("nonzero") | ||
1152 | 1095 | except MatchError, e: | ||
1153 | 1096 | return True | ||
1154 | 1097 | |||
1155 | 1098 | def __iter__(self): | ||
1156 | 1099 | # XXX On py3k, when next() becomes __next__(), we'll be able | ||
1157 | 1100 | # to return the mock itself because it will be considered | ||
1158 | 1101 | # an iterator (we'll be mocking __next__ as well, which we | ||
1159 | 1102 | # can't now). | ||
1160 | 1103 | result = self.__mocker_act__("iter") | ||
1161 | 1104 | if type(result) is Mock: | ||
1162 | 1105 | return iter([]) | ||
1163 | 1106 | return result | ||
1164 | 1107 | |||
1165 | 1108 | # When adding a new action kind here, also add support for it on | ||
1166 | 1109 | # Action.execute() and Path.__str__(). | ||
1167 | 1110 | |||
1168 | 1111 | |||
1169 | 1112 | def find_object_name(obj, depth=0): | ||
1170 | 1113 | """Try to detect how the object is named on a previous scope.""" | ||
1171 | 1114 | try: | ||
1172 | 1115 | frame = sys._getframe(depth+1) | ||
1173 | 1116 | except: | ||
1174 | 1117 | return None | ||
1175 | 1118 | for name, frame_obj in frame.f_locals.iteritems(): | ||
1176 | 1119 | if frame_obj is obj: | ||
1177 | 1120 | return name | ||
1178 | 1121 | self = frame.f_locals.get("self") | ||
1179 | 1122 | if self is not None: | ||
1180 | 1123 | try: | ||
1181 | 1124 | items = list(self.__dict__.iteritems()) | ||
1182 | 1125 | except: | ||
1183 | 1126 | pass | ||
1184 | 1127 | else: | ||
1185 | 1128 | for name, self_obj in items: | ||
1186 | 1129 | if self_obj is obj: | ||
1187 | 1130 | return name | ||
1188 | 1131 | return None | ||
1189 | 1132 | |||
1190 | 1133 | |||
1191 | 1134 | # -------------------------------------------------------------------- | ||
1192 | 1135 | # Action and path. | ||
1193 | 1136 | |||
1194 | 1137 | class Action(object): | ||
1195 | 1138 | |||
1196 | 1139 | def __init__(self, kind, args, kwargs, path=None): | ||
1197 | 1140 | self.kind = kind | ||
1198 | 1141 | self.args = args | ||
1199 | 1142 | self.kwargs = kwargs | ||
1200 | 1143 | self.path = path | ||
1201 | 1144 | self._execute_cache = {} | ||
1202 | 1145 | |||
1203 | 1146 | def __repr__(self): | ||
1204 | 1147 | if self.path is None: | ||
1205 | 1148 | return "Action(%r, %r, %r)" % (self.kind, self.args, self.kwargs) | ||
1206 | 1149 | return "Action(%r, %r, %r, %r)" % \ | ||
1207 | 1150 | (self.kind, self.args, self.kwargs, self.path) | ||
1208 | 1151 | |||
1209 | 1152 | def __eq__(self, other): | ||
1210 | 1153 | return (self.kind == other.kind and | ||
1211 | 1154 | self.args == other.args and | ||
1212 | 1155 | self.kwargs == other.kwargs) | ||
1213 | 1156 | |||
1214 | 1157 | def __ne__(self, other): | ||
1215 | 1158 | return not self.__eq__(other) | ||
1216 | 1159 | |||
1217 | 1160 | def matches(self, other): | ||
1218 | 1161 | return (self.kind == other.kind and | ||
1219 | 1162 | match_params(self.args, self.kwargs, other.args, other.kwargs)) | ||
1220 | 1163 | |||
1221 | 1164 | def execute(self, object): | ||
1222 | 1165 | # This caching scheme may fail if the object gets deallocated before | ||
1223 | 1166 | # the action, as the id might get reused. It's somewhat easy to fix | ||
1224 | 1167 | # that with a weakref callback. For our uses, though, the object | ||
1225 | 1168 | # should never get deallocated before the action itself, so we'll | ||
1226 | 1169 | # just keep it simple. | ||
1227 | 1170 | if id(object) in self._execute_cache: | ||
1228 | 1171 | return self._execute_cache[id(object)] | ||
1229 | 1172 | execute = getattr(object, "__mocker_execute__", None) | ||
1230 | 1173 | if execute is not None: | ||
1231 | 1174 | result = execute(self, object) | ||
1232 | 1175 | else: | ||
1233 | 1176 | kind = self.kind | ||
1234 | 1177 | if kind == "getattr": | ||
1235 | 1178 | result = getattr(object, self.args[0]) | ||
1236 | 1179 | elif kind == "setattr": | ||
1237 | 1180 | result = setattr(object, self.args[0], self.args[1]) | ||
1238 | 1181 | elif kind == "delattr": | ||
1239 | 1182 | result = delattr(object, self.args[0]) | ||
1240 | 1183 | elif kind == "call": | ||
1241 | 1184 | result = object(*self.args, **self.kwargs) | ||
1242 | 1185 | elif kind == "contains": | ||
1243 | 1186 | result = self.args[0] in object | ||
1244 | 1187 | elif kind == "getitem": | ||
1245 | 1188 | result = object[self.args[0]] | ||
1246 | 1189 | elif kind == "setitem": | ||
1247 | 1190 | result = object[self.args[0]] = self.args[1] | ||
1248 | 1191 | elif kind == "delitem": | ||
1249 | 1192 | del object[self.args[0]] | ||
1250 | 1193 | result = None | ||
1251 | 1194 | elif kind == "len": | ||
1252 | 1195 | result = len(object) | ||
1253 | 1196 | elif kind == "nonzero": | ||
1254 | 1197 | result = bool(object) | ||
1255 | 1198 | elif kind == "iter": | ||
1256 | 1199 | result = iter(object) | ||
1257 | 1200 | else: | ||
1258 | 1201 | raise RuntimeError("Don't know how to execute %r kind." % kind) | ||
1259 | 1202 | self._execute_cache[id(object)] = result | ||
1260 | 1203 | return result | ||
1261 | 1204 | |||
1262 | 1205 | |||
1263 | 1206 | class Path(object): | ||
1264 | 1207 | |||
1265 | 1208 | def __init__(self, root_mock, root_object=None, actions=()): | ||
1266 | 1209 | self.root_mock = root_mock | ||
1267 | 1210 | self.root_object = root_object | ||
1268 | 1211 | self.actions = tuple(actions) | ||
1269 | 1212 | self.__mocker_replace__ = False | ||
1270 | 1213 | |||
1271 | 1214 | def parent_path(self): | ||
1272 | 1215 | if not self.actions: | ||
1273 | 1216 | return None | ||
1274 | 1217 | return self.actions[-1].path | ||
1275 | 1218 | parent_path = property(parent_path) | ||
1276 | 1219 | |||
1277 | 1220 | def __add__(self, action): | ||
1278 | 1221 | """Return a new path which includes the given action at the end.""" | ||
1279 | 1222 | return self.__class__(self.root_mock, self.root_object, | ||
1280 | 1223 | self.actions + (action,)) | ||
1281 | 1224 | |||
1282 | 1225 | def __eq__(self, other): | ||
1283 | 1226 | """Verify if the two paths are equal. | ||
1284 | 1227 | |||
1285 | 1228 | Two paths are equal if they refer to the same mock object, and | ||
1286 | 1229 | have the actions with equal kind, args and kwargs. | ||
1287 | 1230 | """ | ||
1288 | 1231 | if (self.root_mock is not other.root_mock or | ||
1289 | 1232 | self.root_object is not other.root_object or | ||
1290 | 1233 | len(self.actions) != len(other.actions)): | ||
1291 | 1234 | return False | ||
1292 | 1235 | for action, other_action in zip(self.actions, other.actions): | ||
1293 | 1236 | if action != other_action: | ||
1294 | 1237 | return False | ||
1295 | 1238 | return True | ||
1296 | 1239 | |||
1297 | 1240 | def matches(self, other): | ||
1298 | 1241 | """Verify if the two paths are equivalent. | ||
1299 | 1242 | |||
1300 | 1243 | Two paths are equal if they refer to the same mock object, and | ||
1301 | 1244 | have the same actions performed on them. | ||
1302 | 1245 | """ | ||
1303 | 1246 | if (self.root_mock is not other.root_mock or | ||
1304 | 1247 | len(self.actions) != len(other.actions)): | ||
1305 | 1248 | return False | ||
1306 | 1249 | for action, other_action in zip(self.actions, other.actions): | ||
1307 | 1250 | if not action.matches(other_action): | ||
1308 | 1251 | return False | ||
1309 | 1252 | return True | ||
1310 | 1253 | |||
1311 | 1254 | def execute(self, object): | ||
1312 | 1255 | """Execute all actions sequentially on object, and return result. | ||
1313 | 1256 | """ | ||
1314 | 1257 | for action in self.actions: | ||
1315 | 1258 | object = action.execute(object) | ||
1316 | 1259 | return object | ||
1317 | 1260 | |||
1318 | 1261 | def __str__(self): | ||
1319 | 1262 | """Transform the path into a nice string such as obj.x.y('z').""" | ||
1320 | 1263 | result = self.root_mock.__mocker_name__ or "<mock>" | ||
1321 | 1264 | for action in self.actions: | ||
1322 | 1265 | if action.kind == "getattr": | ||
1323 | 1266 | result = "%s.%s" % (result, action.args[0]) | ||
1324 | 1267 | elif action.kind == "setattr": | ||
1325 | 1268 | result = "%s.%s = %r" % (result, action.args[0], action.args[1]) | ||
1326 | 1269 | elif action.kind == "delattr": | ||
1327 | 1270 | result = "del %s.%s" % (result, action.args[0]) | ||
1328 | 1271 | elif action.kind == "call": | ||
1329 | 1272 | args = [repr(x) for x in action.args] | ||
1330 | 1273 | items = list(action.kwargs.iteritems()) | ||
1331 | 1274 | items.sort() | ||
1332 | 1275 | for pair in items: | ||
1333 | 1276 | args.append("%s=%r" % pair) | ||
1334 | 1277 | result = "%s(%s)" % (result, ", ".join(args)) | ||
1335 | 1278 | elif action.kind == "contains": | ||
1336 | 1279 | result = "%r in %s" % (action.args[0], result) | ||
1337 | 1280 | elif action.kind == "getitem": | ||
1338 | 1281 | result = "%s[%r]" % (result, action.args[0]) | ||
1339 | 1282 | elif action.kind == "setitem": | ||
1340 | 1283 | result = "%s[%r] = %r" % (result, action.args[0], | ||
1341 | 1284 | action.args[1]) | ||
1342 | 1285 | elif action.kind == "delitem": | ||
1343 | 1286 | result = "del %s[%r]" % (result, action.args[0]) | ||
1344 | 1287 | elif action.kind == "len": | ||
1345 | 1288 | result = "len(%s)" % result | ||
1346 | 1289 | elif action.kind == "nonzero": | ||
1347 | 1290 | result = "bool(%s)" % result | ||
1348 | 1291 | elif action.kind == "iter": | ||
1349 | 1292 | result = "iter(%s)" % result | ||
1350 | 1293 | else: | ||
1351 | 1294 | raise RuntimeError("Don't know how to format kind %r" % | ||
1352 | 1295 | action.kind) | ||
1353 | 1296 | return result | ||
1354 | 1297 | |||
1355 | 1298 | |||
1356 | 1299 | class SpecialArgument(object): | ||
1357 | 1300 | """Base for special arguments for matching parameters.""" | ||
1358 | 1301 | |||
1359 | 1302 | def __init__(self, object=None): | ||
1360 | 1303 | self.object = object | ||
1361 | 1304 | |||
1362 | 1305 | def __repr__(self): | ||
1363 | 1306 | if self.object is None: | ||
1364 | 1307 | return self.__class__.__name__ | ||
1365 | 1308 | else: | ||
1366 | 1309 | return "%s(%r)" % (self.__class__.__name__, self.object) | ||
1367 | 1310 | |||
1368 | 1311 | def matches(self, other): | ||
1369 | 1312 | return True | ||
1370 | 1313 | |||
1371 | 1314 | def __eq__(self, other): | ||
1372 | 1315 | return type(other) == type(self) and self.object == other.object | ||
1373 | 1316 | |||
1374 | 1317 | |||
1375 | 1318 | class ANY(SpecialArgument): | ||
1376 | 1319 | """Matches any single argument.""" | ||
1377 | 1320 | |||
1378 | 1321 | ANY = ANY() | ||
1379 | 1322 | |||
1380 | 1323 | |||
1381 | 1324 | class ARGS(SpecialArgument): | ||
1382 | 1325 | """Matches zero or more positional arguments.""" | ||
1383 | 1326 | |||
1384 | 1327 | ARGS = ARGS() | ||
1385 | 1328 | |||
1386 | 1329 | |||
1387 | 1330 | class KWARGS(SpecialArgument): | ||
1388 | 1331 | """Matches zero or more keyword arguments.""" | ||
1389 | 1332 | |||
1390 | 1333 | KWARGS = KWARGS() | ||
1391 | 1334 | |||
1392 | 1335 | |||
1393 | 1336 | class IS(SpecialArgument): | ||
1394 | 1337 | |||
1395 | 1338 | def matches(self, other): | ||
1396 | 1339 | return self.object is other | ||
1397 | 1340 | |||
1398 | 1341 | def __eq__(self, other): | ||
1399 | 1342 | return type(other) == type(self) and self.object is other.object | ||
1400 | 1343 | |||
1401 | 1344 | |||
1402 | 1345 | class CONTAINS(SpecialArgument): | ||
1403 | 1346 | |||
1404 | 1347 | def matches(self, other): | ||
1405 | 1348 | try: | ||
1406 | 1349 | other.__contains__ | ||
1407 | 1350 | except AttributeError: | ||
1408 | 1351 | try: | ||
1409 | 1352 | iter(other) | ||
1410 | 1353 | except TypeError: | ||
1411 | 1354 | # If an object can't be iterated, and has no __contains__ | ||
1412 | 1355 | # hook, it'd blow up on the test below. We test this in | ||
1413 | 1356 | # advance to prevent catching more errors than we really | ||
1414 | 1357 | # want. | ||
1415 | 1358 | return False | ||
1416 | 1359 | return self.object in other | ||
1417 | 1360 | |||
1418 | 1361 | |||
1419 | 1362 | class IN(SpecialArgument): | ||
1420 | 1363 | |||
1421 | 1364 | def matches(self, other): | ||
1422 | 1365 | return other in self.object | ||
1423 | 1366 | |||
1424 | 1367 | |||
1425 | 1368 | class MATCH(SpecialArgument): | ||
1426 | 1369 | |||
1427 | 1370 | def matches(self, other): | ||
1428 | 1371 | return bool(self.object(other)) | ||
1429 | 1372 | |||
1430 | 1373 | def __eq__(self, other): | ||
1431 | 1374 | return type(other) == type(self) and self.object is other.object | ||
1432 | 1375 | |||
1433 | 1376 | |||
1434 | 1377 | def match_params(args1, kwargs1, args2, kwargs2): | ||
1435 | 1378 | """Match the two sets of parameters, considering special parameters.""" | ||
1436 | 1379 | |||
1437 | 1380 | has_args = ARGS in args1 | ||
1438 | 1381 | has_kwargs = KWARGS in args1 | ||
1439 | 1382 | |||
1440 | 1383 | if has_kwargs: | ||
1441 | 1384 | args1 = [arg1 for arg1 in args1 if arg1 is not KWARGS] | ||
1442 | 1385 | elif len(kwargs1) != len(kwargs2): | ||
1443 | 1386 | return False | ||
1444 | 1387 | |||
1445 | 1388 | if not has_args and len(args1) != len(args2): | ||
1446 | 1389 | return False | ||
1447 | 1390 | |||
1448 | 1391 | # Either we have the same number of kwargs, or unknown keywords are | ||
1449 | 1392 | # accepted (KWARGS was used), so check just the ones in kwargs1. | ||
1450 | 1393 | for key, arg1 in kwargs1.iteritems(): | ||
1451 | 1394 | if key not in kwargs2: | ||
1452 | 1395 | return False | ||
1453 | 1396 | arg2 = kwargs2[key] | ||
1454 | 1397 | if isinstance(arg1, SpecialArgument): | ||
1455 | 1398 | if not arg1.matches(arg2): | ||
1456 | 1399 | return False | ||
1457 | 1400 | elif arg1 != arg2: | ||
1458 | 1401 | return False | ||
1459 | 1402 | |||
1460 | 1403 | # Keywords match. Now either we have the same number of | ||
1461 | 1404 | # arguments, or ARGS was used. If ARGS wasn't used, arguments | ||
1462 | 1405 | # must match one-on-one necessarily. | ||
1463 | 1406 | if not has_args: | ||
1464 | 1407 | for arg1, arg2 in zip(args1, args2): | ||
1465 | 1408 | if isinstance(arg1, SpecialArgument): | ||
1466 | 1409 | if not arg1.matches(arg2): | ||
1467 | 1410 | return False | ||
1468 | 1411 | elif arg1 != arg2: | ||
1469 | 1412 | return False | ||
1470 | 1413 | return True | ||
1471 | 1414 | |||
1472 | 1415 | # Easy choice. Keywords are matching, and anything on args is accepted. | ||
1473 | 1416 | if (ARGS,) == args1: | ||
1474 | 1417 | return True | ||
1475 | 1418 | |||
1476 | 1419 | # We have something different there. If we don't have positional | ||
1477 | 1420 | # arguments on the original call, it can't match. | ||
1478 | 1421 | if not args2: | ||
1479 | 1422 | # Unless we have just several ARGS (which is bizarre, but..). | ||
1480 | 1423 | for arg1 in args1: | ||
1481 | 1424 | if arg1 is not ARGS: | ||
1482 | 1425 | return False | ||
1483 | 1426 | return True | ||
1484 | 1427 | |||
1485 | 1428 | # Ok, all bets are lost. We have to actually do the more expensive | ||
1486 | 1429 | # matching. This is an algorithm based on the idea of the Levenshtein | ||
1487 | 1430 | # Distance between two strings, but heavily hacked for this purpose. | ||
1488 | 1431 | args2l = len(args2) | ||
1489 | 1432 | if args1[0] is ARGS: | ||
1490 | 1433 | args1 = args1[1:] | ||
1491 | 1434 | array = [0]*args2l | ||
1492 | 1435 | else: | ||
1493 | 1436 | array = [1]*args2l | ||
1494 | 1437 | for i in range(len(args1)): | ||
1495 | 1438 | last = array[0] | ||
1496 | 1439 | if args1[i] is ARGS: | ||
1497 | 1440 | for j in range(1, args2l): | ||
1498 | 1441 | last, array[j] = array[j], min(array[j-1], array[j], last) | ||
1499 | 1442 | else: | ||
1500 | 1443 | array[0] = i or int(args1[i] != args2[0]) | ||
1501 | 1444 | for j in range(1, args2l): | ||
1502 | 1445 | last, array[j] = array[j], last or int(args1[i] != args2[j]) | ||
1503 | 1446 | if 0 not in array: | ||
1504 | 1447 | return False | ||
1505 | 1448 | if array[-1] != 0: | ||
1506 | 1449 | return False | ||
1507 | 1450 | return True | ||
1508 | 1451 | |||
1509 | 1452 | |||
1510 | 1453 | # -------------------------------------------------------------------- | ||
1511 | 1454 | # Event and task base. | ||
1512 | 1455 | |||
1513 | 1456 | class Event(object): | ||
1514 | 1457 | """Aggregation of tasks that keep track of a recorded action. | ||
1515 | 1458 | |||
1516 | 1459 | An event represents something that may or may not happen while the | ||
1517 | 1460 | mocked environment is running, such as an attribute access, or a | ||
1518 | 1461 | method call. The event is composed of several tasks that are | ||
1519 | 1462 | orchestrated together to create a composed meaning for the event, | ||
1520 | 1463 | including for which actions it should be run, what happens when it | ||
1521 | 1464 | runs, and what's the expectations about the actions run. | ||
1522 | 1465 | """ | ||
1523 | 1466 | |||
1524 | 1467 | def __init__(self, path=None): | ||
1525 | 1468 | self.path = path | ||
1526 | 1469 | self._tasks = [] | ||
1527 | 1470 | self._has_run = False | ||
1528 | 1471 | |||
1529 | 1472 | def add_task(self, task): | ||
1530 | 1473 | """Add a new task to this taks.""" | ||
1531 | 1474 | self._tasks.append(task) | ||
1532 | 1475 | return task | ||
1533 | 1476 | |||
1534 | 1477 | def remove_task(self, task): | ||
1535 | 1478 | self._tasks.remove(task) | ||
1536 | 1479 | |||
1537 | 1480 | def get_tasks(self): | ||
1538 | 1481 | return self._tasks[:] | ||
1539 | 1482 | |||
1540 | 1483 | def matches(self, path): | ||
1541 | 1484 | """Return true if *all* tasks match the given path.""" | ||
1542 | 1485 | for task in self._tasks: | ||
1543 | 1486 | if not task.matches(path): | ||
1544 | 1487 | return False | ||
1545 | 1488 | return bool(self._tasks) | ||
1546 | 1489 | |||
1547 | 1490 | def has_run(self): | ||
1548 | 1491 | return self._has_run | ||
1549 | 1492 | |||
1550 | 1493 | def may_run(self, path): | ||
1551 | 1494 | """Verify if any task would certainly raise an error if run. | ||
1552 | 1495 | |||
1553 | 1496 | This will call the C{may_run()} method on each task and return | ||
1554 | 1497 | false if any of them returns false. | ||
1555 | 1498 | """ | ||
1556 | 1499 | for task in self._tasks: | ||
1557 | 1500 | if not task.may_run(path): | ||
1558 | 1501 | return False | ||
1559 | 1502 | return True | ||
1560 | 1503 | |||
1561 | 1504 | def run(self, path): | ||
1562 | 1505 | """Run all tasks with the given action. | ||
1563 | 1506 | |||
1564 | 1507 | @param path: The path of the expression run. | ||
1565 | 1508 | |||
1566 | 1509 | Running an event means running all of its tasks individually and in | ||
1567 | 1510 | order. An event should only ever be run if all of its tasks claim to | ||
1568 | 1511 | match the given action. | ||
1569 | 1512 | |||
1570 | 1513 | The result of this method will be the last result of a task | ||
1571 | 1514 | which isn't None, or None if they're all None. | ||
1572 | 1515 | """ | ||
1573 | 1516 | self._has_run = True | ||
1574 | 1517 | result = None | ||
1575 | 1518 | errors = [] | ||
1576 | 1519 | for task in self._tasks: | ||
1577 | 1520 | try: | ||
1578 | 1521 | task_result = task.run(path) | ||
1579 | 1522 | except AssertionError, e: | ||
1580 | 1523 | error = str(e) | ||
1581 | 1524 | if not error: | ||
1582 | 1525 | raise RuntimeError("Empty error message from %r" % task) | ||
1583 | 1526 | errors.append(error) | ||
1584 | 1527 | else: | ||
1585 | 1528 | if task_result is not None: | ||
1586 | 1529 | result = task_result | ||
1587 | 1530 | if errors: | ||
1588 | 1531 | message = [str(self.path)] | ||
1589 | 1532 | if str(path) != message[0]: | ||
1590 | 1533 | message.append("- Run: %s" % path) | ||
1591 | 1534 | for error in errors: | ||
1592 | 1535 | lines = error.splitlines() | ||
1593 | 1536 | message.append("- " + lines.pop(0)) | ||
1594 | 1537 | message.extend([" " + line for line in lines]) | ||
1595 | 1538 | raise AssertionError(os.linesep.join(message)) | ||
1596 | 1539 | return result | ||
1597 | 1540 | |||
1598 | 1541 | def satisfied(self): | ||
1599 | 1542 | """Return true if all tasks are satisfied. | ||
1600 | 1543 | |||
1601 | 1544 | Being satisfied means that there are no unmet expectations. | ||
1602 | 1545 | """ | ||
1603 | 1546 | for task in self._tasks: | ||
1604 | 1547 | try: | ||
1605 | 1548 | task.verify() | ||
1606 | 1549 | except AssertionError: | ||
1607 | 1550 | return False | ||
1608 | 1551 | return True | ||
1609 | 1552 | |||
1610 | 1553 | def verify(self): | ||
1611 | 1554 | """Run verify on all tasks. | ||
1612 | 1555 | |||
1613 | 1556 | The verify method is supposed to raise an AssertionError if the | ||
1614 | 1557 | task has unmet expectations, with a one-line explanation about | ||
1615 | 1558 | why this item is unmet. This method should be safe to be called | ||
1616 | 1559 | multiple times without side effects. | ||
1617 | 1560 | """ | ||
1618 | 1561 | errors = [] | ||
1619 | 1562 | for task in self._tasks: | ||
1620 | 1563 | try: | ||
1621 | 1564 | task.verify() | ||
1622 | 1565 | except AssertionError, e: | ||
1623 | 1566 | error = str(e) | ||
1624 | 1567 | if not error: | ||
1625 | 1568 | raise RuntimeError("Empty error message from %r" % task) | ||
1626 | 1569 | errors.append(error) | ||
1627 | 1570 | if errors: | ||
1628 | 1571 | message = [str(self.path)] | ||
1629 | 1572 | for error in errors: | ||
1630 | 1573 | lines = error.splitlines() | ||
1631 | 1574 | message.append("- " + lines.pop(0)) | ||
1632 | 1575 | message.extend([" " + line for line in lines]) | ||
1633 | 1576 | raise AssertionError(os.linesep.join(message)) | ||
1634 | 1577 | |||
1635 | 1578 | def replay(self): | ||
1636 | 1579 | """Put all tasks in replay mode.""" | ||
1637 | 1580 | self._has_run = False | ||
1638 | 1581 | for task in self._tasks: | ||
1639 | 1582 | task.replay() | ||
1640 | 1583 | |||
1641 | 1584 | def restore(self): | ||
1642 | 1585 | """Restore the state of all tasks.""" | ||
1643 | 1586 | for task in self._tasks: | ||
1644 | 1587 | task.restore() | ||
1645 | 1588 | |||
1646 | 1589 | |||
1647 | 1590 | class ReplayRestoreEvent(Event): | ||
1648 | 1591 | """Helper event for tasks which need replay/restore but shouldn't match.""" | ||
1649 | 1592 | |||
1650 | 1593 | def matches(self, path): | ||
1651 | 1594 | return False | ||
1652 | 1595 | |||
1653 | 1596 | |||
1654 | 1597 | class Task(object): | ||
1655 | 1598 | """Element used to track one specific aspect on an event. | ||
1656 | 1599 | |||
1657 | 1600 | A task is responsible for adding any kind of logic to an event. | ||
1658 | 1601 | Examples of that are counting the number of times the event was | ||
1659 | 1602 | made, verifying parameters if any, and so on. | ||
1660 | 1603 | """ | ||
1661 | 1604 | |||
1662 | 1605 | def matches(self, path): | ||
1663 | 1606 | """Return true if the task is supposed to be run for the given path. | ||
1664 | 1607 | """ | ||
1665 | 1608 | return True | ||
1666 | 1609 | |||
1667 | 1610 | def may_run(self, path): | ||
1668 | 1611 | """Return false if running this task would certainly raise an error.""" | ||
1669 | 1612 | return True | ||
1670 | 1613 | |||
1671 | 1614 | def run(self, path): | ||
1672 | 1615 | """Perform the task item, considering that the given action happened. | ||
1673 | 1616 | """ | ||
1674 | 1617 | |||
1675 | 1618 | def verify(self): | ||
1676 | 1619 | """Raise AssertionError if expectations for this item are unmet. | ||
1677 | 1620 | |||
1678 | 1621 | The verify method is supposed to raise an AssertionError if the | ||
1679 | 1622 | task has unmet expectations, with a one-line explanation about | ||
1680 | 1623 | why this item is unmet. This method should be safe to be called | ||
1681 | 1624 | multiple times without side effects. | ||
1682 | 1625 | """ | ||
1683 | 1626 | |||
1684 | 1627 | def replay(self): | ||
1685 | 1628 | """Put the task in replay mode. | ||
1686 | 1629 | |||
1687 | 1630 | Any expectations of the task should be reset. | ||
1688 | 1631 | """ | ||
1689 | 1632 | |||
1690 | 1633 | def restore(self): | ||
1691 | 1634 | """Restore any environmental changes made by the task. | ||
1692 | 1635 | |||
1693 | 1636 | Verify should continue to work after this is called. | ||
1694 | 1637 | """ | ||
1695 | 1638 | |||
1696 | 1639 | |||
1697 | 1640 | # -------------------------------------------------------------------- | ||
1698 | 1641 | # Task implementations. | ||
1699 | 1642 | |||
1700 | 1643 | class OnRestoreCaller(Task): | ||
1701 | 1644 | """Call a given callback when restoring.""" | ||
1702 | 1645 | |||
1703 | 1646 | def __init__(self, callback): | ||
1704 | 1647 | self._callback = callback | ||
1705 | 1648 | |||
1706 | 1649 | def restore(self): | ||
1707 | 1650 | self._callback() | ||
1708 | 1651 | |||
1709 | 1652 | |||
1710 | 1653 | class PathMatcher(Task): | ||
1711 | 1654 | """Match the action path against a given path.""" | ||
1712 | 1655 | |||
1713 | 1656 | def __init__(self, path): | ||
1714 | 1657 | self.path = path | ||
1715 | 1658 | |||
1716 | 1659 | def matches(self, path): | ||
1717 | 1660 | return self.path.matches(path) | ||
1718 | 1661 | |||
1719 | 1662 | def path_matcher_recorder(mocker, event): | ||
1720 | 1663 | event.add_task(PathMatcher(event.path)) | ||
1721 | 1664 | |||
1722 | 1665 | Mocker.add_recorder(path_matcher_recorder) | ||
1723 | 1666 | |||
1724 | 1667 | |||
1725 | 1668 | class RunCounter(Task): | ||
1726 | 1669 | """Task which verifies if the number of runs are within given boundaries. | ||
1727 | 1670 | """ | ||
1728 | 1671 | |||
1729 | 1672 | def __init__(self, min, max=False): | ||
1730 | 1673 | self.min = min | ||
1731 | 1674 | if max is None: | ||
1732 | 1675 | self.max = sys.maxint | ||
1733 | 1676 | elif max is False: | ||
1734 | 1677 | self.max = min | ||
1735 | 1678 | else: | ||
1736 | 1679 | self.max = max | ||
1737 | 1680 | self._runs = 0 | ||
1738 | 1681 | |||
1739 | 1682 | def replay(self): | ||
1740 | 1683 | self._runs = 0 | ||
1741 | 1684 | |||
1742 | 1685 | def may_run(self, path): | ||
1743 | 1686 | return self._runs < self.max | ||
1744 | 1687 | |||
1745 | 1688 | def run(self, path): | ||
1746 | 1689 | self._runs += 1 | ||
1747 | 1690 | if self._runs > self.max: | ||
1748 | 1691 | self.verify() | ||
1749 | 1692 | |||
1750 | 1693 | def verify(self): | ||
1751 | 1694 | if not self.min <= self._runs <= self.max: | ||
1752 | 1695 | if self._runs < self.min: | ||
1753 | 1696 | raise AssertionError("Performed fewer times than expected.") | ||
1754 | 1697 | raise AssertionError("Performed more times than expected.") | ||
1755 | 1698 | |||
1756 | 1699 | |||
1757 | 1700 | class ImplicitRunCounter(RunCounter): | ||
1758 | 1701 | """RunCounter inserted by default on any event. | ||
1759 | 1702 | |||
1760 | 1703 | This is a way to differentiate explicitly added counters and | ||
1761 | 1704 | implicit ones. | ||
1762 | 1705 | """ | ||
1763 | 1706 | |||
1764 | 1707 | def run_counter_recorder(mocker, event): | ||
1765 | 1708 | """Any event may be repeated once, unless disabled by default.""" | ||
1766 | 1709 | if event.path.root_mock.__mocker_count__: | ||
1767 | 1710 | event.add_task(ImplicitRunCounter(1)) | ||
1768 | 1711 | |||
1769 | 1712 | Mocker.add_recorder(run_counter_recorder) | ||
1770 | 1713 | |||
1771 | 1714 | def run_counter_removal_recorder(mocker, event): | ||
1772 | 1715 | """ | ||
1773 | 1716 | Events created by getattr actions which lead to other events | ||
1774 | 1717 | may be repeated any number of times. For that, we remove implicit | ||
1775 | 1718 | run counters of any getattr actions leading to the current one. | ||
1776 | 1719 | """ | ||
1777 | 1720 | parent_path = event.path.parent_path | ||
1778 | 1721 | for event in mocker.get_events()[::-1]: | ||
1779 | 1722 | if (event.path is parent_path and | ||
1780 | 1723 | event.path.actions[-1].kind == "getattr"): | ||
1781 | 1724 | for task in event.get_tasks(): | ||
1782 | 1725 | if type(task) is ImplicitRunCounter: | ||
1783 | 1726 | event.remove_task(task) | ||
1784 | 1727 | |||
1785 | 1728 | Mocker.add_recorder(run_counter_removal_recorder) | ||
1786 | 1729 | |||
1787 | 1730 | |||
1788 | 1731 | class MockReturner(Task): | ||
1789 | 1732 | """Return a mock based on the action path.""" | ||
1790 | 1733 | |||
1791 | 1734 | def __init__(self, mocker): | ||
1792 | 1735 | self.mocker = mocker | ||
1793 | 1736 | |||
1794 | 1737 | def run(self, path): | ||
1795 | 1738 | return Mock(self.mocker, path) | ||
1796 | 1739 | |||
1797 | 1740 | def mock_returner_recorder(mocker, event): | ||
1798 | 1741 | """Events that lead to other events must return mock objects.""" | ||
1799 | 1742 | parent_path = event.path.parent_path | ||
1800 | 1743 | for event in mocker.get_events(): | ||
1801 | 1744 | if event.path is parent_path: | ||
1802 | 1745 | for task in event.get_tasks(): | ||
1803 | 1746 | if isinstance(task, MockReturner): | ||
1804 | 1747 | break | ||
1805 | 1748 | else: | ||
1806 | 1749 | event.add_task(MockReturner(mocker)) | ||
1807 | 1750 | break | ||
1808 | 1751 | |||
1809 | 1752 | Mocker.add_recorder(mock_returner_recorder) | ||
1810 | 1753 | |||
1811 | 1754 | |||
1812 | 1755 | class FunctionRunner(Task): | ||
1813 | 1756 | """Task that runs a function everything it's run. | ||
1814 | 1757 | |||
1815 | 1758 | Arguments of the last action in the path are passed to the function, | ||
1816 | 1759 | and the function result is also returned. | ||
1817 | 1760 | """ | ||
1818 | 1761 | |||
1819 | 1762 | def __init__(self, func): | ||
1820 | 1763 | self._func = func | ||
1821 | 1764 | |||
1822 | 1765 | def run(self, path): | ||
1823 | 1766 | action = path.actions[-1] | ||
1824 | 1767 | return self._func(*action.args, **action.kwargs) | ||
1825 | 1768 | |||
1826 | 1769 | |||
1827 | 1770 | class PathExecuter(Task): | ||
1828 | 1771 | """Task that executes a path in the real object, and returns the result.""" | ||
1829 | 1772 | |||
1830 | 1773 | def __init__(self, result_callback=None): | ||
1831 | 1774 | self._result_callback = result_callback | ||
1832 | 1775 | |||
1833 | 1776 | def get_result_callback(self): | ||
1834 | 1777 | return self._result_callback | ||
1835 | 1778 | |||
1836 | 1779 | def run(self, path): | ||
1837 | 1780 | result = path.execute(path.root_object) | ||
1838 | 1781 | if self._result_callback is not None: | ||
1839 | 1782 | self._result_callback(result) | ||
1840 | 1783 | return result | ||
1841 | 1784 | |||
1842 | 1785 | |||
1843 | 1786 | class Orderer(Task): | ||
1844 | 1787 | """Task to establish an order relation between two events. | ||
1845 | 1788 | |||
1846 | 1789 | An orderer task will only match once all its dependencies have | ||
1847 | 1790 | been run. | ||
1848 | 1791 | """ | ||
1849 | 1792 | |||
1850 | 1793 | def __init__(self, path): | ||
1851 | 1794 | self.path = path | ||
1852 | 1795 | self._run = False | ||
1853 | 1796 | self._dependencies = [] | ||
1854 | 1797 | |||
1855 | 1798 | def replay(self): | ||
1856 | 1799 | self._run = False | ||
1857 | 1800 | |||
1858 | 1801 | def has_run(self): | ||
1859 | 1802 | return self._run | ||
1860 | 1803 | |||
1861 | 1804 | def may_run(self, path): | ||
1862 | 1805 | for dependency in self._dependencies: | ||
1863 | 1806 | if not dependency.has_run(): | ||
1864 | 1807 | return False | ||
1865 | 1808 | return True | ||
1866 | 1809 | |||
1867 | 1810 | def run(self, path): | ||
1868 | 1811 | for dependency in self._dependencies: | ||
1869 | 1812 | if not dependency.has_run(): | ||
1870 | 1813 | raise AssertionError("Should be after: %s" % dependency.path) | ||
1871 | 1814 | self._run = True | ||
1872 | 1815 | |||
1873 | 1816 | def add_dependency(self, orderer): | ||
1874 | 1817 | self._dependencies.append(orderer) | ||
1875 | 1818 | |||
1876 | 1819 | def get_dependencies(self): | ||
1877 | 1820 | return self._dependencies | ||
1878 | 1821 | |||
1879 | 1822 | |||
1880 | 1823 | class SpecChecker(Task): | ||
1881 | 1824 | """Task to check if arguments of the last action conform to a real method. | ||
1882 | 1825 | """ | ||
1883 | 1826 | |||
1884 | 1827 | def __init__(self, method): | ||
1885 | 1828 | self._method = method | ||
1886 | 1829 | self._unsupported = False | ||
1887 | 1830 | |||
1888 | 1831 | if method: | ||
1889 | 1832 | try: | ||
1890 | 1833 | self._args, self._varargs, self._varkwargs, self._defaults = \ | ||
1891 | 1834 | inspect.getargspec(method) | ||
1892 | 1835 | except TypeError: | ||
1893 | 1836 | self._unsupported = True | ||
1894 | 1837 | else: | ||
1895 | 1838 | if self._defaults is None: | ||
1896 | 1839 | self._defaults = () | ||
1897 | 1840 | if type(method) is type(self.run): | ||
1898 | 1841 | self._args = self._args[1:] | ||
1899 | 1842 | |||
1900 | 1843 | def get_method(self): | ||
1901 | 1844 | return self._method | ||
1902 | 1845 | |||
1903 | 1846 | def _raise(self, message): | ||
1904 | 1847 | spec = inspect.formatargspec(self._args, self._varargs, | ||
1905 | 1848 | self._varkwargs, self._defaults) | ||
1906 | 1849 | raise AssertionError("Specification is %s%s: %s" % | ||
1907 | 1850 | (self._method.__name__, spec, message)) | ||
1908 | 1851 | |||
1909 | 1852 | def verify(self): | ||
1910 | 1853 | if not self._method: | ||
1911 | 1854 | raise AssertionError("Method not found in real specification") | ||
1912 | 1855 | |||
1913 | 1856 | def may_run(self, path): | ||
1914 | 1857 | try: | ||
1915 | 1858 | self.run(path) | ||
1916 | 1859 | except AssertionError: | ||
1917 | 1860 | return False | ||
1918 | 1861 | return True | ||
1919 | 1862 | |||
1920 | 1863 | def run(self, path): | ||
1921 | 1864 | if not self._method: | ||
1922 | 1865 | raise AssertionError("Method not found in real specification") | ||
1923 | 1866 | if self._unsupported: | ||
1924 | 1867 | return # Can't check it. Happens with builtin functions. :-( | ||
1925 | 1868 | action = path.actions[-1] | ||
1926 | 1869 | obtained_len = len(action.args) | ||
1927 | 1870 | obtained_kwargs = action.kwargs.copy() | ||
1928 | 1871 | nodefaults_len = len(self._args) - len(self._defaults) | ||
1929 | 1872 | for i, name in enumerate(self._args): | ||
1930 | 1873 | if i < obtained_len and name in action.kwargs: | ||
1931 | 1874 | self._raise("%r provided twice" % name) | ||
1932 | 1875 | if (i >= obtained_len and i < nodefaults_len and | ||
1933 | 1876 | name not in action.kwargs): | ||
1934 | 1877 | self._raise("%r not provided" % name) | ||
1935 | 1878 | obtained_kwargs.pop(name, None) | ||
1936 | 1879 | if obtained_len > len(self._args) and not self._varargs: | ||
1937 | 1880 | self._raise("too many args provided") | ||
1938 | 1881 | if obtained_kwargs and not self._varkwargs: | ||
1939 | 1882 | self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs)) | ||
1940 | 1883 | |||
1941 | 1884 | def spec_checker_recorder(mocker, event): | ||
1942 | 1885 | spec = event.path.root_mock.__mocker_spec__ | ||
1943 | 1886 | if spec: | ||
1944 | 1887 | actions = event.path.actions | ||
1945 | 1888 | if len(actions) == 1: | ||
1946 | 1889 | if actions[0].kind == "call": | ||
1947 | 1890 | method = getattr(spec, "__call__", None) | ||
1948 | 1891 | event.add_task(SpecChecker(method)) | ||
1949 | 1892 | elif len(actions) == 2: | ||
1950 | 1893 | if actions[0].kind == "getattr" and actions[1].kind == "call": | ||
1951 | 1894 | method = getattr(spec, actions[0].args[0], None) | ||
1952 | 1895 | event.add_task(SpecChecker(method)) | ||
1953 | 1896 | |||
1954 | 1897 | Mocker.add_recorder(spec_checker_recorder) | ||
1955 | 1898 | |||
1956 | 1899 | |||
1957 | 1900 | class ProxyReplacer(Task): | ||
1958 | 1901 | """Task which installs and deinstalls proxy mocks. | ||
1959 | 1902 | |||
1960 | 1903 | This task will replace a real object by a mock in all dictionaries | ||
1961 | 1904 | found in the running interpreter via the garbage collecting system. | ||
1962 | 1905 | """ | ||
1963 | 1906 | |||
1964 | 1907 | def __init__(self, mock): | ||
1965 | 1908 | self.mock = mock | ||
1966 | 1909 | self.__mocker_replace__ = False | ||
1967 | 1910 | |||
1968 | 1911 | def replay(self): | ||
1969 | 1912 | global_replace(self.mock.__mocker_object__, self.mock) | ||
1970 | 1913 | |||
1971 | 1914 | def restore(self): | ||
1972 | 1915 | global_replace(self.mock, self.mock.__mocker_object__) | ||
1973 | 1916 | |||
1974 | 1917 | |||
1975 | 1918 | def global_replace(remove, install): | ||
1976 | 1919 | """Replace object 'remove' with object 'install' on all dictionaries.""" | ||
1977 | 1920 | for referrer in gc.get_referrers(remove): | ||
1978 | 1921 | if (type(referrer) is dict and | ||
1979 | 1922 | referrer.get("__mocker_replace__", True)): | ||
1980 | 1923 | for key, value in referrer.items(): | ||
1981 | 1924 | if value is remove: | ||
1982 | 1925 | referrer[key] = install | ||
1983 | 1926 | |||
1984 | 1927 | |||
1985 | 1928 | class Undefined(object): | ||
1986 | 1929 | |||
1987 | 1930 | def __repr__(self): | ||
1988 | 1931 | return "Undefined" | ||
1989 | 1932 | |||
1990 | 1933 | Undefined = Undefined() | ||
1991 | 1934 | |||
1992 | 1935 | |||
1993 | 1936 | class Patcher(Task): | ||
1994 | 1937 | |||
1995 | 1938 | def __init__(self): | ||
1996 | 1939 | super(Patcher, self).__init__() | ||
1997 | 1940 | self._monitored = {} # {kind: {id(object): object}} | ||
1998 | 1941 | self._patched = {} | ||
1999 | 1942 | |||
2000 | 1943 | def is_monitoring(self, obj, kind): | ||
2001 | 1944 | monitored = self._monitored.get(kind) | ||
2002 | 1945 | if monitored: | ||
2003 | 1946 | if id(obj) in monitored: | ||
2004 | 1947 | return True | ||
2005 | 1948 | cls = type(obj) | ||
2006 | 1949 | if issubclass(cls, type): | ||
2007 | 1950 | cls = obj | ||
2008 | 1951 | bases = set([id(base) for base in cls.__mro__]) | ||
2009 | 1952 | bases.intersection_update(monitored) | ||
2010 | 1953 | return bool(bases) | ||
2011 | 1954 | return False | ||
2012 | 1955 | |||
2013 | 1956 | def monitor(self, obj, kind): | ||
2014 | 1957 | if kind not in self._monitored: | ||
2015 | 1958 | self._monitored[kind] = {} | ||
2016 | 1959 | self._monitored[kind][id(obj)] = obj | ||
2017 | 1960 | |||
2018 | 1961 | def patch_attr(self, obj, attr, value): | ||
2019 | 1962 | original = obj.__dict__.get(attr, Undefined) | ||
2020 | 1963 | self._patched[id(obj), attr] = obj, attr, original | ||
2021 | 1964 | setattr(obj, attr, value) | ||
2022 | 1965 | |||
2023 | 1966 | def get_unpatched_attr(self, obj, attr): | ||
2024 | 1967 | cls = type(obj) | ||
2025 | 1968 | if issubclass(cls, type): | ||
2026 | 1969 | cls = obj | ||
2027 | 1970 | result = Undefined | ||
2028 | 1971 | for mro_cls in cls.__mro__: | ||
2029 | 1972 | key = (id(mro_cls), attr) | ||
2030 | 1973 | if key in self._patched: | ||
2031 | 1974 | result = self._patched[key][2] | ||
2032 | 1975 | if result is not Undefined: | ||
2033 | 1976 | break | ||
2034 | 1977 | elif attr in mro_cls.__dict__: | ||
2035 | 1978 | result = mro_cls.__dict__.get(attr, Undefined) | ||
2036 | 1979 | break | ||
2037 | 1980 | if isinstance(result, object) and hasattr(type(result), "__get__"): | ||
2038 | 1981 | if cls is obj: | ||
2039 | 1982 | obj = None | ||
2040 | 1983 | return result.__get__(obj, cls) | ||
2041 | 1984 | return result | ||
2042 | 1985 | |||
2043 | 1986 | def _get_kind_attr(self, kind): | ||
2044 | 1987 | if kind == "getattr": | ||
2045 | 1988 | return "__getattribute__" | ||
2046 | 1989 | return "__%s__" % kind | ||
2047 | 1990 | |||
2048 | 1991 | def replay(self): | ||
2049 | 1992 | for kind in self._monitored: | ||
2050 | 1993 | attr = self._get_kind_attr(kind) | ||
2051 | 1994 | seen = set() | ||
2052 | 1995 | for obj in self._monitored[kind].itervalues(): | ||
2053 | 1996 | cls = type(obj) | ||
2054 | 1997 | if issubclass(cls, type): | ||
2055 | 1998 | cls = obj | ||
2056 | 1999 | if cls not in seen: | ||
2057 | 2000 | seen.add(cls) | ||
2058 | 2001 | unpatched = getattr(cls, attr, Undefined) | ||
2059 | 2002 | self.patch_attr(cls, attr, | ||
2060 | 2003 | PatchedMethod(kind, unpatched, | ||
2061 | 2004 | self.is_monitoring)) | ||
2062 | 2005 | self.patch_attr(cls, "__mocker_execute__", | ||
2063 | 2006 | self.execute) | ||
2064 | 2007 | |||
2065 | 2008 | def restore(self): | ||
2066 | 2009 | for obj, attr, original in self._patched.itervalues(): | ||
2067 | 2010 | if original is Undefined: | ||
2068 | 2011 | delattr(obj, attr) | ||
2069 | 2012 | else: | ||
2070 | 2013 | setattr(obj, attr, original) | ||
2071 | 2014 | self._patched.clear() | ||
2072 | 2015 | |||
2073 | 2016 | def execute(self, action, object): | ||
2074 | 2017 | attr = self._get_kind_attr(action.kind) | ||
2075 | 2018 | unpatched = self.get_unpatched_attr(object, attr) | ||
2076 | 2019 | try: | ||
2077 | 2020 | return unpatched(*action.args, **action.kwargs) | ||
2078 | 2021 | except AttributeError: | ||
2079 | 2022 | if action.kind == "getattr": | ||
2080 | 2023 | # The normal behavior of Python is to try __getattribute__, | ||
2081 | 2024 | # and if it raises AttributeError, try __getattr__. We've | ||
2082 | 2025 | # tried the unpatched __getattribute__ above, and we'll now | ||
2083 | 2026 | # try __getattr__. | ||
2084 | 2027 | try: | ||
2085 | 2028 | __getattr__ = unpatched("__getattr__") | ||
2086 | 2029 | except AttributeError: | ||
2087 | 2030 | pass | ||
2088 | 2031 | else: | ||
2089 | 2032 | return __getattr__(*action.args, **action.kwargs) | ||
2090 | 2033 | raise | ||
2091 | 2034 | |||
2092 | 2035 | |||
2093 | 2036 | class PatchedMethod(object): | ||
2094 | 2037 | |||
2095 | 2038 | def __init__(self, kind, unpatched, is_monitoring): | ||
2096 | 2039 | self._kind = kind | ||
2097 | 2040 | self._unpatched = unpatched | ||
2098 | 2041 | self._is_monitoring = is_monitoring | ||
2099 | 2042 | |||
2100 | 2043 | def __get__(self, obj, cls=None): | ||
2101 | 2044 | object = obj or cls | ||
2102 | 2045 | if not self._is_monitoring(object, self._kind): | ||
2103 | 2046 | return self._unpatched.__get__(obj, cls) | ||
2104 | 2047 | def method(*args, **kwargs): | ||
2105 | 2048 | if self._kind == "getattr" and args[0].startswith("__mocker_"): | ||
2106 | 2049 | return self._unpatched.__get__(obj, cls)(args[0]) | ||
2107 | 2050 | mock = object.__mocker_mock__ | ||
2108 | 2051 | return mock.__mocker_act__(self._kind, args, kwargs, object) | ||
2109 | 2052 | return method | ||
2110 | 2053 | |||
2111 | 2054 | def __call__(self, obj, *args, **kwargs): | ||
2112 | 2055 | # At least with __getattribute__, Python seems to use *both* the | ||
2113 | 2056 | # descriptor API and also call the class attribute directly. It | ||
2114 | 2057 | # looks like an interpreter bug, or at least an undocumented | ||
2115 | 2058 | # inconsistency. | ||
2116 | 2059 | return self.__get__(obj)(*args, **kwargs) | ||
2117 | 2060 | |||
2118 | 2061 | |||
2119 | 2062 | def patcher_recorder(mocker, event): | ||
2120 | 2063 | mock = event.path.root_mock | ||
2121 | 2064 | if mock.__mocker_patcher__ and len(event.path.actions) == 1: | ||
2122 | 2065 | patcher = mock.__mocker_patcher__ | ||
2123 | 2066 | patcher.monitor(mock.__mocker_object__, event.path.actions[0].kind) | ||
2124 | 2067 | |||
2125 | 2068 | Mocker.add_recorder(patcher_recorder) | ||
2126 | 0 | 2069 | ||
2127 | === modified file 'desktopcouch/__init__.py' | |||
2128 | --- desktopcouch/__init__.py 2009-08-04 11:15:52 +0000 | |||
2129 | +++ desktopcouch/__init__.py 2009-08-07 12:31:27 +0000 | |||
2130 | @@ -16,14 +16,12 @@ | |||
2131 | 16 | "Desktop Couch helper files" | 16 | "Desktop Couch helper files" |
2132 | 17 | 17 | ||
2133 | 18 | from __future__ import with_statement | 18 | from __future__ import with_statement |
2138 | 19 | import os | 19 | import os, re, errno, time, fcntl |
2139 | 20 | import re | 20 | |
2136 | 21 | import errno | ||
2137 | 22 | import time | ||
2140 | 23 | 21 | ||
2141 | 24 | def find_pid(): | 22 | def find_pid(): |
2142 | 25 | # Work out whether CouchDB is running by looking at its pid file | ||
2143 | 26 | from desktopcouch import local_files | 23 | from desktopcouch import local_files |
2144 | 24 | |||
2145 | 27 | pid = '' | 25 | pid = '' |
2146 | 28 | try: | 26 | try: |
2147 | 29 | fp = open(local_files.FILE_PID) | 27 | fp = open(local_files.FILE_PID) |
2148 | @@ -39,6 +37,8 @@ | |||
2149 | 39 | from desktopcouch import start_local_couchdb | 37 | from desktopcouch import start_local_couchdb |
2150 | 40 | start_local_couchdb.start_couchdb() | 38 | start_local_couchdb.start_couchdb() |
2151 | 41 | time.sleep(2) # give the process a chance to start | 39 | time.sleep(2) # give the process a chance to start |
2152 | 40 | # now load the design documents, because it's started | ||
2153 | 41 | start_local_couchdb.update_design_documents() | ||
2154 | 42 | 42 | ||
2155 | 43 | # get the pid | 43 | # get the pid |
2156 | 44 | try: | 44 | try: |
2157 | 45 | 45 | ||
2158 | === modified file 'desktopcouch/start_local_couchdb.py' | |||
2159 | --- desktopcouch/start_local_couchdb.py 2009-07-27 18:16:19 +0000 | |||
2160 | +++ desktopcouch/start_local_couchdb.py 2009-08-07 12:33:43 +0000 | |||
2161 | @@ -32,11 +32,12 @@ | |||
2162 | 32 | """ | 32 | """ |
2163 | 33 | 33 | ||
2164 | 34 | from __future__ import with_statement | 34 | from __future__ import with_statement |
2166 | 35 | import os, subprocess, sys | 35 | import os, subprocess, sys, glob |
2167 | 36 | import desktopcouch | 36 | import desktopcouch |
2168 | 37 | from desktopcouch import local_files | 37 | from desktopcouch import local_files |
2169 | 38 | import xdg.BaseDirectory | 38 | import xdg.BaseDirectory |
2170 | 39 | import time | 39 | import time |
2171 | 40 | from desktopcouch.records.server import CouchDatabase | ||
2172 | 40 | 41 | ||
2173 | 41 | def dump_ini(data, filename): | 42 | def dump_ini(data, filename): |
2174 | 42 | """Dump INI data with sorted sections and keywords""" | 43 | """Dump INI data with sorted sections and keywords""" |
2175 | @@ -99,8 +100,43 @@ | |||
2176 | 99 | exit(1) | 100 | exit(1) |
2177 | 100 | 101 | ||
2178 | 101 | def update_design_documents(): | 102 | def update_design_documents(): |
2181 | 102 | """Check system design documents and update any that need updating""" | 103 | """Check system design documents and update any that need updating |
2182 | 103 | pass | 104 | |
2183 | 105 | A database should be created if | ||
2184 | 106 | $XDG_DATA_DIRs/desktop-couch/databases/dbname/database.cfg exists | ||
2185 | 107 | Design docs are defined by the existence of | ||
2186 | 108 | $XDG_DATA_DIRs/desktop-couch/databases/dbname/_design/designdocname/views/viewname/map.js | ||
2187 | 109 | reduce.js may also exist in the same folder. | ||
2188 | 110 | """ | ||
2189 | 111 | for base in xdg.BaseDirectory.xdg_data_dirs: | ||
2190 | 112 | db_spec = os.path.join(base, "desktop-couch", "databases", "*", "database.cfg") | ||
2191 | 113 | for database_path in glob.glob(db_spec): | ||
2192 | 114 | database_root = os.path.split(database_path)[0] | ||
2193 | 115 | database_name = os.path.split(database_root)[1] | ||
2194 | 116 | # Just the presence of database.cfg is enough to create the database | ||
2195 | 117 | db = CouchDatabase(database_name, create=True) | ||
2196 | 118 | # look for design documents | ||
2197 | 119 | dd_spec = os.path.join(database_root, "_design", "*", "views", "*", "map.js") | ||
2198 | 120 | for dd_path in glob.glob(dd_spec): | ||
2199 | 121 | view_root = os.path.split(dd_path)[0] | ||
2200 | 122 | view_name = os.path.split(view_root)[1] | ||
2201 | 123 | dd_root = os.path.split(os.path.split(view_root)[0])[0] | ||
2202 | 124 | dd_name = os.path.split(dd_root)[1] | ||
2203 | 125 | |||
2204 | 126 | def load_js_file(filename_no_extension): | ||
2205 | 127 | fn = os.path.join(view_root, "%s.js" % (filename_no_extension)) | ||
2206 | 128 | if not os.path.isfile(fn): return None | ||
2207 | 129 | fp = open(fn) | ||
2208 | 130 | data = fp.read() | ||
2209 | 131 | fp.close() | ||
2210 | 132 | return data | ||
2211 | 133 | |||
2212 | 134 | mapjs = load_js_file("map") | ||
2213 | 135 | reducejs = load_js_file("reduce") | ||
2214 | 136 | |||
2215 | 137 | # XXX check whether this already exists or not, rather | ||
2216 | 138 | # than inefficiently just overwriting it regardless | ||
2217 | 139 | db.add_view(view_name, mapjs, reducejs, dd_name) | ||
2218 | 104 | 140 | ||
2219 | 105 | def write_bookmark_file(): | 141 | def write_bookmark_file(): |
2220 | 106 | """Write out an HTML document that the user can bookmark to find their DB""" | 142 | """Write out an HTML document that the user can bookmark to find their DB""" |
2221 | @@ -132,7 +168,11 @@ | |||
2222 | 132 | """Execute each step to start a desktop CouchDB""" | 168 | """Execute each step to start a desktop CouchDB""" |
2223 | 133 | create_ini_file() | 169 | create_ini_file() |
2224 | 134 | run_couchdb() | 170 | run_couchdb() |
2226 | 135 | update_design_documents() | 171 | # Note that we do not call update_design_documents here. This is because |
2227 | 172 | # Couch won't actually have started yet, so when update_design_documents | ||
2228 | 173 | # calls the Records API, that will call back into get_pid and we end up | ||
2229 | 174 | # starting Couch again. Instead, get_pid calls update_design_documents | ||
2230 | 175 | # *after* Couch startup has occurred. | ||
2231 | 136 | write_bookmark_file() | 176 | write_bookmark_file() |
2232 | 137 | 177 | ||
2233 | 138 | if __name__ == "__main__": | 178 | if __name__ == "__main__": |
2234 | 139 | 179 | ||
2235 | === added file 'desktopcouch/tests/test_start_local_couchdb.py' | |||
2236 | --- desktopcouch/tests/test_start_local_couchdb.py 1970-01-01 00:00:00 +0000 | |||
2237 | +++ desktopcouch/tests/test_start_local_couchdb.py 2009-08-06 13:57:30 +0000 | |||
2238 | @@ -0,0 +1,134 @@ | |||
2239 | 1 | """testing desktopcouch/local_files.py module""" | ||
2240 | 2 | |||
2241 | 3 | from twisted.trial.unittest import TestCase as TwistedTestCase | ||
2242 | 4 | import os, tempfile, sys | ||
2243 | 5 | import desktopcouch | ||
2244 | 6 | sys.path.append(os.path.join(os.path.split(desktopcouch.__file__)[0], "..", "contrib")) | ||
2245 | 7 | from mocker import Mocker | ||
2246 | 8 | |||
2247 | 9 | class TestUpdateDesignDocuments(TwistedTestCase): | ||
2248 | 10 | """Testing that the database/designdoc filesystem loader works""" | ||
2249 | 11 | |||
2250 | 12 | def setUp(self): | ||
2251 | 13 | # create temp folder with databases and design documents in | ||
2252 | 14 | dc_root = os.path.split(desktopcouch.__file__)[0] | ||
2253 | 15 | branch_root = os.path.split(dc_root)[0] | ||
2254 | 16 | self.tmpdir = tempfile.mkdtemp(dir=branch_root) | ||
2255 | 17 | self.basedir = os.path.join(self.tmpdir, "desktop-couch") | ||
2256 | 18 | os.mkdir(self.basedir) | ||
2257 | 19 | DIRS = [ | ||
2258 | 20 | "databases", | ||
2259 | 21 | "databases/nocfg", | ||
2260 | 22 | "databases/cfg", | ||
2261 | 23 | "databases/cfg_and_empty_design", | ||
2262 | 24 | "databases/cfg_and_empty_design/_design", | ||
2263 | 25 | "databases/cfg_and_design_no_views", | ||
2264 | 26 | "databases/cfg_and_design_no_views/_design", | ||
2265 | 27 | "databases/cfg_and_design_no_views/_design/doc1", | ||
2266 | 28 | "databases/cfg_and_design_no_views/_design/doc1/views", | ||
2267 | 29 | "databases/cfg_and_design_one_view_no_map", | ||
2268 | 30 | "databases/cfg_and_design_one_view_no_map/_design", | ||
2269 | 31 | "databases/cfg_and_design_one_view_no_map/_design/doc1", | ||
2270 | 32 | "databases/cfg_and_design_one_view_no_map/_design/doc1/views", | ||
2271 | 33 | "databases/cfg_and_design_one_view_no_map/_design/doc1/views/view1", | ||
2272 | 34 | "databases/cfg_and_design_one_view_map_no_reduce", | ||
2273 | 35 | "databases/cfg_and_design_one_view_map_no_reduce/_design", | ||
2274 | 36 | "databases/cfg_and_design_one_view_map_no_reduce/_design/doc1", | ||
2275 | 37 | "databases/cfg_and_design_one_view_map_no_reduce/_design/doc1/views", | ||
2276 | 38 | "databases/cfg_and_design_one_view_map_no_reduce/_design/doc1/views/view1", | ||
2277 | 39 | "databases/cfg_and_design_one_view_map_reduce", | ||
2278 | 40 | "databases/cfg_and_design_one_view_map_reduce/_design", | ||
2279 | 41 | "databases/cfg_and_design_one_view_map_reduce/_design/doc1", | ||
2280 | 42 | "databases/cfg_and_design_one_view_map_reduce/_design/doc1/views", | ||
2281 | 43 | "databases/cfg_and_design_one_view_map_reduce/_design/doc1/views/view1", | ||
2282 | 44 | "databases/cfg_and_design_two_views_map_reduce", | ||
2283 | 45 | "databases/cfg_and_design_two_views_map_reduce/_design", | ||
2284 | 46 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1", | ||
2285 | 47 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views", | ||
2286 | 48 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views/view1", | ||
2287 | 49 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views/view2", | ||
2288 | 50 | ] | ||
2289 | 51 | FILES = { | ||
2290 | 52 | "databases/cfg/database.cfg": "", | ||
2291 | 53 | "databases/cfg_and_empty_design/database.cfg": "", | ||
2292 | 54 | "databases/cfg_and_design_no_views/database.cfg": "", | ||
2293 | 55 | "databases/cfg_and_design_one_view_no_map/database.cfg": "", | ||
2294 | 56 | "databases/cfg_and_design_one_view_map_no_reduce/database.cfg": "", | ||
2295 | 57 | "databases/cfg_and_design_one_view_map_no_reduce/_design/doc1/views/view1/map.js": | ||
2296 | 58 | "cfg_and_design_one_view_map_no_reduce:map", | ||
2297 | 59 | "databases/cfg_and_design_one_view_map_reduce/database.cfg": "", | ||
2298 | 60 | "databases/cfg_and_design_one_view_map_reduce/_design/doc1/views/view1/map.js": | ||
2299 | 61 | "cfg_and_design_one_view_map_reduce:map", | ||
2300 | 62 | "databases/cfg_and_design_one_view_map_reduce/_design/doc1/views/view1/reduce.js": | ||
2301 | 63 | "cfg_and_design_one_view_map_reduce:reduce", | ||
2302 | 64 | |||
2303 | 65 | "databases/cfg_and_design_two_views_map_reduce/database.cfg": "", | ||
2304 | 66 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views/view1/map.js": | ||
2305 | 67 | "cfg_and_design_two_views_map_reduce:map1", | ||
2306 | 68 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views/view1/reduce.js": | ||
2307 | 69 | "cfg_and_design_two_views_map_reduce:reduce1", | ||
2308 | 70 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views/view2/map.js": | ||
2309 | 71 | "cfg_and_design_two_views_map_reduce:map2", | ||
2310 | 72 | "databases/cfg_and_design_two_views_map_reduce/_design/doc1/views/view2/reduce.js": | ||
2311 | 73 | "cfg_and_design_two_views_map_reduce:reduce2", | ||
2312 | 74 | } | ||
2313 | 75 | for d in DIRS: | ||
2314 | 76 | os.mkdir(os.path.join(self.basedir, d)) | ||
2315 | 77 | for f, data in FILES.items(): | ||
2316 | 78 | fp = open(os.path.join(self.basedir, f), "w") | ||
2317 | 79 | fp.write(data) | ||
2318 | 80 | fp.close() | ||
2319 | 81 | |||
2320 | 82 | def tearDown(self): | ||
2321 | 83 | # delete temp folder | ||
2322 | 84 | for root, dirs, files in os.walk(self.tmpdir, topdown=False): | ||
2323 | 85 | for name in files: | ||
2324 | 86 | os.remove(os.path.join(root, name)) | ||
2325 | 87 | for name in dirs: | ||
2326 | 88 | os.rmdir(os.path.join(root, name)) | ||
2327 | 89 | os.rmdir(self.tmpdir) | ||
2328 | 90 | |||
2329 | 91 | def test_create_databases_and_design_docs(self): | ||
2330 | 92 | """Are databases and design documents correctly | ||
2331 | 93 | created from the filesystem?""" | ||
2332 | 94 | # poke local_files so that it returns our temp folder | ||
2333 | 95 | os.environ['XDG_DATA_HOME'] = self.tmpdir | ||
2334 | 96 | os.environ['XDG_DATA_DIRS'] = '' | ||
2335 | 97 | |||
2336 | 98 | # Mock CouchDatabase | ||
2337 | 99 | mocker = Mocker() | ||
2338 | 100 | couchdb = mocker.replace("desktopcouch.records.server.CouchDatabase") | ||
2339 | 101 | |||
2340 | 102 | # databases that should be created | ||
2341 | 103 | couchdb("cfg", create=True) | ||
2342 | 104 | couchdb("cfg_and_empty_design", create=True) | ||
2343 | 105 | couchdb("cfg_and_design_no_views", create=True) | ||
2344 | 106 | couchdb("cfg_and_design_one_view_no_map", create=True) | ||
2345 | 107 | couchdb("cfg_and_design_one_view_map_no_reduce", create=True) | ||
2346 | 108 | dbmock1 = mocker.mock() | ||
2347 | 109 | mocker.result(dbmock1) | ||
2348 | 110 | dbmock1.add_view("view1", "cfg_and_design_one_view_map_no_reduce:map", | ||
2349 | 111 | None, "doc1") | ||
2350 | 112 | couchdb("cfg_and_design_one_view_map_reduce", create=True) | ||
2351 | 113 | dbmock2 = mocker.mock() | ||
2352 | 114 | mocker.result(dbmock2) | ||
2353 | 115 | dbmock2.add_view("view1", "cfg_and_design_one_view_map_reduce:map", | ||
2354 | 116 | "cfg_and_design_one_view_map_reduce:reduce", "doc1") | ||
2355 | 117 | couchdb("cfg_and_design_two_views_map_reduce", create=True) | ||
2356 | 118 | dbmock3 = mocker.mock() | ||
2357 | 119 | mocker.result(dbmock3) | ||
2358 | 120 | dbmock3.add_view("view1", "cfg_and_design_two_views_map_reduce:map1", | ||
2359 | 121 | "cfg_and_design_two_views_map_reduce:reduce1", "doc1") | ||
2360 | 122 | dbmock3.add_view("view2", "cfg_and_design_two_views_map_reduce:map2", | ||
2361 | 123 | "cfg_and_design_two_views_map_reduce:reduce2", "doc1") | ||
2362 | 124 | |||
2363 | 125 | # actually call update_design_documents to confirm that it creates | ||
2364 | 126 | # all the right things | ||
2365 | 127 | mocker.replay() | ||
2366 | 128 | from desktopcouch.start_local_couchdb import update_design_documents | ||
2367 | 129 | update_design_documents() | ||
2368 | 130 | |||
2369 | 131 | mocker.restore() | ||
2370 | 132 | mocker.verify() | ||
2371 | 133 | |||
2372 | 134 |
I am hijacking https:/ /edge.launchpad .net/~sil/ desktopcouch/ dc-load- design- docs/+merge/ 9822 to get it landed. The only change is to merge from trunk and add the recursive-include in MANIFEST.in that Rodney asked for in his review of the other branch.