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
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+

Subscribers

People subscribed via source and target branches