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

Proposed by Stuart Langridge
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 37
Merged at revision: not available
Proposed branch: lp:~sil/desktopcouch/dc-load-design-docs
Merge into: lp:desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp:~sil/desktopcouch/dc-load-design-docs
Reviewer Review Type Date Requested Status
Elliot Murphy (community) Approve
Eric Casteleijn (community) Approve
dobey (community) Approve
John O'Brien (community) Abstain
Review via email: mp+9822@code.launchpad.net

Commit message

Load design documents on startup. Documentation for how to place a design document on the filesystem.

To post a comment you must log in.
Revision history for this message
Stuart Langridge (sil) wrote :

Load design documents on startup. Documentation for how to place a design document on the filesystem.

Revision history for this message
dobey (dobey) wrote :

You need to add the following to MANIFEST.in:

recursive-include contrib *.py

review: Needs Fixing
Revision history for this message
Stuart Langridge (sil) wrote :

> You need to add the following to MANIFEST.in:
>
> recursive-include contrib *.py

I am told that I don't need to; the mocker is there for the test suite and doesn't need to be in the final package.

Revision history for this message
John O'Brien (jdobrien) wrote :

I am abstaining from reviewing this as I can't run any tests on desktopcouch. As an FYI (and no relation to the code from this branch) the code is full of PEP 8 violations.

review: Abstain
Revision history for this message
dobey (dobey) wrote :

> > You need to add the following to MANIFEST.in:
> >
> > recursive-include contrib *.py
>
> I am told that I don't need to; the mocker is there for the test suite and
> doesn't need to be in the final package.

Who told you that? It's true that contrib/ doesn't need to be in the binary packages in debian/ubuntu/redhat/whatever, but it does need to be in the source tarball, so that the tests are runnable. We shouldn't ship tarballs that fail to run the tests, because we decided not to put a contrib python file in there.

Revision history for this message
Elliot Murphy (statik) wrote :

I probably caused the confusion. Yes, the file needs to be in the tarball, but not in the final binary packages.

Revision history for this message
Elliot Murphy (statik) wrote :
Download full text (4.1 KiB)

===============================================================================
[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", line 503, in verify
    raise AssertionError(os.linesep.join(message))
exceptions.AssertionError: [Mocker] Unmet expectations:

=> desktopcouch.records.server.CouchDatabase('cfg', create=True)
 - Performed fewer times than expected.

=> desktopcouch.records.server.CouchDatabase('cfg_and_empty_design', create=True)
 - Performed fewer times than expected.

=> desktopcouch.records.server.CouchDatabase('cfg_and_design_no_views', create=True)
 - Performed fewer times than expected.

=> desktopcouch.records.server.CouchDatabase('cfg_and_design_one_view_no_map', create=True)
 - Performed fewer times than expected.

=> desktopcouch.records.server.CouchDatabase('cfg...

Read more...

review: Needs Fixing
Revision history for this message
Stuart Langridge (sil) wrote :

> 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", line 503, in verify
> raise AssertionError(os.linesep.join(message))
> exceptions.AssertionError: [Mocker] Unmet expectations:
>
> => desktopcouch.records.server.CouchDatabase('cfg', create=True)
> - Performed fewer times than expected.

Now fixed; I needed to reimport xdg.BaseDirectory to make it notice the os.environ change.

36. By Stuart Langridge

add mocker to manifest

Revision history for this message
Stuart Langridge (sil) wrote :

> I probably caused the confusion. Yes, the file needs to be in the tarball, but
> not in the final binary packages.

Added to manifest.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

I'm getting this again (and seem to be the only one) but it's easily fixed by not using twistedtestcase, which I don't think we need in any case.

===============================================================================
[ERROR]: desktopcouch.tests.test_start_local_couchdb.TestUpdateDesignDocuments.test_create_databases_and_design_docs

Traceback (most recent call last):
Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
<DelayedCall 155909676 [29.9993708134s] called=0 cancelled=0 Client.failIfNotConnected(TimeoutError('',))>
<DelayedCall 155910124 [19.5468599796s] called=0 cancelled=0 exit_on_timeout()>
<DelayedCall 157621132 [59.999724865s] called=0 cancelled=0 ThreadedResolver._cleanup(u'thelog.local', <Deferred at 0x9651b4c>)>
===============================================================================
[ERROR]: desktopcouch.tests.test_start_local_couchdb.TestUpdateDesignDocuments.test_create_databases_and_design_docs

Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/twisted/internet/defer.py", line 106, in maybeDeferred
    result = f(*args, **kw)
  File "/home/eric/canonical/desktopcouch/r-sil/desktopcouch/pair/tests/test_network_io.py", line 90, in exit_on_success
    reactor.stop()
  File "/usr/lib/python2.6/dist-packages/twisted/trial/unittest.py", line 953, in _
    return self._reactorMethods[name](*a, **kw)
  File "/usr/lib/python2.6/dist-packages/twisted/internet/base.py", line 527, in stop
    "Can't stop reactor that isn't running.")
twisted.internet.error.ReactorNotRunning: Can't stop reactor that isn't running.
-------------------------------------------------------------------------------

review: Needs Fixing
Revision history for this message
dobey (dobey) :
review: Approve
37. By Stuart Langridge

use testtools, not twisted

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Works now, great!

review: Approve
Revision history for this message
Elliot Murphy (statik) wrote :

yay, better.

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches