flexmock 0.12.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flexmock/__init__.py +10 -0
- flexmock/_api.py +1533 -0
- flexmock/_integrations.py +184 -0
- flexmock/_pytest_plugin.py +36 -0
- flexmock/exceptions.py +33 -0
- flexmock/py.typed +0 -0
- flexmock-0.12.2.dist-info/LICENSE +26 -0
- flexmock-0.12.2.dist-info/METADATA +130 -0
- flexmock-0.12.2.dist-info/RECORD +11 -0
- flexmock-0.12.2.dist-info/WHEEL +4 -0
- flexmock-0.12.2.dist-info/entry_points.txt +3 -0
flexmock/_api.py
ADDED
|
@@ -0,0 +1,1533 @@
|
|
|
1
|
+
"""Flexmock public API."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-many-lines
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
import types
|
|
8
|
+
from types import BuiltinMethodType, TracebackType
|
|
9
|
+
from typing import Any, Callable, Dict, Iterator, List, NoReturn, Optional, Tuple, Type, Union
|
|
10
|
+
|
|
11
|
+
from flexmock.exceptions import (
|
|
12
|
+
CallOrderError,
|
|
13
|
+
ExceptionClassError,
|
|
14
|
+
ExceptionMessageError,
|
|
15
|
+
FlexmockError,
|
|
16
|
+
MethodCallError,
|
|
17
|
+
MethodSignatureError,
|
|
18
|
+
MockBuiltinError,
|
|
19
|
+
StateError,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
AT_LEAST = "at least"
|
|
23
|
+
AT_MOST = "at most"
|
|
24
|
+
EXACTLY = "exactly"
|
|
25
|
+
SPECIAL_METHODS = (classmethod, staticmethod)
|
|
26
|
+
UPDATED_ATTRS = ["should_receive", "should_call", "new_instances"]
|
|
27
|
+
DEFAULT_CLASS_ATTRIBUTES = [attr for attr in dir(type) if attr not in dir(type("", (object,), {}))]
|
|
28
|
+
# Fix Python 3.6 does not have re.Pattern type
|
|
29
|
+
RE_TYPE = type(re.compile(""))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReturnValue:
|
|
33
|
+
"""ReturnValue"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
value: Optional[Any] = None,
|
|
38
|
+
raises: Optional[Union[Type[BaseException], BaseException]] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.value = value
|
|
41
|
+
self.raises = raises
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
if self.raises:
|
|
45
|
+
return f"{self.raises}({_arg_to_str(self.value)})"
|
|
46
|
+
if not isinstance(self.value, tuple):
|
|
47
|
+
return str(_arg_to_str(self.value))
|
|
48
|
+
if len(self.value) == 1:
|
|
49
|
+
return str(_arg_to_str(self.value[0]))
|
|
50
|
+
values = ", ".join([_arg_to_str(x) for x in self.value])
|
|
51
|
+
return f"({values})"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Mock:
|
|
55
|
+
"""Fake object class returned by the `flexmock()` function."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
58
|
+
"""Mock constructor.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
**kwargs: dict of attribute/value pairs used to initialize the mock
|
|
62
|
+
object.
|
|
63
|
+
"""
|
|
64
|
+
self._object: Any = self
|
|
65
|
+
for attr, value in kwargs.items():
|
|
66
|
+
if isinstance(value, property):
|
|
67
|
+
setattr(self.__class__, attr, value)
|
|
68
|
+
else:
|
|
69
|
+
setattr(self, attr, value)
|
|
70
|
+
|
|
71
|
+
def __enter__(self) -> Any:
|
|
72
|
+
return self._object
|
|
73
|
+
|
|
74
|
+
def __exit__(
|
|
75
|
+
self,
|
|
76
|
+
exc_type: Optional[Type[BaseException]],
|
|
77
|
+
exc_value: Optional[BaseException],
|
|
78
|
+
traceback: Optional[TracebackType],
|
|
79
|
+
) -> None:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def __call__(self, *args: Any, **kwargs: Any) -> "Mock":
|
|
83
|
+
"""Make mocks callable with and without parentheses.
|
|
84
|
+
|
|
85
|
+
If `Mock` is not callable, it is difficult to mock attributes that
|
|
86
|
+
should work with and without parentheses.
|
|
87
|
+
"""
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def __iter__(self) -> Iterator[Any]:
|
|
91
|
+
"""Makes the mock object iterable.
|
|
92
|
+
|
|
93
|
+
Call the instance's version of __iter__ if available, otherwise yield self.
|
|
94
|
+
"""
|
|
95
|
+
if (
|
|
96
|
+
hasattr(self, "__dict__")
|
|
97
|
+
and isinstance(self.__dict__, dict)
|
|
98
|
+
and "__iter__" in self.__dict__
|
|
99
|
+
):
|
|
100
|
+
yield from self.__dict__["__iter__"](self)
|
|
101
|
+
else:
|
|
102
|
+
yield self
|
|
103
|
+
|
|
104
|
+
def should_receive(self, name: str) -> "Expectation":
|
|
105
|
+
"""Replaces the specified attribute with a fake.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
name: Name of the attribute to replace.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Expectation object which can be used to modify the expectations
|
|
112
|
+
on the fake attribute.
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
>>> flexmock(plane).should_receive("fly").and_return("vooosh!").once()
|
|
116
|
+
<flexmock._api.Expectation object at ...>
|
|
117
|
+
>>> plane.fly()
|
|
118
|
+
'vooosh!'
|
|
119
|
+
"""
|
|
120
|
+
if name in UPDATED_ATTRS:
|
|
121
|
+
raise FlexmockError("unable to replace flexmock methods")
|
|
122
|
+
|
|
123
|
+
chained_methods = None
|
|
124
|
+
if "." in name:
|
|
125
|
+
name, chained_methods = name.split(".", 1)
|
|
126
|
+
name = self._update_name_if_mangled(name)
|
|
127
|
+
self._ensure_object_has_named_attribute(name)
|
|
128
|
+
|
|
129
|
+
if chained_methods:
|
|
130
|
+
if not isinstance(self._object, Mock) and not hasattr(
|
|
131
|
+
getattr(self._object, name), "__call__"
|
|
132
|
+
):
|
|
133
|
+
# Create a partial mock if the given name is callable
|
|
134
|
+
# this allows chaining attributes
|
|
135
|
+
return_value = _create_partial_mock(getattr(self._object, name))
|
|
136
|
+
else:
|
|
137
|
+
return_value = Mock()
|
|
138
|
+
self._create_expectation(name, return_value)
|
|
139
|
+
return return_value.should_receive(chained_methods)
|
|
140
|
+
|
|
141
|
+
return self._create_expectation(name)
|
|
142
|
+
|
|
143
|
+
def _update_name_if_mangled(self, name: str) -> str:
|
|
144
|
+
"""This allows flexmock to mock methods with name mangling."""
|
|
145
|
+
if name.startswith("__") and not name.endswith("__") and not inspect.ismodule(self._object):
|
|
146
|
+
class_name: str
|
|
147
|
+
if inspect.isclass(self._object):
|
|
148
|
+
class_name = self._object.__name__
|
|
149
|
+
else:
|
|
150
|
+
class_name = self._object.__class__.__name__
|
|
151
|
+
name = f"_{class_name.lstrip('_')}__{name.lstrip('_')}"
|
|
152
|
+
return name
|
|
153
|
+
|
|
154
|
+
def _ensure_object_has_named_attribute(self, name: str) -> None:
|
|
155
|
+
if not isinstance(self._object, Mock) and not self._hasattr(self._object, name):
|
|
156
|
+
if hasattr(self._object, "__name__"):
|
|
157
|
+
obj_name = self._object.__name__
|
|
158
|
+
else:
|
|
159
|
+
obj_name = str(self._object)
|
|
160
|
+
raise FlexmockError(f"{obj_name} does not have attribute '{name}'")
|
|
161
|
+
|
|
162
|
+
def _hasattr(self, obj: Any, name: str) -> bool:
|
|
163
|
+
"""Ensure hasattr checks don't create side-effects for properties."""
|
|
164
|
+
if not inspect.isclass(obj) and hasattr(obj, "__dict__") and name not in obj.__dict__:
|
|
165
|
+
if name in DEFAULT_CLASS_ATTRIBUTES:
|
|
166
|
+
return False # avoid false positives for things like __call__
|
|
167
|
+
return hasattr(obj.__class__, name)
|
|
168
|
+
return hasattr(obj, name)
|
|
169
|
+
|
|
170
|
+
def should_call(self, name: str) -> "Expectation":
|
|
171
|
+
"""Creates a spy.
|
|
172
|
+
|
|
173
|
+
This means that the original method will be called rather than the fake
|
|
174
|
+
version. However, we can still keep track of how many times it is called
|
|
175
|
+
and with what arguments, and apply expectations accordingly.
|
|
176
|
+
|
|
177
|
+
`should_call` is meaningless/not allowed for non-callable attributes.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
name: Name of the method to spy.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Expectation object.
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
>>> flexmock(plane).should_call("repair").once()
|
|
187
|
+
<flexmock._api.Expectation object at ...>
|
|
188
|
+
>>> plane.repair("wing")
|
|
189
|
+
"""
|
|
190
|
+
if isinstance(self._object, Mock) and not hasattr(self._object, name):
|
|
191
|
+
raise FlexmockError(
|
|
192
|
+
f"Mock object does not have attribute '{name}'. "
|
|
193
|
+
f'Did you mean to call should_receive("{name}") instead?'
|
|
194
|
+
)
|
|
195
|
+
expectation = self.should_receive(name)
|
|
196
|
+
return expectation.replace_with(expectation.__dict__["_original"])
|
|
197
|
+
|
|
198
|
+
def new_instances(self, *args: Any) -> "Expectation":
|
|
199
|
+
"""Overrides `__new__` method on a class to return custom objects.
|
|
200
|
+
|
|
201
|
+
Alias for `should_receive('__new__').and_return(args).one_by_one()`.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
*args: Objects to return on each successive call to `__new__`.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Expectation object.
|
|
208
|
+
|
|
209
|
+
Examples:
|
|
210
|
+
|
|
211
|
+
>>> fake_plane = flexmock(model="fake")
|
|
212
|
+
>>> flexmock(Plane).new_instances(fake_plane)
|
|
213
|
+
<flexmock._api.Expectation object at ...>
|
|
214
|
+
>>> Plane().model
|
|
215
|
+
'fake'
|
|
216
|
+
|
|
217
|
+
It is also possible to return different fake objects in a sequence:
|
|
218
|
+
|
|
219
|
+
>>> fake_plane1 = flexmock(model="fake1")
|
|
220
|
+
>>> fake_plane2 = flexmock(model="fake2")
|
|
221
|
+
>>> flexmock(Plane).new_instances(fake_plane1, fake_plane2)
|
|
222
|
+
<flexmock._api.Expectation object at ...>
|
|
223
|
+
>>> Plane().model
|
|
224
|
+
'fake1'
|
|
225
|
+
>>> Plane().model
|
|
226
|
+
'fake2'
|
|
227
|
+
"""
|
|
228
|
+
if inspect.isclass(self._object):
|
|
229
|
+
return self.should_receive("__new__").and_return(args).one_by_one()
|
|
230
|
+
raise FlexmockError("new_instances can only be called on a class mock")
|
|
231
|
+
|
|
232
|
+
def _create_expectation(self, name: str, return_value: Optional[Any] = None) -> "Expectation":
|
|
233
|
+
expectation = self._get_or_create_expectation(name, return_value)
|
|
234
|
+
FlexmockContainer.add_expectation(self, expectation)
|
|
235
|
+
|
|
236
|
+
if _isproperty(self._object, name):
|
|
237
|
+
self._update_property(expectation, name)
|
|
238
|
+
elif (
|
|
239
|
+
isinstance(self._object, Mock)
|
|
240
|
+
or hasattr(getattr(self._object, name), "__call__")
|
|
241
|
+
or inspect.isclass(getattr(self._object, name))
|
|
242
|
+
):
|
|
243
|
+
self._update_method(expectation, name)
|
|
244
|
+
else:
|
|
245
|
+
self._update_attribute(expectation, name, return_value)
|
|
246
|
+
return expectation
|
|
247
|
+
|
|
248
|
+
def _get_or_create_expectation(
|
|
249
|
+
self, name: str, return_value: Optional[Any] = None
|
|
250
|
+
) -> "Expectation":
|
|
251
|
+
saved_expectations = FlexmockContainer.get_expectations_with_name(self, name)
|
|
252
|
+
if saved_expectations:
|
|
253
|
+
# If there is already an expectation for the same name, get the
|
|
254
|
+
# original object from the FIRST saved expectation.
|
|
255
|
+
return Expectation(
|
|
256
|
+
self._object,
|
|
257
|
+
name=name,
|
|
258
|
+
return_value=return_value,
|
|
259
|
+
original=saved_expectations[0].__dict__.get("_original"),
|
|
260
|
+
method_type=saved_expectations[0].__dict__.get("_method_type"),
|
|
261
|
+
)
|
|
262
|
+
return Expectation(self._object, name=name, return_value=return_value)
|
|
263
|
+
|
|
264
|
+
def _create_placeholder_mock_for_proper_teardown(
|
|
265
|
+
self, obj: Any, name: str, original: Any
|
|
266
|
+
) -> None:
|
|
267
|
+
"""Ensures that the given function is replaced on teardown."""
|
|
268
|
+
mock = Mock()
|
|
269
|
+
mock._object = obj
|
|
270
|
+
expectation = Expectation(obj, name=name, original=original)
|
|
271
|
+
FlexmockContainer.add_expectation(mock, expectation)
|
|
272
|
+
|
|
273
|
+
def _update_method(self, expectation: "Expectation", name: str) -> None:
|
|
274
|
+
method_instance = self._create_mock_method(name)
|
|
275
|
+
if self._hasattr(self._object, name) and not hasattr(expectation, "_original"):
|
|
276
|
+
expectation._update_original(name, self._object)
|
|
277
|
+
expectation._method_type = self._get_method_type(name, expectation._original)
|
|
278
|
+
if expectation._method_type in SPECIAL_METHODS:
|
|
279
|
+
expectation._original_function = getattr(self._object, name)
|
|
280
|
+
if not inspect.isclass(self._object) or expectation._method_type in SPECIAL_METHODS:
|
|
281
|
+
method_instance = types.MethodType(method_instance, self._object)
|
|
282
|
+
expectation._local_override = _setattr(self._object, name, method_instance)
|
|
283
|
+
if (
|
|
284
|
+
expectation._local_override
|
|
285
|
+
and not inspect.isclass(self._object)
|
|
286
|
+
and not isinstance(self._object, Mock)
|
|
287
|
+
and hasattr(self._object.__class__, name)
|
|
288
|
+
):
|
|
289
|
+
self._update_class_for_magic_builtins(name)
|
|
290
|
+
|
|
291
|
+
def _get_method_type(self, name: str, method: Callable[..., Any]) -> Any:
|
|
292
|
+
"""Get method type of the original method.
|
|
293
|
+
|
|
294
|
+
Method type is saved because after mocking the base class, it is difficult to determine
|
|
295
|
+
the original method type.
|
|
296
|
+
"""
|
|
297
|
+
method_type = self._get_saved_method_type(name, method)
|
|
298
|
+
if method_type is not None:
|
|
299
|
+
return method_type
|
|
300
|
+
if _is_class_method(method, name):
|
|
301
|
+
method_type = classmethod
|
|
302
|
+
elif _is_static_method(self._object, name):
|
|
303
|
+
method_type = staticmethod
|
|
304
|
+
else:
|
|
305
|
+
method_type = type(method)
|
|
306
|
+
setattr(self._object, f"{name}__flexmock__method_type", method_type)
|
|
307
|
+
return method_type
|
|
308
|
+
|
|
309
|
+
def _get_saved_method_type(self, name: str, method: Callable[..., Any]) -> Optional[Any]:
|
|
310
|
+
"""Check method type of the original method if it was saved to the class or base class."""
|
|
311
|
+
bound_to = getattr(method, "__self__", None)
|
|
312
|
+
if bound_to is not None and inspect.isclass(bound_to):
|
|
313
|
+
# Check if the method type was saved in a base class
|
|
314
|
+
for cls in inspect.getmro(bound_to):
|
|
315
|
+
method_type = vars(cls).get(f"{name}__flexmock__method_type")
|
|
316
|
+
if method_type:
|
|
317
|
+
return method_type
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
def _update_class_for_magic_builtins(self, name: str) -> None:
|
|
321
|
+
"""Fixes method resolution order for built-in methods.
|
|
322
|
+
|
|
323
|
+
Replacing magic builtins on instances has no effect as the one attached
|
|
324
|
+
to the class takes precedence. To work around it, we update the class'
|
|
325
|
+
method to check if the instance in question has one in its own __dict__
|
|
326
|
+
and call that instead.
|
|
327
|
+
"""
|
|
328
|
+
if not (name.startswith("__") and name.endswith("__") and len(name) > 4):
|
|
329
|
+
return
|
|
330
|
+
original = getattr(self._object.__class__, name)
|
|
331
|
+
|
|
332
|
+
def updated(self: Any, *kargs: Any, **kwargs: Any) -> Any:
|
|
333
|
+
if (
|
|
334
|
+
hasattr(self, "__dict__")
|
|
335
|
+
and isinstance(self.__dict__, dict)
|
|
336
|
+
and name in self.__dict__
|
|
337
|
+
):
|
|
338
|
+
return self.__dict__[name](*kargs, **kwargs)
|
|
339
|
+
return original(self, *kargs, **kwargs)
|
|
340
|
+
|
|
341
|
+
setattr(self._object.__class__, name, updated)
|
|
342
|
+
if updated.__code__ != original.__code__:
|
|
343
|
+
self._create_placeholder_mock_for_proper_teardown(
|
|
344
|
+
self._object.__class__, name, original
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def _update_attribute(
|
|
348
|
+
self, expectation: "Expectation", name: str, return_value: Optional[Any] = None
|
|
349
|
+
) -> None:
|
|
350
|
+
expectation._callable = False
|
|
351
|
+
if self._hasattr(self._object, name) and not hasattr(expectation, "_original"):
|
|
352
|
+
expectation._update_original(name, self._object)
|
|
353
|
+
expectation._local_override = _setattr(self._object, name, return_value)
|
|
354
|
+
|
|
355
|
+
def _update_property(self, expectation: "Expectation", name: str) -> None:
|
|
356
|
+
new_name = f"_flexmock__{name}"
|
|
357
|
+
obj = self._object
|
|
358
|
+
if not inspect.isclass(obj):
|
|
359
|
+
obj = obj.__class__
|
|
360
|
+
|
|
361
|
+
expectation._callable = False
|
|
362
|
+
original = getattr(obj, name)
|
|
363
|
+
|
|
364
|
+
@property # type: ignore
|
|
365
|
+
def updated(self: Any) -> Any:
|
|
366
|
+
if (
|
|
367
|
+
hasattr(self, "__dict__")
|
|
368
|
+
and isinstance(self.__dict__, dict)
|
|
369
|
+
and name in self.__dict__
|
|
370
|
+
):
|
|
371
|
+
return self.__dict__[name]
|
|
372
|
+
# Return original for instances that are not mocked
|
|
373
|
+
return getattr(self, new_name)
|
|
374
|
+
|
|
375
|
+
setattr(obj, name, updated)
|
|
376
|
+
if not hasattr(obj, new_name):
|
|
377
|
+
# don't try to double update
|
|
378
|
+
FlexmockContainer.add_teardown_property(obj, new_name)
|
|
379
|
+
setattr(obj, new_name, original)
|
|
380
|
+
self._create_placeholder_mock_for_proper_teardown(obj, name, original)
|
|
381
|
+
|
|
382
|
+
def _create_mock_method(self, name: str) -> Callable[..., Any]:
|
|
383
|
+
def _handle_exception_matching(expectation: Expectation) -> None:
|
|
384
|
+
# pylint: disable=misplaced-bare-raise
|
|
385
|
+
return_values = expectation._return_values
|
|
386
|
+
if return_values:
|
|
387
|
+
raised, instance = sys.exc_info()[:2]
|
|
388
|
+
assert raised, "no exception was raised"
|
|
389
|
+
message = str(instance)
|
|
390
|
+
expected = return_values[0].raises
|
|
391
|
+
if not expected:
|
|
392
|
+
raise
|
|
393
|
+
args = return_values[0].value
|
|
394
|
+
if inspect.isclass(expected):
|
|
395
|
+
assert isinstance(args, dict)
|
|
396
|
+
expected_instance = expected(*args["kargs"], **args["kwargs"])
|
|
397
|
+
expected_message = str(expected_instance)
|
|
398
|
+
if expected is not raised and expected not in raised.__bases__:
|
|
399
|
+
raise ExceptionClassError(
|
|
400
|
+
f"Raised exception for call {expectation._name} "
|
|
401
|
+
"did not match expectation:\n"
|
|
402
|
+
f" Expected:\t{expected}\n"
|
|
403
|
+
f" Raised:\t{raised}"
|
|
404
|
+
)
|
|
405
|
+
if args["kargs"] and isinstance(args["kargs"][0], RE_TYPE):
|
|
406
|
+
if not args["kargs"][0].search(message):
|
|
407
|
+
raise ExceptionMessageError(
|
|
408
|
+
f"Error message mismatch with raised {expected.__name__}:\n"
|
|
409
|
+
f" Expected pattern:\n\t/{args['kargs'][0].pattern}/\n"
|
|
410
|
+
f" Received message:\n\t'{message}'"
|
|
411
|
+
)
|
|
412
|
+
elif expected_message and expected_message != message:
|
|
413
|
+
raise (
|
|
414
|
+
ExceptionMessageError(
|
|
415
|
+
f"Error message mismatch with raised {expected.__name__}:\n"
|
|
416
|
+
f" Expected message:\n\t'{message}'\n"
|
|
417
|
+
f" Received message:\n\t'{expected_message}'"
|
|
418
|
+
)
|
|
419
|
+
)
|
|
420
|
+
elif expected is not raised:
|
|
421
|
+
raise ExceptionClassError(
|
|
422
|
+
f"Raised exception for call {expectation._name} "
|
|
423
|
+
f"did not match expectation:\n"
|
|
424
|
+
f" Expected:\t{repr(expected)}\n"
|
|
425
|
+
f" Raised:\t{raised}\n\n"
|
|
426
|
+
"Did you try to call and_raise with an instance?\n"
|
|
427
|
+
'Instead of and_raise(Exception("arg")), try and_raise(Exception, "arg")'
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
raise
|
|
431
|
+
|
|
432
|
+
def match_return_values(expected: Any, received: Any) -> bool:
|
|
433
|
+
if not isinstance(expected, tuple):
|
|
434
|
+
expected = (expected,)
|
|
435
|
+
if not isinstance(received, tuple):
|
|
436
|
+
received = (received,)
|
|
437
|
+
if len(received) != len(expected):
|
|
438
|
+
return False
|
|
439
|
+
for i, val in enumerate(received):
|
|
440
|
+
if not _arguments_match(val, expected[i]):
|
|
441
|
+
return False
|
|
442
|
+
return True
|
|
443
|
+
|
|
444
|
+
def pass_thru(
|
|
445
|
+
expectation: Expectation, runtime_self: Any, *kargs: Any, **kwargs: Any
|
|
446
|
+
) -> Any:
|
|
447
|
+
return_values = None
|
|
448
|
+
try:
|
|
449
|
+
original = expectation._original
|
|
450
|
+
_mock = expectation._mock
|
|
451
|
+
if inspect.isclass(_mock):
|
|
452
|
+
if expectation._method_type in SPECIAL_METHODS:
|
|
453
|
+
original = expectation._original_function
|
|
454
|
+
return_values = original(*kargs, **kwargs)
|
|
455
|
+
else:
|
|
456
|
+
return_values = original(runtime_self, *kargs, **kwargs)
|
|
457
|
+
else:
|
|
458
|
+
return_values = original(*kargs, **kwargs)
|
|
459
|
+
except Exception:
|
|
460
|
+
return _handle_exception_matching(expectation)
|
|
461
|
+
expected_values = expectation._return_values
|
|
462
|
+
if expected_values and not match_return_values(expected_values[0].value, return_values):
|
|
463
|
+
expected_value = expected_values[0].value
|
|
464
|
+
# Display strings with quotes in the error message
|
|
465
|
+
if isinstance(return_values, str):
|
|
466
|
+
return_values = repr(return_values)
|
|
467
|
+
if isinstance(expected_value, str):
|
|
468
|
+
expected_value = repr(expected_value)
|
|
469
|
+
raise (
|
|
470
|
+
MethodSignatureError(
|
|
471
|
+
f"Returned values for call {expectation._name} did not match expectation:\n"
|
|
472
|
+
f" Expected:\t{expected_value}\n"
|
|
473
|
+
f" Returned:\t{return_values}"
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
return return_values
|
|
477
|
+
|
|
478
|
+
def _handle_matched_expectation(
|
|
479
|
+
expectation: Expectation, runtime_self: Any, *kargs: Any, **kwargs: Any
|
|
480
|
+
) -> Any:
|
|
481
|
+
if not expectation._runnable():
|
|
482
|
+
raise StateError(
|
|
483
|
+
f"{name} expected to be called when {expectation._get_runnable()} is True"
|
|
484
|
+
)
|
|
485
|
+
expectation._times_called += 1
|
|
486
|
+
expectation._verify(final=False)
|
|
487
|
+
_pass_thru = expectation._pass_thru
|
|
488
|
+
_replace_with = expectation._replace_with
|
|
489
|
+
if _pass_thru:
|
|
490
|
+
return pass_thru(expectation, runtime_self, *kargs, **kwargs)
|
|
491
|
+
if _replace_with:
|
|
492
|
+
return _replace_with(*kargs, **kwargs)
|
|
493
|
+
return_values = expectation._return_values
|
|
494
|
+
if return_values:
|
|
495
|
+
return_value = return_values[0]
|
|
496
|
+
del return_values[0]
|
|
497
|
+
return_values.append(return_value)
|
|
498
|
+
else:
|
|
499
|
+
return_value = ReturnValue()
|
|
500
|
+
if return_value.raises:
|
|
501
|
+
if inspect.isclass(return_value.raises):
|
|
502
|
+
args = return_value.value
|
|
503
|
+
assert isinstance(args, dict)
|
|
504
|
+
raise return_value.raises(*args["kargs"], **args["kwargs"])
|
|
505
|
+
raise return_value.raises
|
|
506
|
+
return return_value.value
|
|
507
|
+
|
|
508
|
+
def mock_method(runtime_self: Any, *kargs: Any, **kwargs: Any) -> Any:
|
|
509
|
+
arguments = {"kargs": kargs, "kwargs": kwargs}
|
|
510
|
+
expectation = FlexmockContainer.get_flexmock_expectation(self, name, arguments)
|
|
511
|
+
if expectation:
|
|
512
|
+
return _handle_matched_expectation(expectation, runtime_self, *kargs, **kwargs)
|
|
513
|
+
# inform the user which expectation(s) for the method were _not_ matched
|
|
514
|
+
saved_expectations = reversed(FlexmockContainer.get_expectations_with_name(self, name))
|
|
515
|
+
error_msg = (
|
|
516
|
+
f"Arguments for call {name} did not match expectations:\n"
|
|
517
|
+
f" Received call:\t{_format_args(name, arguments)}\n"
|
|
518
|
+
)
|
|
519
|
+
if saved_expectations:
|
|
520
|
+
error_msg += "\n".join(
|
|
521
|
+
f" Expected call[{index}]:\t{_format_args(name, expectation._args)}"
|
|
522
|
+
for index, expectation in enumerate(saved_expectations, 1)
|
|
523
|
+
)
|
|
524
|
+
raise MethodSignatureError(error_msg)
|
|
525
|
+
|
|
526
|
+
return mock_method
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def flexmock_teardown() -> None:
|
|
530
|
+
"""Performs flexmock-specific teardown tasks."""
|
|
531
|
+
saved = {}
|
|
532
|
+
instances = []
|
|
533
|
+
classes = []
|
|
534
|
+
for mock_object, expectations in FlexmockContainer.flexmock_objects.items():
|
|
535
|
+
saved[mock_object] = expectations[:]
|
|
536
|
+
# Reset the expectations in reversed order to ensure everything is restored to
|
|
537
|
+
# its original form even if setting consequent expectations on the same object
|
|
538
|
+
# operates on top of previous expectation and not on original object.
|
|
539
|
+
for expectation in reversed(expectations):
|
|
540
|
+
expectation._reset()
|
|
541
|
+
for expectation in expectations:
|
|
542
|
+
# Remove method type attributes set by flexmock. This needs to be done after
|
|
543
|
+
# resetting all the expectations because method type is needed in expectation teardown.
|
|
544
|
+
if inspect.isclass(mock_object) or hasattr(mock_object, "__class__"):
|
|
545
|
+
try:
|
|
546
|
+
delattr(mock_object._object, f"{expectation._name}__flexmock__method_type")
|
|
547
|
+
except (AttributeError, TypeError):
|
|
548
|
+
pass
|
|
549
|
+
for mock in saved:
|
|
550
|
+
obj = mock._object
|
|
551
|
+
if not isinstance(obj, Mock) and not inspect.isclass(obj):
|
|
552
|
+
instances.append(obj)
|
|
553
|
+
if inspect.isclass(obj):
|
|
554
|
+
classes.append(obj)
|
|
555
|
+
for obj in instances + classes:
|
|
556
|
+
for attr in UPDATED_ATTRS:
|
|
557
|
+
try:
|
|
558
|
+
obj_dict = obj.__dict__
|
|
559
|
+
if obj_dict[attr].__code__ is Mock.__dict__[attr].__code__:
|
|
560
|
+
del obj_dict[attr]
|
|
561
|
+
except Exception:
|
|
562
|
+
try:
|
|
563
|
+
if getattr(obj, attr).__code__ is Mock.__dict__[attr].__code__:
|
|
564
|
+
delattr(obj, attr)
|
|
565
|
+
except AttributeError:
|
|
566
|
+
pass
|
|
567
|
+
FlexmockContainer.teardown_properties()
|
|
568
|
+
FlexmockContainer.reset()
|
|
569
|
+
|
|
570
|
+
# make sure this is done last to keep exceptions here from breaking
|
|
571
|
+
# any of the previous steps that cleanup all the changes
|
|
572
|
+
for mock_object, expectations in saved.items():
|
|
573
|
+
for expectation in expectations:
|
|
574
|
+
expectation._verify()
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class Expectation:
|
|
578
|
+
"""Holds expectations about methods.
|
|
579
|
+
|
|
580
|
+
The information contained in the Expectation object includes method name,
|
|
581
|
+
its argument list, return values, and any exceptions that the method might
|
|
582
|
+
raise.
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
def __init__(
|
|
586
|
+
self,
|
|
587
|
+
mock: Mock,
|
|
588
|
+
name: Optional[str] = None,
|
|
589
|
+
return_value: Optional[Any] = None,
|
|
590
|
+
original: Optional[Any] = None,
|
|
591
|
+
method_type: Optional[Any] = None,
|
|
592
|
+
) -> None:
|
|
593
|
+
if original is not None:
|
|
594
|
+
self._original = original
|
|
595
|
+
|
|
596
|
+
self._name = name
|
|
597
|
+
self._times_called: int = 0
|
|
598
|
+
self._modifier: str = EXACTLY
|
|
599
|
+
self._args: Optional[Dict[str, Any]] = None
|
|
600
|
+
self._method_type = method_type
|
|
601
|
+
self._argspec: Optional[inspect.FullArgSpec] = None
|
|
602
|
+
self._return_values = [ReturnValue(return_value)] if return_value is not None else []
|
|
603
|
+
self._replace_with: Optional[Callable[..., Any]] = None
|
|
604
|
+
self._original_function: Optional[Callable[..., Any]] = None
|
|
605
|
+
self._expected_calls: Dict[str, Optional[int]] = {
|
|
606
|
+
EXACTLY: None,
|
|
607
|
+
AT_LEAST: None,
|
|
608
|
+
AT_MOST: None,
|
|
609
|
+
}
|
|
610
|
+
self._runnable: Callable[..., bool] = lambda: True
|
|
611
|
+
self._mock = mock
|
|
612
|
+
self._pass_thru = False
|
|
613
|
+
self._ordered = False
|
|
614
|
+
self._one_by_one = False
|
|
615
|
+
self._verified = False
|
|
616
|
+
self._callable = True
|
|
617
|
+
self._local_override = False
|
|
618
|
+
# Remove in 1.0. Issue #96
|
|
619
|
+
self._called_deprecated_property = False
|
|
620
|
+
|
|
621
|
+
def __str__(self) -> str:
|
|
622
|
+
args = _format_args(str(self._name), self._args)
|
|
623
|
+
return_values = ", ".join(str(x) for x in self._return_values)
|
|
624
|
+
return f"{args} -> ({return_values})"
|
|
625
|
+
|
|
626
|
+
def __getattribute__(self, name: str) -> Any:
|
|
627
|
+
# Workaround to raise an error if once, twice, or never are called
|
|
628
|
+
# without parenthesis.
|
|
629
|
+
if name in ("once", "twice", "never", "mock"):
|
|
630
|
+
self._called_deprecated_property = True
|
|
631
|
+
return object.__getattribute__(self, name)
|
|
632
|
+
|
|
633
|
+
def _get_runnable(self) -> str:
|
|
634
|
+
"""Ugly hack to get the name of when() condition from the source code."""
|
|
635
|
+
name = "condition"
|
|
636
|
+
try:
|
|
637
|
+
source = inspect.getsource(self._runnable)
|
|
638
|
+
if "when(" in source:
|
|
639
|
+
name = source.split("when(")[1].split(")")[0]
|
|
640
|
+
elif "def " in source:
|
|
641
|
+
name = source.split("def ")[1].split("(")[0]
|
|
642
|
+
except Exception:
|
|
643
|
+
# couldn't get the source, oh well
|
|
644
|
+
pass
|
|
645
|
+
return name
|
|
646
|
+
|
|
647
|
+
def _verify_signature_match(self, *kargs: Any, **kwargs: Any) -> None:
|
|
648
|
+
if isinstance(self._mock, Mock):
|
|
649
|
+
return # no sense in enforcing this for fake objects
|
|
650
|
+
allowed = self._argspec
|
|
651
|
+
args_len = len(allowed.args)
|
|
652
|
+
|
|
653
|
+
# self is the first expected argument
|
|
654
|
+
has_self = allowed.args and allowed.args[0] == "self"
|
|
655
|
+
# Builtin methods take `self` as the first argument but `inspect.ismethod` returns False
|
|
656
|
+
# so we need to check for them explicitly
|
|
657
|
+
is_builtin_method = isinstance(self._original, BuiltinMethodType) and has_self
|
|
658
|
+
# Methods take `self` if not a staticmethod
|
|
659
|
+
is_method = inspect.ismethod(self._original) and self._method_type is not staticmethod
|
|
660
|
+
# Class init takes `self`
|
|
661
|
+
is_class = inspect.isclass(self._original)
|
|
662
|
+
# When calling class methods or instance methods on a class method takes `cls`
|
|
663
|
+
is_class_method = (
|
|
664
|
+
inspect.isfunction(self._original)
|
|
665
|
+
and inspect.isclass(self._mock)
|
|
666
|
+
and self._method_type is not staticmethod
|
|
667
|
+
)
|
|
668
|
+
if is_builtin_method or is_method or is_class or is_class_method:
|
|
669
|
+
# Do not count `self` or `cls`.
|
|
670
|
+
args_len -= 1
|
|
671
|
+
|
|
672
|
+
minimum = args_len - (allowed.defaults and len(allowed.defaults) or 0)
|
|
673
|
+
maximum = None
|
|
674
|
+
if allowed.varargs is None and allowed.varkw is None:
|
|
675
|
+
maximum = args_len
|
|
676
|
+
total_positional = len(kargs + tuple(a for a in kwargs if a in allowed.args))
|
|
677
|
+
named_optionals = [
|
|
678
|
+
a
|
|
679
|
+
for a in kwargs
|
|
680
|
+
if allowed.defaults
|
|
681
|
+
if a in allowed.args[len(allowed.args) - len(allowed.defaults) :]
|
|
682
|
+
]
|
|
683
|
+
if allowed.defaults and total_positional == minimum and named_optionals:
|
|
684
|
+
minimum += len(named_optionals)
|
|
685
|
+
if total_positional < minimum:
|
|
686
|
+
arguments = "argument" if minimum == 1 else "arguments"
|
|
687
|
+
raise MethodSignatureError(
|
|
688
|
+
f"{self._name} requires at least {minimum} {arguments}, "
|
|
689
|
+
f"expectation provided {total_positional}"
|
|
690
|
+
)
|
|
691
|
+
if maximum is not None and total_positional > maximum:
|
|
692
|
+
arguments = "argument" if maximum == 1 else "arguments"
|
|
693
|
+
raise MethodSignatureError(
|
|
694
|
+
f"{self._name} requires at most {maximum} {arguments}, "
|
|
695
|
+
f"expectation provided {total_positional}"
|
|
696
|
+
)
|
|
697
|
+
if args_len == len(kargs) and any(a for a in kwargs if a in allowed.args):
|
|
698
|
+
given_args = [a for a in kwargs if a in allowed.args]
|
|
699
|
+
arguments = "argument" if len(given_args) == 1 else "arguments"
|
|
700
|
+
raise MethodSignatureError(
|
|
701
|
+
f"{given_args} already given as positional {arguments} to {self._name}"
|
|
702
|
+
)
|
|
703
|
+
if not allowed.varkw and any(
|
|
704
|
+
a for a in kwargs if a not in allowed.args + allowed.kwonlyargs
|
|
705
|
+
):
|
|
706
|
+
invalid_arg = [a for a in kwargs if a not in allowed.args + allowed.kwonlyargs][0]
|
|
707
|
+
raise MethodSignatureError(
|
|
708
|
+
f"{invalid_arg} is not a valid keyword argument to {self._name}"
|
|
709
|
+
)
|
|
710
|
+
# check that kwonlyargs that don't have default value specified are provided
|
|
711
|
+
required_kwonlyargs = [
|
|
712
|
+
a for a in allowed.kwonlyargs if a not in (allowed.kwonlydefaults or {})
|
|
713
|
+
]
|
|
714
|
+
missing_kwonlyargs = [a for a in required_kwonlyargs if a not in kwargs]
|
|
715
|
+
if missing_kwonlyargs:
|
|
716
|
+
arguments = "argument" if len(missing_kwonlyargs) == 1 else "arguments"
|
|
717
|
+
missing_args = '", "'.join(missing_kwonlyargs)
|
|
718
|
+
raise MethodSignatureError(
|
|
719
|
+
f'{self._name} requires keyword-only {arguments} "{missing_args}"'
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
def _update_original(self, name: str, obj: Any) -> None:
|
|
723
|
+
if hasattr(obj, "__dict__") and name in obj.__dict__:
|
|
724
|
+
self._original = obj.__dict__[name]
|
|
725
|
+
else:
|
|
726
|
+
self._original = getattr(obj, name)
|
|
727
|
+
self._update_argspec()
|
|
728
|
+
|
|
729
|
+
def _update_argspec(self) -> None:
|
|
730
|
+
original = self.__dict__.get("_original")
|
|
731
|
+
if original:
|
|
732
|
+
try:
|
|
733
|
+
self._argspec = inspect.getfullargspec(original)
|
|
734
|
+
except TypeError:
|
|
735
|
+
# built-in function: fall back to stupid processing and hope the
|
|
736
|
+
# builtins don't change signature
|
|
737
|
+
pass
|
|
738
|
+
|
|
739
|
+
def _normalize_named_args(self, *kargs: Any, **kwargs: Any) -> Dict[str, Any]:
|
|
740
|
+
argspec = self._argspec
|
|
741
|
+
default = {"kargs": kargs, "kwargs": kwargs}
|
|
742
|
+
if not argspec:
|
|
743
|
+
return default
|
|
744
|
+
ret: Dict[str, Any] = {"kargs": (), "kwargs": kwargs}
|
|
745
|
+
if inspect.ismethod(self._original):
|
|
746
|
+
args = argspec.args[1:]
|
|
747
|
+
else:
|
|
748
|
+
args = argspec.args
|
|
749
|
+
for i, arg in enumerate(kargs):
|
|
750
|
+
if len(args) <= i:
|
|
751
|
+
return default
|
|
752
|
+
ret["kwargs"][args[i]] = arg
|
|
753
|
+
return ret
|
|
754
|
+
|
|
755
|
+
def __raise(self, exception: Type[BaseException], message: str) -> NoReturn:
|
|
756
|
+
"""Safe internal raise implementation.
|
|
757
|
+
|
|
758
|
+
In case we're patching builtins, it's important to reset the
|
|
759
|
+
expectation before raising any exceptions or else things like
|
|
760
|
+
open() might be stubbed out and the resulting runner errors are very
|
|
761
|
+
difficult to diagnose.
|
|
762
|
+
"""
|
|
763
|
+
self._reset()
|
|
764
|
+
raise exception(message)
|
|
765
|
+
|
|
766
|
+
def _match_args(self, given_args: Any) -> bool:
|
|
767
|
+
"""Check if the set of given arguments matches this expectation."""
|
|
768
|
+
expected_args = self._args
|
|
769
|
+
given_args = self._normalize_named_args(*given_args["kargs"], **given_args["kwargs"])
|
|
770
|
+
if expected_args == given_args or expected_args is None:
|
|
771
|
+
return True
|
|
772
|
+
if (
|
|
773
|
+
len(given_args["kargs"]) != len(expected_args["kargs"])
|
|
774
|
+
or len(given_args["kwargs"]) != len(expected_args["kwargs"])
|
|
775
|
+
or (sorted(given_args["kwargs"].keys()) != sorted(expected_args["kwargs"].keys()))
|
|
776
|
+
):
|
|
777
|
+
return False
|
|
778
|
+
for i, arg in enumerate(given_args["kargs"]):
|
|
779
|
+
if not _arguments_match(arg, expected_args["kargs"][i]):
|
|
780
|
+
return False
|
|
781
|
+
for key, value in given_args["kwargs"].items():
|
|
782
|
+
if not _arguments_match(value, expected_args["kwargs"][key]):
|
|
783
|
+
return False
|
|
784
|
+
return True
|
|
785
|
+
|
|
786
|
+
def mock(self) -> Mock:
|
|
787
|
+
"""Return the mock associated with this expectation.
|
|
788
|
+
|
|
789
|
+
Returns:
|
|
790
|
+
Mock associated with this expectation.
|
|
791
|
+
|
|
792
|
+
Examples:
|
|
793
|
+
>>> plane = flexmock(plane).should_receive("fly").mock()
|
|
794
|
+
"""
|
|
795
|
+
self._called_deprecated_property = False
|
|
796
|
+
return self._mock
|
|
797
|
+
|
|
798
|
+
def with_args(self, *args: Any, **kwargs: Any) -> "Expectation":
|
|
799
|
+
"""Override the arguments used to match this expectation's method.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
*args: Positional arguments.
|
|
803
|
+
**kwargs: Keyword arguments.
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
807
|
+
|
|
808
|
+
Examples:
|
|
809
|
+
Match calls with no arguments:
|
|
810
|
+
|
|
811
|
+
>>> flexmock(plane).should_receive("fly").with_args()
|
|
812
|
+
<flexmock._api.Expectation object at ...>
|
|
813
|
+
|
|
814
|
+
Match a single argument:
|
|
815
|
+
|
|
816
|
+
>>> flexmock(plane).should_receive("fly").with_args("east")
|
|
817
|
+
<flexmock._api.Expectation object at ...>
|
|
818
|
+
|
|
819
|
+
Match keyword arguments:
|
|
820
|
+
|
|
821
|
+
>>> flexmock(plane).should_receive("fly").with_args("up", destination="Oslo")
|
|
822
|
+
<flexmock._api.Expectation object at ...>
|
|
823
|
+
|
|
824
|
+
Match argument type:
|
|
825
|
+
|
|
826
|
+
>>> flexmock(plane).should_receive("fly").with_args(str)
|
|
827
|
+
<flexmock._api.Expectation object at ...>
|
|
828
|
+
|
|
829
|
+
Match a string using a compiled regular expression:
|
|
830
|
+
|
|
831
|
+
>>> regex = re.compile("^(up|down)$")
|
|
832
|
+
>>> flexmock(plane).should_receive("fly").with_args(regex)
|
|
833
|
+
<flexmock._api.Expectation object at ...>
|
|
834
|
+
"""
|
|
835
|
+
if not self._callable:
|
|
836
|
+
self.__raise(FlexmockError, "can't use with_args() with attribute stubs")
|
|
837
|
+
self._update_argspec()
|
|
838
|
+
if self._argspec:
|
|
839
|
+
# do this outside try block as TypeError is way too general and catches
|
|
840
|
+
# unrelated errors in the verify signature code
|
|
841
|
+
self._verify_signature_match(*args, **kwargs)
|
|
842
|
+
self._args = self._normalize_named_args(*args, **kwargs)
|
|
843
|
+
else:
|
|
844
|
+
self._args = {"kargs": args, "kwargs": kwargs}
|
|
845
|
+
return self
|
|
846
|
+
|
|
847
|
+
def and_return(self, *values: Any) -> "Expectation":
|
|
848
|
+
"""Override the return value of this expectation's method.
|
|
849
|
+
|
|
850
|
+
When `and_return` is given multiple times, each value provided is returned
|
|
851
|
+
on successive invocations of the method. It is also possible to mix
|
|
852
|
+
`and_return` with `and_raise` in the same manner to alternate between returning
|
|
853
|
+
a value and raising and exception on different method invocations.
|
|
854
|
+
|
|
855
|
+
When combined with the `one_by_one` modifier, value is treated as a list of
|
|
856
|
+
values to be returned in the order specified by successive calls to this
|
|
857
|
+
method rather than a single list to be returned each time.
|
|
858
|
+
|
|
859
|
+
Args:
|
|
860
|
+
*values: Optional list of return values, defaults to `None` if not given.
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
864
|
+
|
|
865
|
+
Examples:
|
|
866
|
+
Provide return values for mocks:
|
|
867
|
+
|
|
868
|
+
>>> flexmock(plane).should_receive("land").and_return("landed!").once()
|
|
869
|
+
<flexmock._api.Expectation object at ...>
|
|
870
|
+
>>> plane.land()
|
|
871
|
+
'landed!'
|
|
872
|
+
|
|
873
|
+
Match specific return values with spies:
|
|
874
|
+
|
|
875
|
+
>>> flexmock(plane).should_call("passenger_count").and_return(3)
|
|
876
|
+
<flexmock._api.Expectation object at ...>
|
|
877
|
+
>>> plane.passenger_count()
|
|
878
|
+
3
|
|
879
|
+
"""
|
|
880
|
+
if not values:
|
|
881
|
+
value = None
|
|
882
|
+
elif len(values) == 1:
|
|
883
|
+
value = values[0]
|
|
884
|
+
else:
|
|
885
|
+
value = values
|
|
886
|
+
|
|
887
|
+
if not self._callable:
|
|
888
|
+
_setattr(self._mock, str(self._name), value)
|
|
889
|
+
return self
|
|
890
|
+
|
|
891
|
+
return_values = self._return_values
|
|
892
|
+
if not self._one_by_one:
|
|
893
|
+
value = ReturnValue(value)
|
|
894
|
+
return_values.append(value)
|
|
895
|
+
else:
|
|
896
|
+
try:
|
|
897
|
+
return_values.extend([ReturnValue(v) for v in value]) # type: ignore
|
|
898
|
+
except TypeError:
|
|
899
|
+
return_values.append(ReturnValue(value))
|
|
900
|
+
return self
|
|
901
|
+
|
|
902
|
+
def times(self, number: int) -> "Expectation":
|
|
903
|
+
"""Number of times this expectation's method is expected to be called.
|
|
904
|
+
|
|
905
|
+
There are also 3 aliases for the `times()` method:
|
|
906
|
+
|
|
907
|
+
- `once()` -> `times(1)`
|
|
908
|
+
- `twice()` -> `times(2)`
|
|
909
|
+
- `never()` -> `times(0)`
|
|
910
|
+
|
|
911
|
+
Args:
|
|
912
|
+
number: Expected call count.
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
916
|
+
|
|
917
|
+
Examples:
|
|
918
|
+
>>> flexmock(plane).should_receive("fly").times(1)
|
|
919
|
+
<flexmock._api.Expectation object at ...>
|
|
920
|
+
>>> plane.fly()
|
|
921
|
+
|
|
922
|
+
>>> flexmock(plane).should_call("land").times(2)
|
|
923
|
+
<flexmock._api.Expectation object at ...>
|
|
924
|
+
>>> plane.land()
|
|
925
|
+
>>> plane.land()
|
|
926
|
+
"""
|
|
927
|
+
if not self._callable:
|
|
928
|
+
self.__raise(FlexmockError, "can't use times() with attribute stubs")
|
|
929
|
+
expected_calls = self._expected_calls
|
|
930
|
+
modifier = self._modifier
|
|
931
|
+
expected_calls[modifier] = number
|
|
932
|
+
return self
|
|
933
|
+
|
|
934
|
+
def once(self) -> "Expectation":
|
|
935
|
+
"""Expect expectation's method to be called once.
|
|
936
|
+
|
|
937
|
+
Alias for `times(1)`.
|
|
938
|
+
|
|
939
|
+
Returns:
|
|
940
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
941
|
+
|
|
942
|
+
Examples:
|
|
943
|
+
>>> flexmock(plane).should_receive("land").once()
|
|
944
|
+
<flexmock._api.Expectation object at ...>
|
|
945
|
+
>>> plane.land()
|
|
946
|
+
"""
|
|
947
|
+
self._called_deprecated_property = False
|
|
948
|
+
return self.times(1)
|
|
949
|
+
|
|
950
|
+
def twice(self) -> "Expectation":
|
|
951
|
+
"""Expect expectation's method to be called twice.
|
|
952
|
+
|
|
953
|
+
Alias for `times(2)`.
|
|
954
|
+
|
|
955
|
+
Returns:
|
|
956
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
957
|
+
|
|
958
|
+
Examples:
|
|
959
|
+
>>> flexmock(plane).should_receive("fly").twice()
|
|
960
|
+
<flexmock._api.Expectation object at ...>
|
|
961
|
+
>>> plane.fly()
|
|
962
|
+
>>> plane.fly()
|
|
963
|
+
"""
|
|
964
|
+
self._called_deprecated_property = False
|
|
965
|
+
return self.times(2)
|
|
966
|
+
|
|
967
|
+
def never(self) -> "Expectation":
|
|
968
|
+
"""Expect expectation's method to never be called.
|
|
969
|
+
|
|
970
|
+
Alias for `times(0)`.
|
|
971
|
+
|
|
972
|
+
Returns:
|
|
973
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
974
|
+
|
|
975
|
+
Examples:
|
|
976
|
+
>>> flexmock(plane).should_receive("crash").never()
|
|
977
|
+
<flexmock._api.Expectation object at ...>
|
|
978
|
+
>>> plane.crash()
|
|
979
|
+
Traceback (most recent call last):
|
|
980
|
+
...
|
|
981
|
+
flexmock.exceptions.MethodCallError: crash() expected to be called...
|
|
982
|
+
"""
|
|
983
|
+
self._called_deprecated_property = False
|
|
984
|
+
return self.times(0)
|
|
985
|
+
|
|
986
|
+
def one_by_one(self) -> "Expectation":
|
|
987
|
+
"""Modifies the return value to be treated as a list of return values.
|
|
988
|
+
|
|
989
|
+
Each value in the list is returned on successive invocations of the method.
|
|
990
|
+
|
|
991
|
+
Returns:
|
|
992
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
993
|
+
|
|
994
|
+
Examples:
|
|
995
|
+
>>> flexmock(plane).should_receive("pilot").and_return("pilot1", "pilot2").one_by_one()
|
|
996
|
+
<flexmock._api.Expectation object at ...>
|
|
997
|
+
>>> plane.pilot()
|
|
998
|
+
'pilot1'
|
|
999
|
+
>>> plane.pilot()
|
|
1000
|
+
'pilot2'
|
|
1001
|
+
"""
|
|
1002
|
+
if not self._callable:
|
|
1003
|
+
self.__raise(FlexmockError, "can't use one_by_one() with attribute stubs")
|
|
1004
|
+
if not self._one_by_one:
|
|
1005
|
+
self._one_by_one = True
|
|
1006
|
+
return_values = self._return_values
|
|
1007
|
+
saved_values = return_values[:]
|
|
1008
|
+
self._return_values = return_values = []
|
|
1009
|
+
for value in saved_values:
|
|
1010
|
+
try:
|
|
1011
|
+
for val in value.value: # type: ignore
|
|
1012
|
+
return_values.append(ReturnValue(val))
|
|
1013
|
+
except TypeError:
|
|
1014
|
+
return_values.append(value)
|
|
1015
|
+
return self
|
|
1016
|
+
|
|
1017
|
+
def at_least(self) -> "Expectation":
|
|
1018
|
+
"""Modifies the associated `times()` expectation.
|
|
1019
|
+
|
|
1020
|
+
When given, an exception will only be raised if the method is called less
|
|
1021
|
+
than `times()` specified. Does nothing if `times()` is not given.
|
|
1022
|
+
|
|
1023
|
+
Returns:
|
|
1024
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1025
|
+
|
|
1026
|
+
Examples:
|
|
1027
|
+
>>> flexmock(plane).should_receive("fly").at_least().once()
|
|
1028
|
+
<flexmock._api.Expectation object at ...>
|
|
1029
|
+
>>> plane.fly("east")
|
|
1030
|
+
"""
|
|
1031
|
+
if not self._callable:
|
|
1032
|
+
self.__raise(FlexmockError, "can't use at_least() with attribute stubs")
|
|
1033
|
+
expected_calls = self._expected_calls
|
|
1034
|
+
modifier = self._modifier
|
|
1035
|
+
if expected_calls[AT_LEAST] is not None or modifier == AT_LEAST:
|
|
1036
|
+
self.__raise(FlexmockError, "cannot use at_least modifier twice")
|
|
1037
|
+
if modifier == AT_MOST and expected_calls[AT_MOST] is None:
|
|
1038
|
+
self.__raise(FlexmockError, "cannot use at_least with at_most unset")
|
|
1039
|
+
self._modifier = AT_LEAST
|
|
1040
|
+
return self
|
|
1041
|
+
|
|
1042
|
+
def at_most(self) -> "Expectation":
|
|
1043
|
+
"""Modifies the associated `times()` expectation.
|
|
1044
|
+
|
|
1045
|
+
When given, an exception will only be raised if the method is called more
|
|
1046
|
+
than `times()` specified. Does nothing if `times()` is not given.
|
|
1047
|
+
|
|
1048
|
+
Returns:
|
|
1049
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1050
|
+
|
|
1051
|
+
Examples:
|
|
1052
|
+
>>> flexmock(plane).should_receive("land").at_most().once()
|
|
1053
|
+
<flexmock._api.Expectation object at ...>
|
|
1054
|
+
>>> plane.land()
|
|
1055
|
+
>>> plane.land()
|
|
1056
|
+
Traceback (most recent call last):
|
|
1057
|
+
...
|
|
1058
|
+
flexmock.exceptions.MethodCallError: land() expected to be called at most...
|
|
1059
|
+
"""
|
|
1060
|
+
if not self._callable:
|
|
1061
|
+
self.__raise(FlexmockError, "can't use at_most() with attribute stubs")
|
|
1062
|
+
expected_calls = self._expected_calls
|
|
1063
|
+
modifier = self._modifier
|
|
1064
|
+
if expected_calls[AT_MOST] is not None or modifier == AT_MOST:
|
|
1065
|
+
self.__raise(FlexmockError, "cannot use at_most modifier twice")
|
|
1066
|
+
if modifier == AT_LEAST and expected_calls[AT_LEAST] is None:
|
|
1067
|
+
self.__raise(FlexmockError, "cannot use at_most with at_least unset")
|
|
1068
|
+
self._modifier = AT_MOST
|
|
1069
|
+
return self
|
|
1070
|
+
|
|
1071
|
+
def ordered(self) -> "Expectation":
|
|
1072
|
+
"""Makes the expectation respect the order of `should_receive` statements.
|
|
1073
|
+
|
|
1074
|
+
An exception will be raised if methods are called out of order, determined
|
|
1075
|
+
by order of `should_receive` calls in the test.
|
|
1076
|
+
|
|
1077
|
+
Returns:
|
|
1078
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1079
|
+
|
|
1080
|
+
Examples:
|
|
1081
|
+
>>> flexmock(plane).should_receive("fly").with_args("east").ordered()
|
|
1082
|
+
<flexmock._api.Expectation object at ...>
|
|
1083
|
+
>>> flexmock(plane).should_receive("fly").with_args("west").ordered()
|
|
1084
|
+
<flexmock._api.Expectation object at ...>
|
|
1085
|
+
>>> plane.fly("west")
|
|
1086
|
+
Traceback (most recent call last):
|
|
1087
|
+
...
|
|
1088
|
+
flexmock.exceptions.CallOrderError: fly("west") called before...
|
|
1089
|
+
"""
|
|
1090
|
+
if not self._callable:
|
|
1091
|
+
self.__raise(FlexmockError, "can't use ordered() with attribute stubs")
|
|
1092
|
+
self._ordered = True
|
|
1093
|
+
FlexmockContainer.ordered.append(self)
|
|
1094
|
+
return self
|
|
1095
|
+
|
|
1096
|
+
def when(self, func: Callable[..., Any]) -> "Expectation":
|
|
1097
|
+
"""Sets an outside resource to be checked when asserting if a method
|
|
1098
|
+
should be called.
|
|
1099
|
+
|
|
1100
|
+
Args:
|
|
1101
|
+
func: Function that indicates if the mocked or spied method should
|
|
1102
|
+
have been called.
|
|
1103
|
+
|
|
1104
|
+
Returns:
|
|
1105
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1106
|
+
|
|
1107
|
+
Examples:
|
|
1108
|
+
>>> flexmock(plane).should_receive("land").when(lambda: plane.is_flying)
|
|
1109
|
+
<flexmock._api.Expectation object at ...>
|
|
1110
|
+
>>> plane.is_flying
|
|
1111
|
+
True
|
|
1112
|
+
>>> plane.land()
|
|
1113
|
+
>>> plane.is_flying = False
|
|
1114
|
+
>>> plane.land()
|
|
1115
|
+
Traceback (most recent call last):
|
|
1116
|
+
...
|
|
1117
|
+
flexmock.exceptions.StateError: land expected to be called when...
|
|
1118
|
+
"""
|
|
1119
|
+
if not self._callable:
|
|
1120
|
+
self.__raise(FlexmockError, "can't use when() with attribute stubs")
|
|
1121
|
+
if not hasattr(func, "__call__"):
|
|
1122
|
+
self.__raise(FlexmockError, "when() parameter must be callable")
|
|
1123
|
+
self._runnable = func
|
|
1124
|
+
return self
|
|
1125
|
+
|
|
1126
|
+
def and_raise(
|
|
1127
|
+
self, exception: Union[Type[BaseException], BaseException], *args: Any, **kwargs: Any
|
|
1128
|
+
) -> "Expectation":
|
|
1129
|
+
"""Specifies the exception to be raised when this expectation is met.
|
|
1130
|
+
|
|
1131
|
+
Args:
|
|
1132
|
+
exception: Exception class.
|
|
1133
|
+
*args: Positional arguments to pass to the exception.
|
|
1134
|
+
**kwargs: Keyword arguments to pass to the exception.
|
|
1135
|
+
|
|
1136
|
+
Returns:
|
|
1137
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1138
|
+
|
|
1139
|
+
Examples:
|
|
1140
|
+
Make a mocked method raise an exception instead of returning a value:
|
|
1141
|
+
|
|
1142
|
+
>>> flexmock(plane).should_receive("fly").and_raise(BadWeatherException)
|
|
1143
|
+
<flexmock._api.Expectation object at ...>
|
|
1144
|
+
>>> plane.fly()
|
|
1145
|
+
Traceback (most recent call last):
|
|
1146
|
+
...
|
|
1147
|
+
BadWeatherException
|
|
1148
|
+
|
|
1149
|
+
Make a spy to verify that a specific exception is raised:
|
|
1150
|
+
|
|
1151
|
+
>>> flexmock(plane).should_call("repair").and_raise(RuntimeError, "err msg")
|
|
1152
|
+
<flexmock._api.Expectation object at ...>
|
|
1153
|
+
"""
|
|
1154
|
+
if not self._callable:
|
|
1155
|
+
self.__raise(FlexmockError, "can't use and_raise() with attribute stubs")
|
|
1156
|
+
if inspect.isclass(exception):
|
|
1157
|
+
try:
|
|
1158
|
+
exception(*args, **kwargs)
|
|
1159
|
+
except TypeError:
|
|
1160
|
+
self.__raise(
|
|
1161
|
+
FlexmockError, f"can't initialize {exception} with the given arguments"
|
|
1162
|
+
)
|
|
1163
|
+
kargs = {"kargs": args, "kwargs": kwargs}
|
|
1164
|
+
return_values = self._return_values
|
|
1165
|
+
return_values.append(ReturnValue(raises=exception, value=kargs))
|
|
1166
|
+
return self
|
|
1167
|
+
|
|
1168
|
+
def replace_with(self, function: Callable[..., Any]) -> "Expectation":
|
|
1169
|
+
"""Gives a function to run instead of the mocked out one.
|
|
1170
|
+
|
|
1171
|
+
Args:
|
|
1172
|
+
function: A callable that is used to replace the original function.
|
|
1173
|
+
|
|
1174
|
+
Returns:
|
|
1175
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1176
|
+
|
|
1177
|
+
Examples:
|
|
1178
|
+
>>> flexmock(plane).should_receive("set_speed").replace_with(lambda x: x == 5)
|
|
1179
|
+
<flexmock._api.Expectation object at ...>
|
|
1180
|
+
>>> plane.set_speed(5)
|
|
1181
|
+
True
|
|
1182
|
+
>>> plane.set_speed(4)
|
|
1183
|
+
False
|
|
1184
|
+
"""
|
|
1185
|
+
if not self._callable:
|
|
1186
|
+
self.__raise(FlexmockError, "can't use replace_with() with attribute/property stubs")
|
|
1187
|
+
replace_with = self._replace_with
|
|
1188
|
+
original = self.__dict__.get("_original")
|
|
1189
|
+
if replace_with:
|
|
1190
|
+
self.__raise(FlexmockError, "replace_with cannot be specified twice")
|
|
1191
|
+
if function == original:
|
|
1192
|
+
self._pass_thru = True
|
|
1193
|
+
self._replace_with = function
|
|
1194
|
+
return self
|
|
1195
|
+
|
|
1196
|
+
def and_yield(self, *args: Any) -> "Expectation":
|
|
1197
|
+
"""Specifies the list of items to be yielded on successive method calls.
|
|
1198
|
+
|
|
1199
|
+
In effect, the mocked object becomes a generator.
|
|
1200
|
+
|
|
1201
|
+
Args:
|
|
1202
|
+
*args: Items to be yielded on successive method calls.
|
|
1203
|
+
|
|
1204
|
+
Returns:
|
|
1205
|
+
Self, i.e. can be chained with other Expectation methods.
|
|
1206
|
+
|
|
1207
|
+
Examples:
|
|
1208
|
+
>>> flexmock(plane).should_receive("flight_log").and_yield("fly", "land")
|
|
1209
|
+
<flexmock._api.Expectation object at ...>
|
|
1210
|
+
>>> log = plane.flight_log()
|
|
1211
|
+
>>> next(log)
|
|
1212
|
+
'fly'
|
|
1213
|
+
>>> next(log)
|
|
1214
|
+
'land'
|
|
1215
|
+
"""
|
|
1216
|
+
if not self._callable:
|
|
1217
|
+
self.__raise(FlexmockError, "can't use and_yield() with attribute stubs")
|
|
1218
|
+
return self.and_return(iter(args))
|
|
1219
|
+
|
|
1220
|
+
def _verify(self, final: bool = True) -> None:
|
|
1221
|
+
"""Verify that this expectation has been met.
|
|
1222
|
+
|
|
1223
|
+
Args:
|
|
1224
|
+
final: True if no further calls to this method expected
|
|
1225
|
+
(skip checking at_least expectations when False).
|
|
1226
|
+
|
|
1227
|
+
Raises:
|
|
1228
|
+
MethodCallError Exception
|
|
1229
|
+
"""
|
|
1230
|
+
failed, message = self._verify_number_of_calls(final)
|
|
1231
|
+
if failed and not self._verified:
|
|
1232
|
+
self._verified = True
|
|
1233
|
+
self.__raise(
|
|
1234
|
+
MethodCallError,
|
|
1235
|
+
(
|
|
1236
|
+
f"{_format_args(str(self._name), self._args)} expected to be called "
|
|
1237
|
+
f"{message}, called {self._times_called} "
|
|
1238
|
+
f"{'time' if self._times_called == 1 else 'times'}"
|
|
1239
|
+
),
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
def _verify_number_of_calls(self, final: bool) -> Tuple[bool, str]:
|
|
1243
|
+
if self._called_deprecated_property:
|
|
1244
|
+
raise FlexmockError(
|
|
1245
|
+
"Calling once, twice, never, or mock without parentheses has been deprecated"
|
|
1246
|
+
)
|
|
1247
|
+
failed = False
|
|
1248
|
+
message = ""
|
|
1249
|
+
called_exactly = self._expected_calls[EXACTLY]
|
|
1250
|
+
called_at_least = self._expected_calls[AT_LEAST]
|
|
1251
|
+
called_at_most = self._expected_calls[AT_MOST]
|
|
1252
|
+
times_called = self._times_called
|
|
1253
|
+
if called_exactly is not None:
|
|
1254
|
+
message = f"exactly {called_exactly}"
|
|
1255
|
+
if final:
|
|
1256
|
+
if times_called != called_exactly:
|
|
1257
|
+
failed = True
|
|
1258
|
+
else:
|
|
1259
|
+
if times_called > called_exactly:
|
|
1260
|
+
failed = True
|
|
1261
|
+
message += " time" if called_exactly == 1 else " times"
|
|
1262
|
+
else:
|
|
1263
|
+
if final and called_at_least is not None:
|
|
1264
|
+
message = f"at least {called_at_least}"
|
|
1265
|
+
if times_called < called_at_least:
|
|
1266
|
+
failed = True
|
|
1267
|
+
message += " time" if called_at_least == 1 else " times"
|
|
1268
|
+
if called_at_most is not None:
|
|
1269
|
+
if message:
|
|
1270
|
+
message += " and "
|
|
1271
|
+
message += f"at most {called_at_most}"
|
|
1272
|
+
if times_called > called_at_most:
|
|
1273
|
+
failed = True
|
|
1274
|
+
message += " time" if called_at_most == 1 else " times"
|
|
1275
|
+
return failed, message
|
|
1276
|
+
|
|
1277
|
+
def _reset(self) -> None:
|
|
1278
|
+
"""Returns the methods overridden by this expectation to their originals."""
|
|
1279
|
+
_mock = self._mock
|
|
1280
|
+
if not isinstance(_mock, Mock):
|
|
1281
|
+
original = self.__dict__.get("_original")
|
|
1282
|
+
if original:
|
|
1283
|
+
# name may be unicode but pypy demands dict keys to be str
|
|
1284
|
+
name = str(self._name)
|
|
1285
|
+
if hasattr(_mock, "__dict__") and name in _mock.__dict__ and self._local_override:
|
|
1286
|
+
delattr(_mock, name)
|
|
1287
|
+
elif (
|
|
1288
|
+
hasattr(_mock, "__dict__")
|
|
1289
|
+
and name in _mock.__dict__
|
|
1290
|
+
and isinstance(_mock.__dict__, dict)
|
|
1291
|
+
):
|
|
1292
|
+
_mock.__dict__[name] = original
|
|
1293
|
+
else:
|
|
1294
|
+
setattr(_mock, name, original)
|
|
1295
|
+
del self
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
class FlexmockContainer:
|
|
1299
|
+
"""Holds global hash of object/expectation mappings."""
|
|
1300
|
+
|
|
1301
|
+
flexmock_objects: Dict[Mock, List[Expectation]] = {}
|
|
1302
|
+
properties: Dict[Any, List[str]] = {}
|
|
1303
|
+
ordered: List[Expectation] = []
|
|
1304
|
+
last: Optional[Expectation] = None
|
|
1305
|
+
|
|
1306
|
+
@classmethod
|
|
1307
|
+
def reset(cls) -> None:
|
|
1308
|
+
"""Reset flexmock state."""
|
|
1309
|
+
cls.ordered = []
|
|
1310
|
+
cls.last = None
|
|
1311
|
+
cls.flexmock_objects = {}
|
|
1312
|
+
cls.properties = {}
|
|
1313
|
+
|
|
1314
|
+
@classmethod
|
|
1315
|
+
def get_flexmock_expectation(
|
|
1316
|
+
cls, obj: Mock, name: str, args: Optional[Any] = None
|
|
1317
|
+
) -> Optional[Expectation]:
|
|
1318
|
+
"""Retrieves an existing matching expectation."""
|
|
1319
|
+
if args is None:
|
|
1320
|
+
args = {"kargs": (), "kwargs": {}}
|
|
1321
|
+
if not isinstance(args, dict):
|
|
1322
|
+
args = {"kargs": args, "kwargs": {}}
|
|
1323
|
+
if not isinstance(args["kargs"], tuple):
|
|
1324
|
+
args["kargs"] = (args["kargs"],)
|
|
1325
|
+
found = None
|
|
1326
|
+
for expectation in reversed(cls.flexmock_objects[obj]):
|
|
1327
|
+
if expectation._name == name and expectation._match_args(args):
|
|
1328
|
+
if expectation in cls.ordered or not expectation._ordered and not found:
|
|
1329
|
+
found = expectation
|
|
1330
|
+
if found and found._ordered:
|
|
1331
|
+
cls._verify_call_order(found, args)
|
|
1332
|
+
return found
|
|
1333
|
+
|
|
1334
|
+
@classmethod
|
|
1335
|
+
def _verify_call_order(cls, expectation: Expectation, args: Dict[str, Any]) -> None:
|
|
1336
|
+
next_method = cls.ordered.pop(0)
|
|
1337
|
+
cls.last = next_method
|
|
1338
|
+
if expectation is not next_method:
|
|
1339
|
+
raise CallOrderError(
|
|
1340
|
+
f"{_format_args(str(expectation._name), args)} called before "
|
|
1341
|
+
f"{_format_args(str(next_method._name), next_method._args)}"
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
@classmethod
|
|
1345
|
+
def add_expectation(cls, obj: Mock, expectation: Expectation) -> None:
|
|
1346
|
+
"""Add expectation."""
|
|
1347
|
+
if obj in cls.flexmock_objects:
|
|
1348
|
+
cls.flexmock_objects[obj].append(expectation)
|
|
1349
|
+
else:
|
|
1350
|
+
cls.flexmock_objects[obj] = [expectation]
|
|
1351
|
+
|
|
1352
|
+
@classmethod
|
|
1353
|
+
def get_expectations_with_name(cls, obj: Mock, name: str) -> List[Expectation]:
|
|
1354
|
+
"""Get all expectations for given name."""
|
|
1355
|
+
return [x for x in FlexmockContainer.flexmock_objects.get(obj, []) if x._name == name]
|
|
1356
|
+
|
|
1357
|
+
@classmethod
|
|
1358
|
+
def add_teardown_property(cls, obj: Any, name: str) -> None:
|
|
1359
|
+
"""Add teardown property."""
|
|
1360
|
+
if obj in cls.properties:
|
|
1361
|
+
cls.properties[obj].append(name)
|
|
1362
|
+
else:
|
|
1363
|
+
cls.properties[obj] = [name]
|
|
1364
|
+
|
|
1365
|
+
@classmethod
|
|
1366
|
+
def teardown_properties(cls) -> None:
|
|
1367
|
+
"""Teardown properties."""
|
|
1368
|
+
for obj, names in cls.properties.items():
|
|
1369
|
+
for name in names:
|
|
1370
|
+
delattr(obj, name)
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
def flexmock(spec: Optional[Any] = None, **kwargs: Any) -> Mock:
|
|
1374
|
+
"""Main entry point into the flexmock API.
|
|
1375
|
+
|
|
1376
|
+
This function is used to either generate a new fake object or take
|
|
1377
|
+
an existing object (or class or module) and use it as a basis for
|
|
1378
|
+
a partial mock. In case of a partial mock, the passed in object
|
|
1379
|
+
is modified to support basic Mock class functionality making
|
|
1380
|
+
it unnecessary to make successive `flexmock()` calls on the same
|
|
1381
|
+
objects to generate new expectations.
|
|
1382
|
+
|
|
1383
|
+
It is safe to call `flexmock()` on the same object again, flexmock will
|
|
1384
|
+
detect when an object has already been partially mocked and return it each
|
|
1385
|
+
time.
|
|
1386
|
+
|
|
1387
|
+
Args:
|
|
1388
|
+
spec: Object, class, or module to mock.
|
|
1389
|
+
**kwargs: Method or return value pairs to attach to the object.
|
|
1390
|
+
|
|
1391
|
+
Returns:
|
|
1392
|
+
Mock object if no spec is provided. Otherwise return the spec object.
|
|
1393
|
+
|
|
1394
|
+
Examples:
|
|
1395
|
+
>>> mock = flexmock(Plane)
|
|
1396
|
+
>>> mock.should_receive("fly")
|
|
1397
|
+
<flexmock._api.Expectation object at ...>
|
|
1398
|
+
"""
|
|
1399
|
+
if spec is not None:
|
|
1400
|
+
return _create_partial_mock(spec, **kwargs)
|
|
1401
|
+
# use this intermediate class to attach properties
|
|
1402
|
+
klass = type("MockClass", (Mock,), {})
|
|
1403
|
+
return klass(**kwargs) # type: ignore
|
|
1404
|
+
|
|
1405
|
+
|
|
1406
|
+
def _arg_to_str(arg: Any) -> str:
|
|
1407
|
+
if isinstance(arg, RE_TYPE):
|
|
1408
|
+
return f"/{arg.pattern}/"
|
|
1409
|
+
if isinstance(arg, str):
|
|
1410
|
+
return f'"{arg}"'
|
|
1411
|
+
return f"{arg}"
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
def _format_args(name: str, arguments: Optional[Dict[str, Any]]) -> str:
|
|
1415
|
+
if arguments is None:
|
|
1416
|
+
arguments = {"kargs": (), "kwargs": {}}
|
|
1417
|
+
kargs = ", ".join(_arg_to_str(arg) for arg in arguments["kargs"])
|
|
1418
|
+
kwargs = ", ".join(f"{k}={_arg_to_str(v)}" for k, v in arguments["kwargs"].items())
|
|
1419
|
+
if kargs and kwargs:
|
|
1420
|
+
args = f"{kargs}, {kwargs}"
|
|
1421
|
+
else:
|
|
1422
|
+
args = f"{kargs}{kwargs}"
|
|
1423
|
+
return f"{name}({args})"
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
def _create_partial_mock(obj_or_class: Any, **kwargs: Any) -> Mock:
|
|
1427
|
+
"""Create partial mock."""
|
|
1428
|
+
matches = [x for x in FlexmockContainer.flexmock_objects if x._object is obj_or_class]
|
|
1429
|
+
if matches:
|
|
1430
|
+
mock = matches[0]
|
|
1431
|
+
else:
|
|
1432
|
+
mock = Mock()
|
|
1433
|
+
mock._object = obj_or_class
|
|
1434
|
+
for name, return_value in kwargs.items():
|
|
1435
|
+
if hasattr(return_value, "__call__"):
|
|
1436
|
+
mock.should_receive(name).replace_with(return_value)
|
|
1437
|
+
else:
|
|
1438
|
+
mock.should_receive(name).and_return(return_value)
|
|
1439
|
+
if not matches:
|
|
1440
|
+
FlexmockContainer.add_expectation(mock, Expectation(obj_or_class))
|
|
1441
|
+
if _attach_flexmock_methods(mock, Mock, obj_or_class) and not inspect.isclass(mock._object):
|
|
1442
|
+
mock = mock._object
|
|
1443
|
+
return mock
|
|
1444
|
+
|
|
1445
|
+
|
|
1446
|
+
def _attach_flexmock_methods(mock: Mock, flexmock_class: Type[Mock], obj: Any) -> bool:
|
|
1447
|
+
try:
|
|
1448
|
+
for attr in UPDATED_ATTRS:
|
|
1449
|
+
if hasattr(obj, attr):
|
|
1450
|
+
if getattr(obj, attr).__code__ is not getattr(flexmock_class, attr).__code__:
|
|
1451
|
+
return False
|
|
1452
|
+
for attr in UPDATED_ATTRS:
|
|
1453
|
+
_setattr(obj, attr, getattr(mock, attr))
|
|
1454
|
+
except TypeError as exc:
|
|
1455
|
+
raise MockBuiltinError(
|
|
1456
|
+
"Python does not allow you to mock builtin objects or modules. "
|
|
1457
|
+
"Consider wrapping it in a class you can mock instead"
|
|
1458
|
+
) from exc
|
|
1459
|
+
except AttributeError as exc:
|
|
1460
|
+
raise MockBuiltinError(
|
|
1461
|
+
"Python does not allow you to mock instances of builtin objects. "
|
|
1462
|
+
"Consider wrapping it in a class you can mock instead"
|
|
1463
|
+
) from exc
|
|
1464
|
+
return True
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
def _arguments_match(arg: Any, expected_arg: Any) -> bool:
|
|
1468
|
+
if expected_arg == arg:
|
|
1469
|
+
return True
|
|
1470
|
+
if inspect.isclass(expected_arg) and isinstance(arg, expected_arg):
|
|
1471
|
+
return True
|
|
1472
|
+
if isinstance(expected_arg, RE_TYPE) and expected_arg.search(arg):
|
|
1473
|
+
return True
|
|
1474
|
+
return False
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
def _setattr(obj: Any, name: str, value: Any) -> bool:
|
|
1478
|
+
"""Ensure we use local __dict__ where possible."""
|
|
1479
|
+
local_override = False
|
|
1480
|
+
if hasattr(obj, "__dict__") and isinstance(obj.__dict__, dict):
|
|
1481
|
+
if name not in obj.__dict__:
|
|
1482
|
+
# Overriding attribute locally on an instance.
|
|
1483
|
+
local_override = True
|
|
1484
|
+
obj.__dict__[name] = value
|
|
1485
|
+
else:
|
|
1486
|
+
if inspect.isclass(obj) and not vars(obj).get(name):
|
|
1487
|
+
# Overriding derived attribute locally on a child class.
|
|
1488
|
+
local_override = True
|
|
1489
|
+
setattr(obj, name, value)
|
|
1490
|
+
return local_override
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
def _isproperty(obj: Any, name: str) -> bool:
|
|
1494
|
+
if isinstance(obj, Mock):
|
|
1495
|
+
return False
|
|
1496
|
+
if not inspect.isclass(obj) and hasattr(obj, "__dict__") and name not in obj.__dict__:
|
|
1497
|
+
attr = getattr(obj.__class__, name)
|
|
1498
|
+
if isinstance(attr, property):
|
|
1499
|
+
return True
|
|
1500
|
+
elif inspect.isclass(obj):
|
|
1501
|
+
attr = getattr(obj, name)
|
|
1502
|
+
if isinstance(attr, property):
|
|
1503
|
+
return True
|
|
1504
|
+
return False
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
def _is_class_method(method: Callable[..., Any], name: str) -> bool:
|
|
1508
|
+
"""Check if a method is a classmethod.
|
|
1509
|
+
|
|
1510
|
+
This function checks all the classes in the class method resolution in order
|
|
1511
|
+
to get the correct result for derived methods as well.
|
|
1512
|
+
"""
|
|
1513
|
+
bound_to = getattr(method, "__self__", None)
|
|
1514
|
+
if not inspect.isclass(bound_to):
|
|
1515
|
+
return False
|
|
1516
|
+
for cls in inspect.getmro(bound_to):
|
|
1517
|
+
descriptor = vars(cls).get(name)
|
|
1518
|
+
if descriptor is not None:
|
|
1519
|
+
return isinstance(descriptor, classmethod)
|
|
1520
|
+
return False
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
def _is_static_method(obj: Any, name: str) -> bool:
|
|
1524
|
+
try:
|
|
1525
|
+
return isinstance(inspect.getattr_static(obj, name), staticmethod)
|
|
1526
|
+
except AttributeError:
|
|
1527
|
+
# AttributeError is raised when mocking a proxied object
|
|
1528
|
+
if hasattr(obj, "__mro__"):
|
|
1529
|
+
for cls in inspect.getmro(obj):
|
|
1530
|
+
descriptor = vars(cls).get(name)
|
|
1531
|
+
if descriptor is not None:
|
|
1532
|
+
return isinstance(descriptor, staticmethod)
|
|
1533
|
+
return False
|