The Gaudi Framework
master (bb4415cc)
Toggle main menu visibility
Loading...
Searching...
No Matches
fixtures.py
Go to the documentation of this file.
1
11
import
inspect
12
import
os
13
import
subprocess
14
from
collections
import
defaultdict
15
from
pathlib
import
Path
16
from
typing
import
Callable, Generator, Optional
17
18
import
pytest
19
import
yaml
20
from
GaudiTesting.FixtureResult
import
FixtureResult
21
from
GaudiTesting.pytest.ctest_measurements_reporter
import
results
22
from
GaudiTesting.SubprocessBaseTest
import
SubprocessBaseTest
23
from
GaudiTesting.utils
import
(
24
CodeWrapper,
25
expand_reference_file_name,
26
file_path_for_class,
27
get_platform,
28
str_representer,
29
)
30
31
32
class
AlwaysTrueDefaultDict
(defaultdict):
33
def
__contains__
(self, key):
34
return
True
35
36
37
yaml.representer.SafeRepresenter.add_representer(str, str_representer)
38
yaml.representer.SafeRepresenter.add_representer(
39
AlwaysTrueDefaultDict, yaml.representer.SafeRepresenter.represent_dict
40
)
41
42
# This plugin provides a set of pytest fixtures for the Gaudi testing framework.
43
# These fixtures facilitate the execution of tests and capture various aspects
44
# of the test results.
45
46
# The fixtures include:
47
# - fixture_result: Runs the program and yields the result.
48
# - completed_process: Yields the completed subprocess process.
49
# - stdout: Captures the standard output of the subprocess.
50
# - stderr: Captures the standard error of the subprocess.
51
# - returncode: Captures the return code of the subprocess.
52
# - cwd: Captures the directory in which the program was executed.
53
# - check_for_exceptions: Skips the test if exceptions are found in the result.
54
# - capture_class_docstring: Captures the docstring of the test class.
55
# - reference: Creates a .new file if the output data is different from the reference.
56
57
58
def
pytest_configure
(config):
59
config.addinivalue_line(
60
"markers"
,
61
"shared_cwd(id): make SubprocessBaseTest tests share a working directory"
,
62
)
63
config.addinivalue_line(
64
"markers"
,
65
"do_not_collect_source: flag the test code as not to be collected"
,
66
)
67
68
69
def
pytest_collection_modifyitems
(config, items):
70
"""
71
Record source code of tests.
72
"""
73
for
item
in
items:
74
if
isinstance(item, pytest.Function)
and
not
item.get_closest_marker(
75
"do_not_collect_source"
76
):
77
name = (
78
f
"{item.parent.name}.{item.originalname}"
79
if
isinstance(item.parent, pytest.Class)
80
else
item.originalname
81
)
82
source_code =
CodeWrapper
(inspect.getsource(item.function),
"python"
)
83
results[f
"{name}.source_code"
] = source_code
84
85
86
def
_get_shared_cwd_id
(cls: type) -> Optional[str]:
87
"""
88
Extract the id of the shared cwd directory needed by the class, if any.
89
90
If the class is marked with shared_cwd multiple times only the last one
91
is taken into account.
92
93
Return the id or None.
94
"""
95
if
hasattr(cls,
"pytestmark"
):
96
for
mark
in
cls.pytestmark:
97
if
mark.name ==
"shared_cwd"
:
98
return
mark.args[0]
99
return
None
100
101
102
def
_path_for_shared_cwd
(config: pytest.Config, cwd_id: str) -> Path:
103
"""
104
Return the path to the shared directory identified by id.
105
"""
106
name = f
"gaudi.{get_platform()}.{cwd_id}"
.replace(
"/"
,
"_"
)
107
return
config.cache.mkdir(name)
108
109
110
@pytest.fixture(scope="class")
111
def
fixture_result
(
112
request: pytest.FixtureRequest,
113
tmp_path_factory: Callable,
114
) -> Generator[FixtureResult,
None
,
None
]:
115
cls = request.cls
116
result =
None
117
if
cls
and
issubclass(cls, SubprocessBaseTest):
118
if
hasattr(cls,
"popen_kwargs"
)
and
"cwd"
not
in
cls.popen_kwargs:
119
cwd_id =
_get_shared_cwd_id
(cls)
120
cls.popen_kwargs[
"cwd"
] = (
121
_path_for_shared_cwd
(request.config, cwd_id)
122
if
cwd_id
123
else
tmp_path_factory.mktemp(
"workdir"
)
124
)
125
result = cls.run_program(
126
tmp_path=tmp_path_factory.mktemp(
"tmp-"
, numbered=
True
)
127
)
128
129
yield
result
130
131
132
@pytest.fixture(scope="class")
133
def
completed_process
(
134
fixture_result: FixtureResult,
135
) -> Generator[subprocess.CompletedProcess,
None
,
None
]:
136
yield
fixture_result.completed_process
if
fixture_result
else
None
137
138
139
@pytest.fixture(scope="class")
140
def
stdout
(
141
completed_process: subprocess.CompletedProcess,
142
) -> Generator[bytes,
None
,
None
]:
143
yield
completed_process.stdout
if
completed_process
else
None
144
145
146
@pytest.fixture(scope="class")
147
def
stderr
(
148
completed_process: subprocess.CompletedProcess,
149
) -> Generator[bytes,
None
,
None
]:
150
yield
completed_process.stderr
if
completed_process
else
None
151
152
153
@pytest.fixture(scope="class")
154
def
returncode
(
155
completed_process: subprocess.CompletedProcess,
156
) -> Generator[int,
None
,
None
]:
157
yield
completed_process.returncode
if
completed_process
else
None
158
159
160
@pytest.fixture(scope="class")
161
def
cwd
(fixture_result: FixtureResult) -> Generator[Path,
None
,
None
]:
162
yield
Path(fixture_result.cwd)
if
fixture_result
else
None
163
164
165
@pytest.fixture(autouse=True)
166
def
check_for_exceptions
(
167
request: pytest.FixtureRequest, fixture_result: FixtureResult
168
) ->
None
:
169
if
(
170
fixture_result
171
and
fixture_result.run_exception
is
not
None
172
and
"test_fixture_setup"
not
in
request.keywords
173
):
174
pytest.skip(f
"{fixture_result.run_exception}"
)
175
176
177
@pytest.fixture(scope="class", autouse=True)
178
def
capture_class_docstring
(
179
request: pytest.FixtureRequest,
180
) ->
None
:
181
cls = request.cls
182
if
cls
and
cls.__doc__:
183
results[f
"{cls.__name__}.doc"
] = inspect.getdoc(cls)
184
185
186
@pytest.fixture(scope="class")
187
def
reference_path
(request) -> Generator[Optional[Path], None, None]:
188
cls = request.cls
189
190
if
not
hasattr(cls,
"reference"
)
or
cls.reference
is
None
:
191
yield
None
192
193
elif
isinstance(cls.reference, (str, os.PathLike)):
194
path = cls.reference
195
if
hasattr(cls,
"resolve_path"
):
196
path = cls.resolve_path(path)
197
198
path = expand_reference_file_name(path)
199
yield
Path(path)
if
path
else
None
200
201
elif
isinstance(cls.reference, dict):
202
yield
None
203
204
else
:
205
raise
ValueError(
206
f
"Invalid reference type {type(cls.reference)}, only str, PathLike or dict are allowed"
207
)
208
209
210
@pytest.fixture(scope="class")
211
def
reference
(request, reference_path: Optional[Path]) -> Generator[dict,
None
,
None
]:
212
cls = request.cls
213
original_reference_data =
None
214
current_reference_data =
None
215
216
if
reference_path:
217
if
reference_path.exists()
and
reference_path.stat().st_size > 0:
218
with
open(reference_path,
"r"
)
as
f:
219
original_reference_data = yaml.safe_load(f)
220
else
:
221
# if the file does not exist we may have a relative path, so
222
# we have to resolve it wrt the file containing the test class
223
reference_path = file_path_for_class(cls).parent / reference_path
224
original_reference_data =
AlwaysTrueDefaultDict
(
lambda
:
None
)
225
226
current_reference_data = original_reference_data.copy()
227
228
elif
hasattr(cls,
"reference"
)
and
isinstance(cls.reference, dict):
229
original_reference_data = cls.reference
230
current_reference_data = original_reference_data.copy()
231
232
yield
current_reference_data
233
234
if
current_reference_data == original_reference_data:
235
return
236
237
if
reference_path:
238
cnt = 0
239
newrefname = f
"{reference_path}.new"
240
while
os.path.exists(newrefname):
241
cnt += 1
242
newrefname = f
"{reference_path}.~{cnt}~.new"
243
244
if
os.environ.get(
"GAUDI_TEST_IGNORE_STDOUT_VALIDATION"
) ==
"1"
:
245
# when we ignore stdout validation we just overwrite the reference file
246
newrefname = reference_path
247
248
os.makedirs(os.path.dirname(newrefname), exist_ok=
True
)
249
with
open(newrefname,
"w"
)
as
f:
250
yaml.safe_dump(current_reference_data, f, sort_keys=
False
)
GaudiTesting.utils.CodeWrapper
Definition
utils.py:23
fixtures.AlwaysTrueDefaultDict
Definition
fixtures.py:32
fixtures.AlwaysTrueDefaultDict.__contains__
__contains__(self, key)
Definition
fixtures.py:33
GaudiTesting.FixtureResult
Definition
FixtureResult.py:1
GaudiTesting.SubprocessBaseTest
Definition
SubprocessBaseTest.py:1
GaudiTesting.utils
Definition
utils.py:1
fixtures.fixture_result
Generator[FixtureResult, None, None] fixture_result(pytest.FixtureRequest request, Callable tmp_path_factory)
Definition
fixtures.py:114
fixtures.pytest_collection_modifyitems
pytest_collection_modifyitems(config, items)
Definition
fixtures.py:69
fixtures.cwd
Generator[Path, None, None] cwd(FixtureResult fixture_result)
Definition
fixtures.py:161
fixtures.completed_process
Generator[subprocess.CompletedProcess, None, None] completed_process(FixtureResult fixture_result)
Definition
fixtures.py:135
fixtures.pytest_configure
pytest_configure(config)
Definition
fixtures.py:58
fixtures._get_shared_cwd_id
Optional[str] _get_shared_cwd_id(type cls)
Definition
fixtures.py:86
fixtures.stderr
Generator[bytes, None, None] stderr(subprocess.CompletedProcess completed_process)
Definition
fixtures.py:149
fixtures.reference
reference
Definition
fixtures.py:193
fixtures.check_for_exceptions
None check_for_exceptions(pytest.FixtureRequest request, FixtureResult fixture_result)
Definition
fixtures.py:168
fixtures.reference_path
Generator[Optional[Path], None, None] reference_path(request)
Definition
fixtures.py:187
fixtures.stdout
Generator[bytes, None, None] stdout(subprocess.CompletedProcess completed_process)
Definition
fixtures.py:142
fixtures.capture_class_docstring
None capture_class_docstring(pytest.FixtureRequest request)
Definition
fixtures.py:180
fixtures._path_for_shared_cwd
Path _path_for_shared_cwd(pytest.Config config, str cwd_id)
Definition
fixtures.py:102
fixtures.returncode
Generator[int, None, None] returncode(subprocess.CompletedProcess completed_process)
Definition
fixtures.py:156
GaudiPolicy
python
GaudiTesting
pytest
fixtures.py
Generated on
for The Gaudi Framework by
1.17.0