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