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/extracting.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
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.abc
|
|
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 ExtractingMixin:
|
|
41
|
+
"""Collection flattening mixin.
|
|
42
|
+
|
|
43
|
+
It is often necessary to test collections of objects. Use the ``extracting()`` helper to
|
|
44
|
+
reduce the collection on a given attribute. Reduce a list of objects::
|
|
45
|
+
|
|
46
|
+
alice = Person('Alice', 'Alpha')
|
|
47
|
+
bob = Person('Bob', 'Bravo')
|
|
48
|
+
people = [alice, bob]
|
|
49
|
+
|
|
50
|
+
assert_that(people).extracting('first_name').is_equal_to(['Alice', 'Bob'])
|
|
51
|
+
assert_that(people).extracting('first_name').contains('Alice', 'Bob')
|
|
52
|
+
assert_that(people).extracting('first_name').does_not_contain('Charlie')
|
|
53
|
+
|
|
54
|
+
Additionally, the ``extracting()`` helper can accept a list of attributes to be extracted, and
|
|
55
|
+
will flatten them into a list of tuples. Reduce a list of objects on multiple attributes::
|
|
56
|
+
|
|
57
|
+
assert_that(people).extracting('first_name', 'last_name').contains(('Alice', 'Alpha'), ('Bob', 'Bravo'))
|
|
58
|
+
|
|
59
|
+
Also, ``extracting()`` works on not just attributes, but also properties, and even
|
|
60
|
+
zero-argument methods. Reduce a list of object on properties and zero-arg methods::
|
|
61
|
+
|
|
62
|
+
assert_that(people).extracting('name').contains('Alice Alpha', 'Bob Bravo')
|
|
63
|
+
assert_that(people).extracting('say_hello').contains('Hello, Alice!', 'Hello, Bob!')
|
|
64
|
+
|
|
65
|
+
And ``extracting()`` even works on *dict-like* objects. Reduce a list of dicts on key::
|
|
66
|
+
|
|
67
|
+
alice = {'first_name': 'Alice', 'last_name': 'Alpha'}
|
|
68
|
+
bob = {'first_name': 'Bob', 'last_name': 'Bravo'}
|
|
69
|
+
people = [alice, bob]
|
|
70
|
+
|
|
71
|
+
assert_that(people).extracting('first_name').contains('Alice', 'Bob')
|
|
72
|
+
|
|
73
|
+
**Filtering**
|
|
74
|
+
|
|
75
|
+
The ``extracting()`` helper can include a *filter* to keep only those items for which the given
|
|
76
|
+
*filter* is truthy. For example::
|
|
77
|
+
|
|
78
|
+
users = [
|
|
79
|
+
{'user': 'Alice', 'age': 36, 'active': True},
|
|
80
|
+
{'user': 'Bob', 'age': 40, 'active': False},
|
|
81
|
+
{'user': 'Charlie', 'age': 13, 'active': True}
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# filter the active users
|
|
85
|
+
assert_that(users).extracting('user', filter='active').is_equal_to(['Alice', 'Charlie'])
|
|
86
|
+
|
|
87
|
+
The *filter* can be a *dict-like* object and the extracted items are kept if and only if all
|
|
88
|
+
corresponding key-value pairs are equal::
|
|
89
|
+
|
|
90
|
+
assert_that(users).extracting('user', filter={'active': False}).is_equal_to(['Bob'])
|
|
91
|
+
assert_that(users).extracting('user', filter={'age': 36, 'active': True}).is_equal_to(['Alice'])
|
|
92
|
+
|
|
93
|
+
Or a *filter* can be any function (including an in-line ``lambda``) that accepts as its single
|
|
94
|
+
argument each item in the collection, and the extracted items are kept if the function
|
|
95
|
+
evaluates to ``True``::
|
|
96
|
+
|
|
97
|
+
assert_that(users).extracting('user', filter=lambda x: x['age'] > 20)
|
|
98
|
+
.is_equal_to(['Alice', 'Bob'])
|
|
99
|
+
|
|
100
|
+
**Sorting**
|
|
101
|
+
|
|
102
|
+
The ``extracting()`` helper can include a *sort* to enforce order on the extracted items.
|
|
103
|
+
|
|
104
|
+
The *sort* can be the name of a key (or attribute, or property, or zero-argument method) and
|
|
105
|
+
the extracted items are ordered by the corresponding values::
|
|
106
|
+
|
|
107
|
+
assert_that(users).extracting('user', sort='age').is_equal_to(['Charlie', 'Alice', 'Bob'])
|
|
108
|
+
|
|
109
|
+
The *sort* can be an iterable of names and the extracted items are ordered by
|
|
110
|
+
corresponding value of the first name, ties are broken by the corresponding values of the
|
|
111
|
+
second name, and so on::
|
|
112
|
+
|
|
113
|
+
assert_that(users).extracting('user', sort=['active', 'age']).is_equal_to(['Bob', 'Charlie', 'Alice'])
|
|
114
|
+
|
|
115
|
+
The *sort* can be any function (including an in-line ``lambda``) that accepts as its single
|
|
116
|
+
argument each item in the collection, and the extracted items are ordered by the corresponding
|
|
117
|
+
function return values::
|
|
118
|
+
|
|
119
|
+
assert_that(users).extracting('user', sort=lambda x: -x['age']).is_equal_to(['Bob', 'Alice', 'Charlie'])
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def extracting(self, *names, **kwargs) -> Self:
|
|
123
|
+
"""Asserts that val is iterable, then extracts the named attributes, properties, or
|
|
124
|
+
zero-arg methods into a list (or list of tuples if multiple names are given).
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
*names: the attribute to be extracted (or property or zero-arg method)
|
|
128
|
+
**kwargs: see below
|
|
129
|
+
|
|
130
|
+
Keyword Args:
|
|
131
|
+
filter: extract only those items where filter is truthy
|
|
132
|
+
sort: order the extracted items by the sort key
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
Usage::
|
|
136
|
+
|
|
137
|
+
alice = User('Alice', 20, True)
|
|
138
|
+
bob = User('Bob', 30, False)
|
|
139
|
+
charlie = User('Charlie', 10, True)
|
|
140
|
+
users = [alice, bob, charlie]
|
|
141
|
+
|
|
142
|
+
assert_that(users).extracting('user').contains('Alice', 'Bob', 'Charlie')
|
|
143
|
+
|
|
144
|
+
Works with *dict-like* objects too::
|
|
145
|
+
|
|
146
|
+
users = [
|
|
147
|
+
{'user': 'Alice', 'age': 20, 'active': True},
|
|
148
|
+
{'user': 'Bob', 'age': 30, 'active': False},
|
|
149
|
+
{'user': 'Charlie', 'age': 10, 'active': True}
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
assert_that(people).extracting('user').contains('Alice', 'Bob', 'Charlie')
|
|
153
|
+
|
|
154
|
+
Filter::
|
|
155
|
+
|
|
156
|
+
assert_that(users).extracting('user', filter='active').is_equal_to(['Alice', 'Charlie'])
|
|
157
|
+
|
|
158
|
+
Sort::
|
|
159
|
+
|
|
160
|
+
assert_that(users).extracting('user', sort='age').is_equal_to(['Charlie', 'Alice', 'Bob'])
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
AssertionBuilder: returns a new instance (now with the extracted list as the val) to chain to the next assertion
|
|
164
|
+
"""
|
|
165
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
166
|
+
raise TypeError("val is not iterable")
|
|
167
|
+
if isinstance(self.val, str):
|
|
168
|
+
raise TypeError("val must not be string")
|
|
169
|
+
if len(names) == 0:
|
|
170
|
+
raise ValueError("one or more name args must be given")
|
|
171
|
+
|
|
172
|
+
def _extract(x, name):
|
|
173
|
+
if self._check_dict_like(x, check_values=False, return_as_bool=True):
|
|
174
|
+
if name in x:
|
|
175
|
+
return x[name]
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError("item keys %s did not contain key <%s>" % (list(x.keys()), name))
|
|
178
|
+
elif isinstance(x, tuple) and hasattr(x, "_fields") and type(name) is str:
|
|
179
|
+
if name in x._fields:
|
|
180
|
+
return getattr(x, name)
|
|
181
|
+
else: # val has no attribute <foo>
|
|
182
|
+
raise ValueError("item attributes %s did no contain attribute <%s>" % (x._fields, name))
|
|
183
|
+
elif isinstance(x, collections.abc.Iterable): # FIXME, this does __getitem__, but doesn't check for it...
|
|
184
|
+
self._check_iterable(x, name="item")
|
|
185
|
+
return x[name]
|
|
186
|
+
elif hasattr(x, name):
|
|
187
|
+
attr = getattr(x, name)
|
|
188
|
+
if callable(attr):
|
|
189
|
+
try:
|
|
190
|
+
return attr()
|
|
191
|
+
except TypeError:
|
|
192
|
+
raise ValueError("item method <%s()> exists, but is not zero-arg method" % name) from None
|
|
193
|
+
else:
|
|
194
|
+
return attr
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError("item does not have property or zero-arg method <%s>" % name)
|
|
197
|
+
|
|
198
|
+
def _filter(x):
|
|
199
|
+
if "filter" in kwargs:
|
|
200
|
+
if isinstance(kwargs["filter"], str):
|
|
201
|
+
return bool(_extract(x, kwargs["filter"]))
|
|
202
|
+
elif self._check_dict_like(kwargs["filter"], check_values=False, return_as_bool=True):
|
|
203
|
+
for k in kwargs["filter"]:
|
|
204
|
+
if isinstance(k, str) and _extract(x, k) != kwargs["filter"][k]:
|
|
205
|
+
return False
|
|
206
|
+
return True
|
|
207
|
+
elif callable(kwargs["filter"]):
|
|
208
|
+
return kwargs["filter"](x)
|
|
209
|
+
return kwargs["filter"] is None
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
def _sort(x):
|
|
213
|
+
if "sort" in kwargs:
|
|
214
|
+
if isinstance(kwargs["sort"], str):
|
|
215
|
+
return _extract(x, kwargs["sort"])
|
|
216
|
+
elif isinstance(kwargs["sort"], collections.abc.Iterable):
|
|
217
|
+
items = []
|
|
218
|
+
for k in kwargs["sort"]:
|
|
219
|
+
if isinstance(k, str):
|
|
220
|
+
items.append(_extract(x, k))
|
|
221
|
+
return tuple(items)
|
|
222
|
+
elif callable(kwargs["sort"]):
|
|
223
|
+
return kwargs["sort"](x)
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
extracted = []
|
|
227
|
+
for i in sorted(self.val, key=lambda x: _sort(x)):
|
|
228
|
+
if _filter(i):
|
|
229
|
+
items = [_extract(i, name) for name in names]
|
|
230
|
+
extracted.append(tuple(items) if len(items) > 1 else items[0])
|
|
231
|
+
|
|
232
|
+
# chain on with _extracted_ list (don't chain to self!)
|
|
233
|
+
return self.builder(extracted, self.description, self.kind)
|
assertpy2/file.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
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 os
|
|
32
|
+
from typing import TYPE_CHECKING
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from typing_extensions import Self
|
|
36
|
+
|
|
37
|
+
__tracebackhide__ = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def contents_of(file, encoding="utf-8"):
|
|
41
|
+
"""Helper to read the contents of the given file or path into a string with the given encoding.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
file: a *path-like object* (aka a file name) or a *file-like object* (aka a file)
|
|
45
|
+
encoding (str): the target encoding. Defaults to ``utf-8``, other useful encodings are ``ascii`` and ``latin-1``.
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
Usage::
|
|
49
|
+
|
|
50
|
+
from assertpy2 import assert_that, contents_of
|
|
51
|
+
|
|
52
|
+
contents = contents_of('foo.txt')
|
|
53
|
+
assert_that(contents).starts_with('foo').ends_with('bar').contains('oob')
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str: returns the file contents as a string
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
IOError: if file not found
|
|
60
|
+
TypeError: if file is not a *path-like object* or a *file-like object*
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
contents = file.read()
|
|
64
|
+
except AttributeError:
|
|
65
|
+
try:
|
|
66
|
+
with open(file) as fp:
|
|
67
|
+
contents = fp.read()
|
|
68
|
+
except TypeError:
|
|
69
|
+
raise ValueError("val must be file or path, but was type <%s>" % type(file).__name__) from None
|
|
70
|
+
except OSError:
|
|
71
|
+
if not isinstance(file, (str, os.PathLike)):
|
|
72
|
+
raise ValueError("val must be file or path, but was type <%s>" % type(file).__name__) from None
|
|
73
|
+
raise
|
|
74
|
+
|
|
75
|
+
if type(contents) is bytes:
|
|
76
|
+
return contents.decode(encoding, "replace")
|
|
77
|
+
try:
|
|
78
|
+
return contents.decode(encoding, "replace")
|
|
79
|
+
except AttributeError:
|
|
80
|
+
pass
|
|
81
|
+
# if all else fails, just return the contents "as is"
|
|
82
|
+
return contents
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class FileMixin:
|
|
86
|
+
"""File assertions mixin."""
|
|
87
|
+
|
|
88
|
+
def exists(self) -> Self:
|
|
89
|
+
"""Asserts that val is a path and that it exists.
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
Usage::
|
|
93
|
+
|
|
94
|
+
assert_that('myfile.txt').exists()
|
|
95
|
+
assert_that('mydir').exists()
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
AssertionError: if val does **not** exist
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(self.val, (str, os.PathLike)):
|
|
104
|
+
raise TypeError("val is not a path")
|
|
105
|
+
if not os.path.exists(self.val):
|
|
106
|
+
return self.error("Expected <%s> to exist, but was not found." % self.val)
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
def does_not_exist(self) -> Self:
|
|
110
|
+
"""Asserts that val is a path and that it does *not* exist.
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
Usage::
|
|
114
|
+
|
|
115
|
+
assert_that('missing.txt').does_not_exist()
|
|
116
|
+
assert_that('missing_dir').does_not_exist()
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
AssertionError: if val **does** exist
|
|
123
|
+
"""
|
|
124
|
+
if not isinstance(self.val, (str, os.PathLike)):
|
|
125
|
+
raise TypeError("val is not a path")
|
|
126
|
+
if os.path.exists(self.val):
|
|
127
|
+
return self.error("Expected <%s> to not exist, but was found." % self.val)
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def is_file(self) -> Self:
|
|
131
|
+
"""Asserts that val is a *file* and that it exists.
|
|
132
|
+
|
|
133
|
+
Examples:
|
|
134
|
+
Usage::
|
|
135
|
+
|
|
136
|
+
assert_that('myfile.txt').is_file()
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
AssertionError: if val does **not** exist, or is **not** a file
|
|
143
|
+
"""
|
|
144
|
+
self.exists()
|
|
145
|
+
if not os.path.isfile(self.val):
|
|
146
|
+
return self.error("Expected <%s> to be a file, but was not." % self.val)
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def is_directory(self) -> Self:
|
|
150
|
+
"""Asserts that val is a *directory* and that it exists.
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
Usage::
|
|
154
|
+
|
|
155
|
+
assert_that('mydir').is_directory()
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
AssertionError: if val does **not** exist, or is **not** a directory
|
|
162
|
+
"""
|
|
163
|
+
self.exists()
|
|
164
|
+
if not os.path.isdir(self.val):
|
|
165
|
+
return self.error("Expected <%s> to be a directory, but was not." % self.val)
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def is_named(self, filename) -> Self:
|
|
169
|
+
"""Asserts that val is an existing path to a file and that file is named filename.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
filename: the expected filename
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
Usage::
|
|
176
|
+
|
|
177
|
+
assert_that('/path/to/mydir/myfile.txt').is_named('myfile.txt')
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
AssertionError: if val does **not** exist, or is **not** a file, or is **not** named the given filename
|
|
184
|
+
"""
|
|
185
|
+
self.is_file()
|
|
186
|
+
if not isinstance(filename, (str, os.PathLike)):
|
|
187
|
+
raise TypeError("given filename arg must be a path")
|
|
188
|
+
val_filename = os.path.basename(os.path.abspath(self.val))
|
|
189
|
+
if val_filename != filename:
|
|
190
|
+
return self.error("Expected filename <%s> to be equal to <%s>, but was not." % (val_filename, filename))
|
|
191
|
+
return self
|
|
192
|
+
|
|
193
|
+
def is_child_of(self, parent) -> Self:
|
|
194
|
+
"""Asserts that val is an existing path to a file and that file is a child of parent.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
parent: the expected parent directory
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
Usage::
|
|
201
|
+
|
|
202
|
+
assert_that('/path/to/mydir/myfile.txt').is_child_of('mydir')
|
|
203
|
+
assert_that('/path/to/mydir/myfile.txt').is_child_of('to')
|
|
204
|
+
assert_that('/path/to/mydir/myfile.txt').is_child_of('path')
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
AssertionError: if val does **not** exist, or is **not** a file, or is **not** a child of the given directory
|
|
211
|
+
"""
|
|
212
|
+
self.is_file()
|
|
213
|
+
if not isinstance(parent, (str, os.PathLike)):
|
|
214
|
+
raise TypeError("given parent directory arg must be a path")
|
|
215
|
+
val_abspath = os.path.abspath(self.val)
|
|
216
|
+
parent_abspath = os.path.abspath(parent)
|
|
217
|
+
if not val_abspath.startswith(parent_abspath):
|
|
218
|
+
return self.error("Expected file <%s> to be a child of <%s>, but was not." % (val_abspath, parent_abspath))
|
|
219
|
+
return self
|
|
220
|
+
|
|
221
|
+
def is_readable(self) -> Self:
|
|
222
|
+
"""Asserts that val is an existing path and is readable.
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
Usage::
|
|
226
|
+
|
|
227
|
+
assert_that('/path/to/file.txt').is_readable()
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
AssertionError: if val does **not** exist, or is **not** readable
|
|
234
|
+
"""
|
|
235
|
+
self.exists()
|
|
236
|
+
if not os.access(self.val, os.R_OK):
|
|
237
|
+
return self.error("Expected <%s> to be readable, but was not." % self.val)
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def is_writable(self) -> Self:
|
|
241
|
+
"""Asserts that val is an existing path and is writable.
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
Usage::
|
|
245
|
+
|
|
246
|
+
assert_that('/path/to/file.txt').is_writable()
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
AssertionError: if val does **not** exist, or is **not** writable
|
|
253
|
+
"""
|
|
254
|
+
self.exists()
|
|
255
|
+
if not os.access(self.val, os.W_OK):
|
|
256
|
+
return self.error("Expected <%s> to be writable, but was not." % self.val)
|
|
257
|
+
return self
|
|
258
|
+
|
|
259
|
+
def is_executable(self) -> Self:
|
|
260
|
+
"""Asserts that val is an existing path and is executable.
|
|
261
|
+
|
|
262
|
+
Examples:
|
|
263
|
+
Usage::
|
|
264
|
+
|
|
265
|
+
assert_that('/path/to/script.sh').is_executable()
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
AssertionError: if val does **not** exist, or is **not** executable
|
|
272
|
+
"""
|
|
273
|
+
self.exists()
|
|
274
|
+
if not os.access(self.val, os.X_OK):
|
|
275
|
+
return self.error("Expected <%s> to be executable, but was not." % self.val)
|
|
276
|
+
return self
|