assertpy2 2.0.0__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.
- assertpy2/__init__.py +25 -0
- assertpy2/assertpy.py +468 -0
- assertpy2/base.py +435 -0
- assertpy2/collection.py +217 -0
- assertpy2/contains.py +386 -0
- assertpy2/date.py +219 -0
- assertpy2/dict.py +255 -0
- assertpy2/dynamic.py +113 -0
- assertpy2/exception.py +128 -0
- assertpy2/extracting.py +233 -0
- assertpy2/file.py +276 -0
- assertpy2/helpers.py +273 -0
- assertpy2/numeric.py +555 -0
- assertpy2/py.typed +0 -0
- assertpy2/snapshot.py +217 -0
- assertpy2/string.py +413 -0
- assertpy2-2.0.0.dist-info/METADATA +227 -0
- assertpy2-2.0.0.dist-info/RECORD +20 -0
- assertpy2-2.0.0.dist-info/WHEEL +4 -0
- assertpy2-2.0.0.dist-info/licenses/LICENSE +28 -0
assertpy2/base.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
# are permitted provided that the following conditions are met:
|
|
6
|
+
#
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
# list of conditions and the following disclaimer.
|
|
9
|
+
#
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
# and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
|
16
|
+
# specific prior written permission.
|
|
17
|
+
#
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from typing import TYPE_CHECKING
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from typing_extensions import Self
|
|
35
|
+
|
|
36
|
+
__tracebackhide__ = True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BaseMixin:
|
|
40
|
+
"""Base mixin."""
|
|
41
|
+
|
|
42
|
+
def described_as(self, description) -> Self:
|
|
43
|
+
"""Describes the assertion. On failure, the description is included in the error message.
|
|
44
|
+
|
|
45
|
+
This is not an assertion itself. But if the any of the following chained assertions fail,
|
|
46
|
+
the description will be included in addition to the regular error message.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
description: the error message description
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
Usage::
|
|
53
|
+
|
|
54
|
+
assert_that(1).described_as('error msg desc').is_equal_to(2) # fails
|
|
55
|
+
# [error msg desc] Expected <1> to be equal to <2>, but was not.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
59
|
+
"""
|
|
60
|
+
self.description = str(description)
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def is_equal_to(self, other, **kwargs) -> Self:
|
|
64
|
+
"""Asserts that val is equal to other.
|
|
65
|
+
|
|
66
|
+
Checks actual is equal to expected using the ``==`` operator. When val is *dict-like*,
|
|
67
|
+
optionally ignore or include keys when checking equality.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
other: the expected value
|
|
71
|
+
**kwargs: see below
|
|
72
|
+
|
|
73
|
+
Keyword Args:
|
|
74
|
+
ignore: the dict key (or list of keys) to ignore
|
|
75
|
+
include: the dict key (of list of keys) to include
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
Usage::
|
|
79
|
+
|
|
80
|
+
assert_that(1 + 2).is_equal_to(3)
|
|
81
|
+
assert_that('foo').is_equal_to('foo')
|
|
82
|
+
assert_that(123).is_equal_to(123)
|
|
83
|
+
assert_that(123.4).is_equal_to(123.4)
|
|
84
|
+
assert_that(['a', 'b']).is_equal_to(['a', 'b'])
|
|
85
|
+
assert_that((1, 2, 3)).is_equal_to((1, 2, 3))
|
|
86
|
+
assert_that({'a': 1, 'b': 2}).is_equal_to({'a': 1, 'b': 2})
|
|
87
|
+
assert_that({'a', 'b'}).is_equal_to({'a', 'b'})
|
|
88
|
+
|
|
89
|
+
When the val is *dict-like*, keys can optionally be *ignored* when checking equality::
|
|
90
|
+
|
|
91
|
+
# ignore a single key
|
|
92
|
+
assert_that({'a': 1, 'b': 2}).is_equal_to({'a': 1}, ignore='b')
|
|
93
|
+
|
|
94
|
+
# ignore multiple keys
|
|
95
|
+
assert_that({'a': 1, 'b': 2, 'c': 3}).is_equal_to({'a': 1}, ignore=['b', 'c'])
|
|
96
|
+
|
|
97
|
+
# ignore nested keys
|
|
98
|
+
assert_that({'a': {'b': 2, 'c': 3, 'd': 4}}).is_equal_to({'a': {'d': 4}}, ignore=[('a', 'b'), ('a', 'c')])
|
|
99
|
+
|
|
100
|
+
When the val is *dict-like*, only certain keys can be *included* when checking equality::
|
|
101
|
+
|
|
102
|
+
# include a single key
|
|
103
|
+
assert_that({'a': 1, 'b': 2}).is_equal_to({'a': 1}, include='a')
|
|
104
|
+
|
|
105
|
+
# include multiple keys
|
|
106
|
+
assert_that({'a': 1, 'b': 2, 'c': 3}).is_equal_to({'a': 1, 'b': 2}, include=['a', 'b'])
|
|
107
|
+
|
|
108
|
+
Failure produces a nice error message::
|
|
109
|
+
|
|
110
|
+
assert_that(1).is_equal_to(2) # fails
|
|
111
|
+
# Expected <1> to be equal to <2>, but was not.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
AssertionError: if actual is **not** equal to expected
|
|
118
|
+
|
|
119
|
+
Tip:
|
|
120
|
+
Using :meth:`is_equal_to` with a ``float`` val is just asking for trouble. Instead, you'll
|
|
121
|
+
always want to use *fuzzy* numeric assertions like :meth:`~assertpy.numeric.NumericMixin.is_close_to`
|
|
122
|
+
or :meth:`~assertpy.numeric.NumericMixin.is_between`.
|
|
123
|
+
|
|
124
|
+
See Also:
|
|
125
|
+
:meth:`~assertpy.string.StringMixin.is_equal_to_ignoring_case` - for case-insensitive string equality
|
|
126
|
+
"""
|
|
127
|
+
if self._check_dict_like(self.val, check_values=False, return_as_bool=True) and self._check_dict_like(
|
|
128
|
+
other, check_values=False, return_as_bool=True
|
|
129
|
+
):
|
|
130
|
+
if self._dict_not_equal(self.val, other, ignore=kwargs.get("ignore"), include=kwargs.get("include")):
|
|
131
|
+
self._dict_err(self.val, other, ignore=kwargs.get("ignore"), include=kwargs.get("include"))
|
|
132
|
+
else:
|
|
133
|
+
if self.val != other:
|
|
134
|
+
return self.error("Expected <%s> to be equal to <%s>, but was not." % (self.val, other))
|
|
135
|
+
return self
|
|
136
|
+
|
|
137
|
+
def is_not_equal_to(self, other) -> Self:
|
|
138
|
+
"""Asserts that val is not equal to other.
|
|
139
|
+
|
|
140
|
+
Checks actual is not equal to expected using the ``!=`` operator.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
other: the expected value
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
Usage::
|
|
147
|
+
|
|
148
|
+
assert_that(1 + 2).is_not_equal_to(4)
|
|
149
|
+
assert_that('foo').is_not_equal_to('bar')
|
|
150
|
+
assert_that(123).is_not_equal_to(456)
|
|
151
|
+
assert_that(123.4).is_not_equal_to(567.8)
|
|
152
|
+
assert_that(['a', 'b']).is_not_equal_to(['c', 'd'])
|
|
153
|
+
assert_that((1, 2, 3)).is_not_equal_to((1, 2, 4))
|
|
154
|
+
assert_that({'a': 1, 'b': 2}).is_not_equal_to({'a': 1, 'b': 3})
|
|
155
|
+
assert_that({'a', 'b'}).is_not_equal_to({'a', 'x'})
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
AssertionError: if actual **is** equal to expected
|
|
162
|
+
"""
|
|
163
|
+
if self.val == other:
|
|
164
|
+
return self.error("Expected <%s> to be not equal to <%s>, but was." % (self.val, other))
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def is_same_as(self, other) -> Self:
|
|
168
|
+
"""Asserts that val is identical to other.
|
|
169
|
+
|
|
170
|
+
Checks actual is identical to expected using the ``is`` operator.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
other: the expected value
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
Basic types are identical::
|
|
177
|
+
|
|
178
|
+
assert_that(1).is_same_as(1)
|
|
179
|
+
assert_that('foo').is_same_as('foo')
|
|
180
|
+
assert_that(123.4).is_same_as(123.4)
|
|
181
|
+
|
|
182
|
+
As are immutables like ``tuple``::
|
|
183
|
+
|
|
184
|
+
assert_that((1, 2, 3)).is_same_as((1, 2, 3))
|
|
185
|
+
|
|
186
|
+
But mutable collections like ``list``, ``dict``, and ``set`` are not::
|
|
187
|
+
|
|
188
|
+
# these all fail...
|
|
189
|
+
assert_that(['a', 'b']).is_same_as(['a', 'b']) # fails
|
|
190
|
+
assert_that({'a': 1, 'b': 2}).is_same_as({'a': 1, 'b': 2}) # fails
|
|
191
|
+
assert_that({'a', 'b'}).is_same_as({'a', 'b'}) # fails
|
|
192
|
+
|
|
193
|
+
Unless they are the same object::
|
|
194
|
+
|
|
195
|
+
x = {'a': 1, 'b': 2}
|
|
196
|
+
y = x
|
|
197
|
+
assert_that(x).is_same_as(y)
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
AssertionError: if actual is **not** identical to expected
|
|
204
|
+
"""
|
|
205
|
+
if self.val is not other:
|
|
206
|
+
return self.error("Expected <%s> to be identical to <%s>, but was not." % (self.val, other))
|
|
207
|
+
return self
|
|
208
|
+
|
|
209
|
+
def is_not_same_as(self, other) -> Self:
|
|
210
|
+
"""Asserts that val is not identical to other.
|
|
211
|
+
|
|
212
|
+
Checks actual is not identical to expected using the ``is`` operator.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
other: the expected value
|
|
216
|
+
|
|
217
|
+
Examples:
|
|
218
|
+
Usage::
|
|
219
|
+
|
|
220
|
+
assert_that(1).is_not_same_as(2)
|
|
221
|
+
assert_that('foo').is_not_same_as('bar')
|
|
222
|
+
assert_that(123.4).is_not_same_as(567.8)
|
|
223
|
+
assert_that((1, 2, 3)).is_not_same_as((1, 2, 4))
|
|
224
|
+
|
|
225
|
+
# mutable collections, even if equal, are not identical...
|
|
226
|
+
assert_that(['a', 'b']).is_not_same_as(['a', 'b'])
|
|
227
|
+
assert_that({'a': 1, 'b': 2}).is_not_same_as({'a': 1, 'b': 2})
|
|
228
|
+
assert_that({'a', 'b'}).is_not_same_as({'a', 'b'})
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
232
|
+
|
|
233
|
+
Raises:
|
|
234
|
+
AssertionError: if actual **is** identical to expected
|
|
235
|
+
"""
|
|
236
|
+
if self.val is other:
|
|
237
|
+
return self.error("Expected <%s> to be not identical to <%s>, but was." % (self.val, other))
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def is_true(self) -> Self:
|
|
241
|
+
"""Asserts that val is true.
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
Usage::
|
|
245
|
+
|
|
246
|
+
assert_that(True).is_true()
|
|
247
|
+
assert_that(1).is_true()
|
|
248
|
+
assert_that('foo').is_true()
|
|
249
|
+
assert_that(1.0).is_true()
|
|
250
|
+
assert_that(['a', 'b']).is_true()
|
|
251
|
+
assert_that((1, 2, 3)).is_true()
|
|
252
|
+
assert_that({'a': 1, 'b': 2}).is_true()
|
|
253
|
+
assert_that({'a', 'b'}).is_true()
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
AssertionError: if val **is** false
|
|
260
|
+
"""
|
|
261
|
+
if not self.val:
|
|
262
|
+
return self.error("Expected <%s> to be <True>, but was not." % self.val)
|
|
263
|
+
return self
|
|
264
|
+
|
|
265
|
+
def is_false(self) -> Self:
|
|
266
|
+
"""Asserts that val is false.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
Usage::
|
|
270
|
+
|
|
271
|
+
assert_that(False).is_false()
|
|
272
|
+
assert_that(0).is_false()
|
|
273
|
+
assert_that('').is_false()
|
|
274
|
+
assert_that(0.0).is_false()
|
|
275
|
+
assert_that([]).is_false()
|
|
276
|
+
assert_that(()).is_false()
|
|
277
|
+
assert_that({}).is_false()
|
|
278
|
+
assert_that(set()).is_false()
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
282
|
+
|
|
283
|
+
Raises:
|
|
284
|
+
AssertionError: if val **is** true
|
|
285
|
+
"""
|
|
286
|
+
if self.val:
|
|
287
|
+
return self.error("Expected <%s> to be <False>, but was not." % self.val)
|
|
288
|
+
return self
|
|
289
|
+
|
|
290
|
+
def is_none(self) -> Self:
|
|
291
|
+
"""Asserts that val is none.
|
|
292
|
+
|
|
293
|
+
Examples:
|
|
294
|
+
Usage::
|
|
295
|
+
|
|
296
|
+
assert_that(None).is_none()
|
|
297
|
+
assert_that(print('hello world')).is_none()
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
AssertionError: if val is **not** none
|
|
304
|
+
"""
|
|
305
|
+
if self.val is not None:
|
|
306
|
+
return self.error("Expected <%s> to be <None>, but was not." % self.val)
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
def is_not_none(self) -> Self:
|
|
310
|
+
"""Asserts that val is not none.
|
|
311
|
+
|
|
312
|
+
Examples:
|
|
313
|
+
Usage::
|
|
314
|
+
|
|
315
|
+
assert_that(0).is_not_none()
|
|
316
|
+
assert_that('foo').is_not_none()
|
|
317
|
+
assert_that(False).is_not_none()
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
AssertionError: if val **is** none
|
|
324
|
+
"""
|
|
325
|
+
if self.val is None:
|
|
326
|
+
return self.error("Expected not <None>, but was.")
|
|
327
|
+
return self
|
|
328
|
+
|
|
329
|
+
def _type(self, val):
|
|
330
|
+
if hasattr(val, "__name__"):
|
|
331
|
+
return val.__name__
|
|
332
|
+
return val.__class__.__name__
|
|
333
|
+
|
|
334
|
+
def is_type_of(self, some_type) -> Self:
|
|
335
|
+
"""Asserts that val is of the given type.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
some_type (type): the expected type
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
Usage::
|
|
342
|
+
|
|
343
|
+
assert_that(1).is_type_of(int)
|
|
344
|
+
assert_that('foo').is_type_of(str)
|
|
345
|
+
assert_that(123.4).is_type_of(float)
|
|
346
|
+
assert_that(['a', 'b']).is_type_of(list)
|
|
347
|
+
assert_that((1, 2, 3)).is_type_of(tuple)
|
|
348
|
+
assert_that({'a': 1, 'b': 2}).is_type_of(dict)
|
|
349
|
+
assert_that({'a', 'b'}).is_type_of(set)
|
|
350
|
+
assert_that(True).is_type_of(bool)
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
354
|
+
|
|
355
|
+
Raises:
|
|
356
|
+
AssertionError: if val is **not** of the given type
|
|
357
|
+
"""
|
|
358
|
+
if type(some_type) is not type and not issubclass(type(some_type), type):
|
|
359
|
+
raise TypeError("given arg must be a type")
|
|
360
|
+
if type(self.val) is not some_type:
|
|
361
|
+
t = self._type(self.val)
|
|
362
|
+
return self.error("Expected <%s:%s> to be of type <%s>, but was not." % (self.val, t, some_type.__name__))
|
|
363
|
+
return self
|
|
364
|
+
|
|
365
|
+
def is_instance_of(self, some_class) -> Self:
|
|
366
|
+
"""Asserts that val is an instance of the given class.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
some_class: the expected class
|
|
370
|
+
|
|
371
|
+
Examples:
|
|
372
|
+
Usage::
|
|
373
|
+
|
|
374
|
+
assert_that(1).is_instance_of(int)
|
|
375
|
+
assert_that('foo').is_instance_of(str)
|
|
376
|
+
assert_that(123.4).is_instance_of(float)
|
|
377
|
+
assert_that(['a', 'b']).is_instance_of(list)
|
|
378
|
+
assert_that((1, 2, 3)).is_instance_of(tuple)
|
|
379
|
+
assert_that({'a': 1, 'b': 2}).is_instance_of(dict)
|
|
380
|
+
assert_that({'a', 'b'}).is_instance_of(set)
|
|
381
|
+
assert_that(True).is_instance_of(bool)
|
|
382
|
+
|
|
383
|
+
With a user-defined class::
|
|
384
|
+
|
|
385
|
+
class Foo: pass
|
|
386
|
+
f = Foo()
|
|
387
|
+
assert_that(f).is_instance_of(Foo)
|
|
388
|
+
assert_that(f).is_instance_of(object)
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
392
|
+
|
|
393
|
+
Raises:
|
|
394
|
+
AssertionError: if val is **not** an instance of the given class
|
|
395
|
+
"""
|
|
396
|
+
try:
|
|
397
|
+
if not isinstance(self.val, some_class):
|
|
398
|
+
t = self._type(self.val)
|
|
399
|
+
return self.error(
|
|
400
|
+
"Expected <%s:%s> to be instance of class <%s>, but was not." % (self.val, t, some_class.__name__)
|
|
401
|
+
)
|
|
402
|
+
except TypeError:
|
|
403
|
+
raise TypeError("given arg must be a class") from None
|
|
404
|
+
return self
|
|
405
|
+
|
|
406
|
+
def is_length(self, length) -> Self:
|
|
407
|
+
"""Asserts that val is the given length.
|
|
408
|
+
|
|
409
|
+
Checks val is the given length using the ``len()`` built-in.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
length (int): the expected length
|
|
413
|
+
|
|
414
|
+
Examples:
|
|
415
|
+
Usage::
|
|
416
|
+
|
|
417
|
+
assert_that('foo').is_length(3)
|
|
418
|
+
assert_that(['a', 'b']).is_length(2)
|
|
419
|
+
assert_that((1, 2, 3)).is_length(3)
|
|
420
|
+
assert_that({'a': 1, 'b': 2}).is_length(2)
|
|
421
|
+
assert_that({'a', 'b'}).is_length(2)
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
425
|
+
|
|
426
|
+
Raises:
|
|
427
|
+
AssertionError: if val is **not** the given length
|
|
428
|
+
"""
|
|
429
|
+
if type(length) is not int:
|
|
430
|
+
raise TypeError("given arg must be an int")
|
|
431
|
+
if length < 0:
|
|
432
|
+
raise ValueError("given arg must be a positive int")
|
|
433
|
+
if len(self.val) != length:
|
|
434
|
+
return self.error("Expected <%s> to be of length <%d>, but was <%d>." % (self.val, length, len(self.val)))
|
|
435
|
+
return self
|
assertpy2/collection.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
# are permitted provided that the following conditions are met:
|
|
6
|
+
#
|
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
# list of conditions and the following disclaimer.
|
|
9
|
+
#
|
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
# and/or other materials provided with the distribution.
|
|
13
|
+
#
|
|
14
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
|
16
|
+
# specific prior written permission.
|
|
17
|
+
#
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import collections
|
|
32
|
+
from typing import TYPE_CHECKING
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from typing_extensions import Self
|
|
36
|
+
|
|
37
|
+
__tracebackhide__ = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CollectionMixin:
|
|
41
|
+
"""Collection assertions mixin."""
|
|
42
|
+
|
|
43
|
+
def is_iterable(self) -> Self:
|
|
44
|
+
"""Asserts that val is iterable.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
Usage::
|
|
48
|
+
|
|
49
|
+
assert_that('foo').is_iterable()
|
|
50
|
+
assert_that(['a', 'b']).is_iterable()
|
|
51
|
+
assert_that((1, 2, 3)).is_iterable()
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
AssertionError: if val is **not** iterable
|
|
58
|
+
"""
|
|
59
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
60
|
+
return self.error("Expected iterable, but was not.")
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def is_not_iterable(self) -> Self:
|
|
64
|
+
"""Asserts that val is not iterable.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
Usage::
|
|
68
|
+
|
|
69
|
+
assert_that(1).is_not_iterable()
|
|
70
|
+
assert_that(123.4).is_not_iterable()
|
|
71
|
+
assert_that(True).is_not_iterable()
|
|
72
|
+
assert_that(None).is_not_iterable()
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
AssertionError: if val **is** iterable
|
|
79
|
+
"""
|
|
80
|
+
if isinstance(self.val, collections.abc.Iterable):
|
|
81
|
+
return self.error("Expected not iterable, but was.")
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def is_subset_of(self, *supersets) -> Self:
|
|
85
|
+
"""Asserts that val is iterable and a subset of the given superset (or supersets).
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
*supersets: the expected superset (or supersets)
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
Usage::
|
|
92
|
+
|
|
93
|
+
assert_that('foo').is_subset_of('abcdefghijklmnopqrstuvwxyz')
|
|
94
|
+
assert_that(['a', 'b']).is_subset_of(['a', 'b', 'c'])
|
|
95
|
+
assert_that((1, 2, 3)).is_subset_of([1, 2, 3, 4])
|
|
96
|
+
assert_that({'a': 1, 'b': 2}).is_subset_of({'a': 1, 'b': 2, 'c': 3})
|
|
97
|
+
assert_that({'a', 'b'}).is_subset_of({'a', 'b', 'c'})
|
|
98
|
+
|
|
99
|
+
# or multiple supersets (as comma-separated args)
|
|
100
|
+
assert_that('aBc').is_subset_of('abc', 'ABC')
|
|
101
|
+
assert_that((1, 2, 3)).is_subset_of([1, 3, 5], [2, 4, 6])
|
|
102
|
+
|
|
103
|
+
assert_that({'a': 1, 'b': 2}).is_subset_of({'a': 1, 'c': 3}) # fails
|
|
104
|
+
# Expected <{'a': 1, 'b': 2}> to be subset of <{'a': 1, 'c': 3}>, but <{'b': 2}> was missing.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
AssertionError: if val is **not** subset of given superset (or supersets)
|
|
111
|
+
"""
|
|
112
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
113
|
+
raise TypeError("val is not iterable")
|
|
114
|
+
if len(supersets) == 0:
|
|
115
|
+
raise ValueError("one or more superset args must be given")
|
|
116
|
+
|
|
117
|
+
missing = []
|
|
118
|
+
if hasattr(self.val, "keys") and callable(self.val.keys) and hasattr(self.val, "__getitem__"):
|
|
119
|
+
# flatten superset dicts
|
|
120
|
+
superdict = {}
|
|
121
|
+
for idx, j in enumerate(supersets):
|
|
122
|
+
self._check_dict_like(j, check_values=False, name="arg #%d" % (idx + 1))
|
|
123
|
+
for k in j:
|
|
124
|
+
superdict.update({k: j[k]})
|
|
125
|
+
|
|
126
|
+
for i in self.val:
|
|
127
|
+
if i not in superdict:
|
|
128
|
+
missing.append({i: self.val[i]}) # bad key
|
|
129
|
+
elif self.val[i] != superdict[i]:
|
|
130
|
+
missing.append({i: self.val[i]}) # bad val
|
|
131
|
+
if missing:
|
|
132
|
+
return self.error(
|
|
133
|
+
"Expected <%s> to be subset of %s, but %s %s missing."
|
|
134
|
+
% (
|
|
135
|
+
self.val,
|
|
136
|
+
self._fmt_items(superdict),
|
|
137
|
+
self._fmt_items(missing),
|
|
138
|
+
"was" if len(missing) == 1 else "were",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
# flatten supersets
|
|
143
|
+
superset = set()
|
|
144
|
+
for j in supersets:
|
|
145
|
+
try:
|
|
146
|
+
for k in j:
|
|
147
|
+
superset.add(k)
|
|
148
|
+
except TypeError:
|
|
149
|
+
superset.add(j)
|
|
150
|
+
|
|
151
|
+
for i in self.val:
|
|
152
|
+
if i not in superset:
|
|
153
|
+
missing.append(i)
|
|
154
|
+
if missing:
|
|
155
|
+
return self.error(
|
|
156
|
+
"Expected <%s> to be subset of %s, but %s %s missing."
|
|
157
|
+
% (
|
|
158
|
+
self.val,
|
|
159
|
+
self._fmt_items(superset),
|
|
160
|
+
self._fmt_items(missing),
|
|
161
|
+
"was" if len(missing) == 1 else "were",
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def is_sorted(self, key=lambda x: x, reverse=False) -> Self:
|
|
168
|
+
"""Asserts that val is iterable and is sorted.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
key (function): the one-arg function to extract the sort comparison key. Defaults to
|
|
172
|
+
``lambda x: x`` to just compare items directly.
|
|
173
|
+
reverse (bool): if ``True``, then comparison key is reversed. Defaults to ``False``.
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
Usage::
|
|
177
|
+
|
|
178
|
+
assert_that(['a', 'b', 'c']).is_sorted()
|
|
179
|
+
assert_that((1, 2, 3)).is_sorted()
|
|
180
|
+
|
|
181
|
+
# with a key function
|
|
182
|
+
assert_that('aBc').is_sorted(key=str.lower)
|
|
183
|
+
|
|
184
|
+
# reverse order
|
|
185
|
+
assert_that(['c', 'b', 'a']).is_sorted(reverse=True)
|
|
186
|
+
assert_that((3, 2, 1)).is_sorted(reverse=True)
|
|
187
|
+
|
|
188
|
+
assert_that((1, 2, 3, 4, -5, 6)).is_sorted() # fails
|
|
189
|
+
# Expected <(1, 2, 3, 4, -5, 6)> to be sorted, but subset <4, -5> at index 3 is not.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
AssertionError: if val is **not** sorted
|
|
196
|
+
"""
|
|
197
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
198
|
+
raise TypeError("val is not iterable")
|
|
199
|
+
|
|
200
|
+
prev = None
|
|
201
|
+
for i, x in enumerate(self.val):
|
|
202
|
+
if i > 0:
|
|
203
|
+
if reverse:
|
|
204
|
+
if key(x) > key(prev):
|
|
205
|
+
return self.error(
|
|
206
|
+
"Expected <%s> to be sorted reverse, but subset %s at index %s is not."
|
|
207
|
+
% (self.val, self._fmt_items([prev, x]), i - 1)
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
if key(x) < key(prev):
|
|
211
|
+
return self.error(
|
|
212
|
+
"Expected <%s> to be sorted, but subset %s at index %s is not."
|
|
213
|
+
% (self.val, self._fmt_items([prev, x]), i - 1)
|
|
214
|
+
)
|
|
215
|
+
prev = x
|
|
216
|
+
|
|
217
|
+
return self
|