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