Merge lp:~mwhudson/lava-dashboard-tool/use-lava-tool into lp:~zyga/lava-dashboard-tool/devel

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Zygmunt Krynicki
Approved revision: 142
Merged at revision: 140
Proposed branch: lp:~mwhudson/lava-dashboard-tool/use-lava-tool
Merge into: lp:~zyga/lava-dashboard-tool/devel
Diff against target: 2615 lines (+40/-2486)
10 files modified
launch_control_tool/commands.py (+11/-1)
launch_control_tool/commands/__init__.py (+0/-21)
launch_control_tool/commands/misc.py (+0/-40)
launch_control_tool/dispatcher.py (+0/-67)
launch_control_tool/interface.py (+0/-104)
launch_control_tool/main.py (+15/-0)
launch_control_tool/mocker.py (+0/-2155)
launch_control_tool/tests/__init__.py (+0/-4)
launch_control_tool/tests/test_commands.py (+0/-78)
setup.py (+14/-16)
To merge this branch: bzr merge lp:~mwhudson/lava-dashboard-tool/use-lava-tool
Reviewer Review Type Date Requested Status
Zygmunt Krynicki Approve
Review via email: mp+59866@code.launchpad.net

Description of the change

Hi there,

This branch deletes the generic code from launch-control-tool in favour of using that code from its new home in lp:lava-tool. I think it's all fairly straightforward.

It could be a little bit more automated, I think, if we wrote our own distutils command (which would be part of lava-tool). Then I think we could, essentially, generate the main.py I add here from the data in setup.py. That seems a bit of a fiddle though. This would also let you keep version as a generic command in lava-tool. Maybe I'll work on this tomorrow.

This shouldn't be landed until lava-tool is packaged, clearly.

Can you please make lp:launch-control-tool point to something? It makes stacking work and proposing branches easier? TIA :)

Cheers,
mwh

To post a comment you must log in.
Revision history for this message
Zygmunt Krynicki (zyga) wrote :

You missed unit tests, after module renames tests no longer work. I fixed that, thanks

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches