-
Notifications
You must be signed in to change notification settings - Fork 11
/
expectation.py
252 lines (207 loc) · 7.25 KB
/
expectation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# -*- coding: utf-8 -*-
'''
Expectations that can set on a stub.
'''
import inspect
from comparators import *
from exception import *
from termcolor import colored
class ExpectationRule(object):
def __init__(self, *args, **kwargs):
self._passed = False
def validate(self, *args, **kwargs):
raise NotImplementedError("Must be implemented by subclasses")
class ArgumentsExpectationRule(ExpectationRule):
def __init__(self, *args, **kwargs):
super(ArgumentsExpectationRule, self).__init__(*args, **kwargs)
self.set_args( *args, **kwargs )
def set_args(self, *args, **kwargs):
self.args = []
self.kwargs = {}
# Convert all of the arguments to comparators
self.args = build_comparators(*args)
self.kwargs = dict([(k, build_comparators(v)[0]) for k, v in kwargs.iteritems()])
def validate(self, *args, **kwargs):
self.in_args = args[:]
self.in_kwargs = kwargs.copy()
# First just check that the number of arguments is the same or different
if len(args)!=len(self.args) or len(kwargs)!=len(self.kwargs):
self._passed = False
return False
for x in xrange(len(self.args)):
if not self.args[x].test( args[x] ):
self._passed = False
return False
for arg_name,arg_test in self.kwargs.iteritems():
try:
value=kwargs.pop(arg_name)
except KeyError:
self._passed = False
return False
if not arg_test.test( value ):
self._passed = False
return False
# If there are arguments left over, is error
if len(kwargs):
self._passed = False
return False
self._passed = True
return self._passed
@classmethod
def pretty_format_args(self, *args, **kwargs):
"""
Take the args, and kwargs that are passed them and format in a prototype style.
"""
args = list([repr(a) for a in args])
for key, value in kwargs.iteritems():
args.append("%s=%s" % (key, repr(value)))
return "(%s)" % ", ".join([a for a in args])
def __str__(self):
if hasattr(self, 'in_args') and hasattr(self, 'in_kwargs'):
return "\tExpected: %s\n\t\t Used: %s" % \
(self.pretty_format_args(*self.args, **self.kwargs), self.pretty_format_args(*self.in_args, **self.in_kwargs))
return "\tExpected: %s" % \
(self.pretty_format_args(*self.args, **self.kwargs))
class Expectation(object):
'''
Encapsulate an expectation.
'''
def __init__(self, stub):
self._met = False
self._stub = stub
self._arguments_rule = ArgumentsExpectationRule()
self._raises = None
self._returns = None
self._max_count = self._min_count = 1
self._run_count = 0
self._any_order = False
self._side_effect = False
self._side_effect_args = None
self._side_effect_kwargs = None
self._teardown = False
self._any_args = False
# Support expectations as context managers. See
# https://github.com/agoragames/chai/issues/1
def __enter__(self):
return self._returns
def __exit__(*args):
pass
def args(self, *args, **kwargs):
"""
Creates a ArgumentsExpectationRule and adds it to the expectation
"""
self._any_args = False
self._arguments_rule.set_args(*args, **kwargs)
return self
def any_args(self):
'''
Accept any arguments passed to this call.
'''
self._any_args = True
return self
def returns(self, value):
"""
What this expectation should return
"""
self._returns = value
return self
def raises(self, exception):
"""
Adds a raises to the expectation, this will be raised when the expectation is met.
This can be either the exception class or instance of a exception
"""
self._raises = exception
return self
def times(self, count):
self._min_count = self._max_count = count
return self
def at_least(self, min_count):
self._min_count = min_count
self._max_count = None
return self
def at_least_once(self):
self.at_least(1)
return self
def at_most(self, max_count):
self._max_count = max_count
return self
def at_most_once(self):
self.at_most(1)
return self
def once(self):
self._min_count = 1
self._max_count = 1
return self
def any_order(self):
self._any_order = True
return self
def side_effect(self, func, *args, **kwargs):
self._side_effect = func
self._side_effect_args = args
self._side_effect_kwargs = kwargs
return self
def teardown(self):
self._teardown = True
return self
def return_value(self):
"""
Returns the value for this expectation or raises the proper exception.
"""
if self._raises:
# Handle exceptions
if inspect.isclass(self._raises):
raise self._raises()
else:
raise self._raises
else:
if isinstance(self._returns, tuple):
return tuple([x.value if isinstance(x,Variable) else x for x in self._returns])
return self._returns.value if isinstance(self._returns,Variable) else self._returns
def close(self, *args, **kwargs):
'''
Mark this expectation as closed. It will no longer be used for matches.
'''
# If any_order, then this effectively is never closed. The Stub.__call__
# will just bypass it when it doesn't match. If there is a strict count
# it will also be bypassed, but if there's just a min set up, then it'll
# effectively stay open and catch any matching call no matter the order
if not self._any_order:
self._met = True
def closed(self, with_counts=False):
rval = self._met
if with_counts:
rval = rval or self.counts_met()
return rval
def counts_met(self):
return self._run_count >= self._min_count and not (self._max_count and not self._max_count == self._run_count)
def match(self, *args, **kwargs):
"""
Check the if these args match this expectation.
"""
return self._any_args or self._arguments_rule.validate(*args, **kwargs)
def test(self, *args, **kwargs):
"""
Validate all the rules with in this expectation to see if this expectation has been met.
"""
if not self._met:
if self.match(*args, **kwargs):
self._run_count += 1
if not self._max_count == None and self._run_count == self._max_count:
self._met = True
if self._side_effect:
if self._side_effect_args or self._side_effect_kwargs:
self._side_effect(*self._side_effect_args, **self._side_effect_kwargs)
else:
self._side_effect(*args, **kwargs)
else:
self._met = False
# If this is met and we're supposed to tear down, must do it now so that
# this stub is not called again
if self._met and self._teardown:
self._stub.teardown()
return self.return_value()
def __str__(self):
runs_string = " Ran: %s, Min Runs: %s, Max Runs: %s" % (self._run_count, self._min_count, "∞" if self._max_count == None else self._max_count)
return_string = " Raises: %s" % self._raises if self._raises else " Returns: %s" % repr(self._returns)
return "\n\t%s\n\t%s\n\t\t%s\n\t\t%s" % (colored("%s - %s" % (self._stub.name, "Passed" if self._arguments_rule._passed else "Failed")
, "green" if self._arguments_rule._passed else "red"), self._arguments_rule, return_string, runs_string)