Merge lp:~statik/desktopcouch/dc-load-design-docs into lp:desktopcouch

Proposed by Elliot Murphy
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
Reviewer Review Type Date Requested Status
Eric Casteleijn (community) Needs Fixing
Review via email: mp+9951@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Elliot Murphy (statik) wrote :

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.

Revision history for this message
Eric Casteleijn (thisfred) wrote :
Download full text (5.1 KiB)

I get these errors:

===============================================================================
[ERROR]: desktopcouch.records.tests.test_field_registry.TestFieldMapping.test_mergeable_list_field_mapping

Traceback (most recent call last):
Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
<DelayedCall 168000268 [0.995854854584s] called=0 cancelled=0 LoopingCall<1.0>(exit_on_success, *(), **{})()>
<DelayedCall 168000044 [1.56556606293s] called=0 cancelled=0 reconnector()>
<DelayedCall 167997580 [28.9958992004s] called=0 cancelled=0 exit_on_timeout()>
===============================================================================
[ERROR]: desktopcouch.records.tests.test_server.TestCouchDatabase.test_delete_record

Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/testtools/testcase.py", line 161, in run
    self.setUp()
  File "/home/eric/canonical/desktopcouch/r-statik-sil/desktopcouch/records/tests/test_server.py", line 42, in setUp
    self.database = CouchDatabase(self.dbname, create=True)
  File "/home/eric/canonical/desktopcouch/r-statik-sil/desktopcouch/records/server.py", line 58, in __init__
    if database not in self._server:
  File "/usr/lib/pymodules/python2.6/couchdb/client.py", line 120, in __contains__
    self.resource.head(validate_dbname(name))
  File "/usr/lib/pymodules/python2.6/couchdb/client.py", line 977, in head
    return self._request('HEAD', path, headers=headers, **params)
  File "/usr/lib/pymodules/python2.6/couchdb/client.py", line 1010, in _request
    resp, data = _make_request()
  File "/usr/lib/pymodules/python2.6/couchdb/client.py", line 1005, in _make_request
    body=body, headers=headers)
  File "/usr/lib/pymodules/python2.6/httplib2/__init__.py", line 1068, in request
    (response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
  File "/usr/lib/pymodules/python2.6/httplib2/__init__.py", line 872, in _request
    (response, content) = self._conn_request(conn, request_uri, method, body, headers)
  File "/usr/lib/pymodules/python2.6/httplib2/__init__.py", line 842, in _conn_request
    response = conn.getresponse()
  File "/usr/lib/python2.6/httplib.py", line 950, in getresponse
    response.begin()
  File "/usr/lib/python2.6/httplib.py", line 390, in begin
    version, status, reason = self._read_status()
  File "/usr/lib/python2.6/httplib.py", line 348, in _read_status
    line = self.fp.readline()
  File "/usr/lib/python2.6/socket.py", line 395, in readline
    data = recv(1)
socket.error: [Errno 4] Interrupted system call
===============================================================================
[ERROR]: desktopcouch.tests.test_start_local_couchdb.TestUpdateDesignDocuments.test_create_databases_and_design_docs

Traceback (most recent call last):
  File "/home/eric/canonical/desktopcouch/r-statik-sil/desktopcouch/tests/test_start_local_couchdb.py", line 132, in test_create_databases_and_design_docs
    mocker.verify()
  File "/home/eric/canonical/desktopcouch/r-statik-sil/desktopcouch/../contrib/mocker.py", li...

Read more...

review: Needs Fixing

Unmerged revisions

35. By Elliot Murphy

include contrib/*.py in the generated release tarball.

34. By Elliot Murphy

Merged from trunk.

Preview Diff

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

Subscribers

People subscribed via source and target branches