OLD | NEW |
| (Empty) |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """ | |
6 A test facility to assert call sequences while mocking their behavior. | |
7 """ | |
8 | |
9 import unittest | |
10 | |
11 from devil import devil_env | |
12 | |
13 with devil_env.SysPath(devil_env.PYMOCK_PATH): | |
14 import mock # pylint: disable=import-error | |
15 | |
16 | |
17 class TestCase(unittest.TestCase): | |
18 """Adds assertCalls to TestCase objects.""" | |
19 class _AssertCalls(object): | |
20 def __init__(self, test_case, expected_calls, watched): | |
21 def call_action(pair): | |
22 if isinstance(pair, type(mock.call)): | |
23 return (pair, None) | |
24 else: | |
25 return pair | |
26 | |
27 def do_check(call): | |
28 def side_effect(*args, **kwargs): | |
29 received_call = call(*args, **kwargs) | |
30 self._test_case.assertTrue( | |
31 self._expected_calls, | |
32 msg=('Unexpected call: %s' % str(received_call))) | |
33 expected_call, action = self._expected_calls.pop(0) | |
34 self._test_case.assertTrue( | |
35 received_call == expected_call, | |
36 msg=('Expected call mismatch:\n' | |
37 ' expected: %s\n' | |
38 ' received: %s\n' | |
39 % (str(expected_call), str(received_call)))) | |
40 if callable(action): | |
41 return action(*args, **kwargs) | |
42 else: | |
43 return action | |
44 return side_effect | |
45 | |
46 self._test_case = test_case | |
47 self._expected_calls = [call_action(pair) for pair in expected_calls] | |
48 watched = watched.copy() # do not pollute the caller's dict | |
49 watched.update((call.parent.name, call.parent) | |
50 for call, _ in self._expected_calls) | |
51 self._patched = [test_case.patch_call(call, side_effect=do_check(call)) | |
52 for call in watched.itervalues()] | |
53 | |
54 def __enter__(self): | |
55 for patch in self._patched: | |
56 patch.__enter__() | |
57 return self | |
58 | |
59 def __exit__(self, exc_type, exc_val, exc_tb): | |
60 for patch in self._patched: | |
61 patch.__exit__(exc_type, exc_val, exc_tb) | |
62 if exc_type is None: | |
63 missing = ''.join(' expected: %s\n' % str(call) | |
64 for call, _ in self._expected_calls) | |
65 self._test_case.assertFalse( | |
66 missing, | |
67 msg='Expected calls not found:\n' + missing) | |
68 | |
69 def __init__(self, *args, **kwargs): | |
70 super(TestCase, self).__init__(*args, **kwargs) | |
71 self.call = mock.call.self | |
72 self._watched = {} | |
73 | |
74 def call_target(self, call): | |
75 """Resolve a self.call instance to the target it represents. | |
76 | |
77 Args: | |
78 call: a self.call instance, e.g. self.call.adb.Shell | |
79 | |
80 Returns: | |
81 The target object represented by the call, e.g. self.adb.Shell | |
82 | |
83 Raises: | |
84 ValueError if the path of the call does not start with "self", i.e. the | |
85 target of the call is external to the self object. | |
86 AttributeError if the path of the call does not specify a valid | |
87 chain of attributes (without any calls) starting from "self". | |
88 """ | |
89 path = call.name.split('.') | |
90 if path.pop(0) != 'self': | |
91 raise ValueError("Target %r outside of 'self' object" % call.name) | |
92 target = self | |
93 for attr in path: | |
94 target = getattr(target, attr) | |
95 return target | |
96 | |
97 def patch_call(self, call, **kwargs): | |
98 """Patch the target of a mock.call instance. | |
99 | |
100 Args: | |
101 call: a mock.call instance identifying a target to patch | |
102 Extra keyword arguments are processed by mock.patch | |
103 | |
104 Returns: | |
105 A context manager to mock/unmock the target of the call | |
106 """ | |
107 if call.name.startswith('self.'): | |
108 target = self.call_target(call.parent) | |
109 _, attribute = call.name.rsplit('.', 1) | |
110 if (hasattr(type(target), attribute) | |
111 and isinstance(getattr(type(target), attribute), property)): | |
112 return mock.patch.object( | |
113 type(target), attribute, new_callable=mock.PropertyMock, **kwargs) | |
114 else: | |
115 return mock.patch.object(target, attribute, **kwargs) | |
116 else: | |
117 return mock.patch(call.name, **kwargs) | |
118 | |
119 def watchCalls(self, calls): | |
120 """Add calls to the set of watched calls. | |
121 | |
122 Args: | |
123 calls: a sequence of mock.call instances identifying targets to watch | |
124 """ | |
125 self._watched.update((call.name, call) for call in calls) | |
126 | |
127 def watchMethodCalls(self, call, ignore=None): | |
128 """Watch all public methods of the target identified by a self.call. | |
129 | |
130 Args: | |
131 call: a self.call instance indetifying an object | |
132 ignore: a list of public methods to ignore when watching for calls | |
133 """ | |
134 target = self.call_target(call) | |
135 if ignore is None: | |
136 ignore = [] | |
137 self.watchCalls(getattr(call, method) | |
138 for method in dir(target.__class__) | |
139 if not method.startswith('_') and not method in ignore) | |
140 | |
141 def clearWatched(self): | |
142 """Clear the set of watched calls.""" | |
143 self._watched = {} | |
144 | |
145 def assertCalls(self, *calls): | |
146 """A context manager to assert that a sequence of calls is made. | |
147 | |
148 During the assertion, a number of functions and methods will be "watched", | |
149 and any calls made to them is expected to appear---in the exact same order, | |
150 and with the exact same arguments---as specified by the argument |calls|. | |
151 | |
152 By default, the targets of all expected calls are watched. Further targets | |
153 to watch may be added using watchCalls and watchMethodCalls. | |
154 | |
155 Optionaly, each call may be accompanied by an action. If the action is a | |
156 (non-callable) value, this value will be used as the return value given to | |
157 the caller when the matching call is found. Alternatively, if the action is | |
158 a callable, the action will be then called with the same arguments as the | |
159 intercepted call, so that it can provide a return value or perform other | |
160 side effects. If the action is missing, a return value of None is assumed. | |
161 | |
162 Note that mock.Mock objects are often convenient to use as a callable | |
163 action, e.g. to raise exceptions or return other objects which are | |
164 themselves callable. | |
165 | |
166 Args: | |
167 calls: each argument is either a pair (expected_call, action) or just an | |
168 expected_call, where expected_call is a mock.call instance. | |
169 | |
170 Raises: | |
171 AssertionError if the watched targets do not receive the exact sequence | |
172 of calls specified. Missing calls, extra calls, and calls with | |
173 mismatching arguments, all cause the assertion to fail. | |
174 """ | |
175 return self._AssertCalls(self, calls, self._watched) | |
176 | |
177 def assertCall(self, call, action=None): | |
178 return self.assertCalls((call, action)) | |
179 | |
OLD | NEW |