Merge lp:~mwhudson/lava-dashboard-tool/use-lava-tool into lp:~zyga/lava-dashboard-tool/devel
- use-lava-tool
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zygmunt Krynicki | Approve | ||
Review via email: mp+59866@code.launchpad.net |
Commit message
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.
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', |
You missed unit tests, after module renames tests no longer work. I fixed that, thanks