forked from Unstructured-IO/unstructured
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunit_utils.py
226 lines (173 loc) · 7.23 KB
/
unit_utils.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
"""Utilities that ease unit-testing."""
from __future__ import annotations
import datetime as dt
import difflib
import pathlib
from typing import Any, List, Optional
from unittest.mock import (
ANY,
MagicMock,
Mock,
PropertyMock,
call,
create_autospec,
mock_open,
patch,
)
from pytest import CaptureFixture, FixtureRequest, LogCaptureFixture # noqa: PT013
from unstructured.documents.elements import Element
from unstructured.staging.base import elements_from_json, elements_to_json
__all__ = (
"ANY",
"CaptureFixture",
"FixtureRequest",
"LogCaptureFixture",
"MagicMock",
"Mock",
"call",
"class_mock",
"function_mock",
"initializer_mock",
"instance_mock",
"method_mock",
"property_mock",
)
def assert_round_trips_through_JSON(elements: List[Element]) -> None:
"""Raises AssertionError if `elements -> JSON -> List[Element] -> JSON` are not equal.
The procedure is:
1. Serialize `elements` to (original) JSON.
2. Deserialize that JSON to `List[Element]`.
3. Serialize that `List[Element]` to JSON.
3. Compare the original and round-tripped JSON, raise if they are different.
"""
original_json = elements_to_json(elements)
assert original_json is not None
round_tripped_elements = elements_from_json(text=original_json)
round_tripped_json = elements_to_json(round_tripped_elements)
assert round_tripped_json is not None
assert round_tripped_json == original_json, _diff(
"JSON differs:", round_tripped_json, original_json
)
def assign_hash_ids(elements: list[Element]) -> list[Element]:
"""Updates the `id` attribute of each element to a hash."""
for idx, element in enumerate(elements):
element.id_to_hash(idx)
return elements
def _diff(heading: str, actual: str, expected: str):
"""Diff of actual compared to expected.
"+" indicates unexpected lines actual, "-" indicates lines missing from actual.
"""
expected_lines = expected.splitlines(keepends=True)
actual_lines = actual.splitlines(keepends=True)
heading = "diff: '+': unexpected lines in actual, '-': lines missing from actual\n"
return heading + "".join(difflib.Differ().compare(actual_lines, expected_lines))
def example_doc_path(file_name: str) -> str:
"""Resolve the absolute-path to `file_name` in the example-docs directory."""
example_docs_dir = pathlib.Path(__file__).parent.parent / "example-docs"
file_path = example_docs_dir / file_name
return str(file_path.resolve())
def parse_optional_datetime(datetime_str: Optional[str]) -> Optional[dt.datetime]:
"""Parse `datetime_str` to a datetime.datetime instance or None if `datetime_str` is None."""
return dt.datetime.fromisoformat(datetime_str) if datetime_str else None
# ------------------------------------------------------------------------------------------------
# MOCKING FIXTURES
# ------------------------------------------------------------------------------------------------
# These allow full-featured and type-safe mocks to be created simply by adding a unit-test
# fixture.
# ------------------------------------------------------------------------------------------------
def class_mock(
request: FixtureRequest, q_class_name: str, autospec: bool = True, **kwargs: Any
) -> Mock:
"""Return mock patching class with qualified name `q_class_name`.
The mock is autospec'ed based on the patched class unless the optional argument `autospec` is
set to False. Any other keyword arguments are passed through to Mock(). Patch is reversed after
calling test returns.
"""
_patch = patch(q_class_name, autospec=autospec, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def cls_attr_mock(
request: FixtureRequest,
cls: type,
attr_name: str,
name: str | None = None,
**kwargs: Any,
):
"""Return a mock for attribute `attr_name` on `cls`.
Patch is reversed after pytest uses it.
"""
name = request.fixturename if name is None else name
_patch = patch.object(cls, attr_name, name=name, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def function_mock(
request: FixtureRequest, q_function_name: str, autospec: bool = True, **kwargs: Any
) -> Mock:
"""Return mock patching function with qualified name `q_function_name`.
Patch is reversed after calling test returns.
"""
_patch = patch(q_function_name, autospec=autospec, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def initializer_mock(request: FixtureRequest, cls: type, autospec: bool = True, **kwargs: Any):
"""Return mock for __init__() method on `cls`.
The patch is reversed after pytest uses it.
"""
_patch = patch.object(cls, "__init__", autospec=autospec, return_value=None, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def instance_mock(
request: FixtureRequest,
cls: type,
name: str | None = None,
spec_set: bool = True,
**kwargs: Any,
):
"""Return a mock for an instance of `cls` that draws its spec from the class.
The mock does not allow new attributes to be set on the instance. If `name` is missing or
|None|, the name of the returned |Mock| instance is set to *request.fixturename*. Additional
keyword arguments are passed through to the Mock() call that creates the mock.
"""
name = name if name is not None else request.fixturename
return create_autospec(cls, _name=name, spec_set=spec_set, instance=True, **kwargs)
def loose_mock(request: FixtureRequest, name: str | None = None, **kwargs: Any):
"""Return a "loose" mock, meaning it has no spec to constrain calls on it.
Additional keyword arguments are passed through to Mock(). If called without a name, it is
assigned the name of the fixture.
"""
if name is None:
name = request.fixturename
return Mock(name=name, **kwargs)
def method_mock(
request: FixtureRequest,
cls: type,
method_name: str,
autospec: bool = True,
**kwargs: Any,
):
"""Return mock for method `method_name` on `cls`.
The patch is reversed after pytest uses it.
"""
_patch = patch.object(cls, method_name, autospec=autospec, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def open_mock(request: FixtureRequest, module_name: str, **kwargs: Any):
"""Return a mock for the builtin `open()` method in `module_name`."""
target = "%s.open" % module_name
_patch = patch(target, mock_open(), create=True, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def property_mock(request: FixtureRequest, cls: type, prop_name: str, **kwargs: Any) -> Mock:
"""A mock for property `prop_name` on class `cls`.
Patch is reversed at the end of the test run.
"""
_patch = patch.object(cls, prop_name, new_callable=PropertyMock, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()
def var_mock(request: FixtureRequest, q_var_name: str, **kwargs: Any):
"""Return a mock patching the variable with qualified name `q_var_name`.
Patch is reversed after calling test returns.
"""
_patch = patch(q_var_name, **kwargs)
request.addfinalizer(_patch.stop)
return _patch.start()