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