The Gaudi Framework
master (a36dfcab)
Toggle main menu visibility
Loading...
Searching...
No Matches
collect_for_ctest.py
Go to the documentation of this file.
1
11
"""
12
pytest plugin that report collected pytest files as CTest tests
13
14
This plugin is not meant to be used directly, but it is invoked by the
15
CMake function `gaudi_add_pytest()`
16
"""
17
18
import
os
19
import
re
20
from
collections
import
defaultdict
21
from
pathlib
import
Path
22
23
24
def
pytest_addoption
(parser, pluginmanager):
25
parser.addoption(
26
"--ctest-output-file"
,
27
help=
"name of the file to write to communicate to ctest the discovered tests"
,
28
)
29
parser.addoption(
30
"--ctest-pytest-command"
,
31
default=
"pytest"
,
32
help=
"how pytest has to be invoked (e.g. using wrapper commands)"
,
33
)
34
parser.addoption(
35
"--ctest-pytest-root-dir"
,
36
default=Path.cwd(),
37
help=
"root directory to compute test names"
,
38
)
39
parser.addoption(
40
"--ctest-prefix"
,
41
default=
""
,
42
help=
"string to prefix to the generated test names"
,
43
)
44
parser.addoption(
45
"--ctest-label"
,
46
default=[
"pytest"
],
47
action=
"append"
,
48
help=
"labels to attach to the test (the label pytest is always added)"
,
49
)
50
parser.addoption(
51
"--ctest-properties"
,
52
default=[],
53
action=
"append"
,
54
help=
"test properties to set for all discovered tests"
,
55
)
56
parser.addoption(
57
"--ctest-binary-dir"
,
58
default=
None
,
59
help=
"value of CMAKE_CURRENT_BINARY_DIR from which gaudi_add_pytest was invoked"
,
60
)
61
parser.addoption(
62
"--ctest-coverage"
,
63
default=
""
,
64
help=
"select modules for which produce coverage reports"
,
65
)
66
parser.addoption(
67
"--ctest-coverage-command"
,
68
default=
"coverage report"
,
69
help=
"how coverage should be invoked to produce the final report"
,
70
)
71
72
73
def
pytest_collectstart
(collector):
74
session, config = collector.session, collector.config
75
args = {
76
name[6:]: getattr(config.option, name)
77
for
name
in
dir(config.option)
78
if
name.startswith(
"ctest_"
)
79
}
80
81
if
args.get(
"binary_dir"
):
82
# $CMAKE_CURRENT_BINARY_DIR will be set by CTest when the test is run
83
# so, for consistency, we set it at collection time too
84
os.environ[
"CMAKE_CURRENT_BINARY_DIR"
] = args[
"binary_dir"
]
85
86
session.ctest_args = args
87
88
89
def
pytest_collection_modifyitems
(session, config, items):
90
if
not
session.ctest_args.get(
"output_file"
):
91
# nothing to do if no output file is specified
92
return
93
94
session.ctest_files = set(item.path
for
item
in
items)
95
session.ctest_fixture_setup = defaultdict(set)
96
session.ctest_fixture_required = defaultdict(set)
97
for
item
in
items:
98
for
marker
in
(
"ctest_fixture_setup"
,
"ctest_fixture_required"
):
99
for
mark
in
item.iter_markers(name=marker):
100
getattr(session, marker)[item.path].update(mark.args)
101
102
103
TEST_DESC_TEMPLATE =
"""
104
add_test({name} {pytest_cmd} {path})
105
set_tests_properties({name} PROPERTIES {properties})
106
"""
107
108
109
def
pytest_collection_finish
(session):
110
args = session.ctest_args
111
output_filename = args.get(
"output_file"
)
112
113
if
not
output_filename:
114
# nothing to do if no output file is specified
115
return
116
117
output = open(output_filename,
"w"
)
118
output_rootdir = Path(output_filename).parent
119
120
coverage = args[
"coverage"
].split(
","
)
if
args[
"coverage"
]
else
[]
121
if
coverage:
122
args[
"pytest_command"
] +=
" --cov-report= --cov-reset "
+
" "
.join(
123
f
"--cov={module}"
for
module
in
coverage
124
)
125
126
properties =
'LABELS "{}" '
.
format
(
";"
.join(args[
"label"
]))
127
if
args.get(
"binary_dir"
):
128
properties += f
'ENVIRONMENT "CMAKE_CURRENT_BINARY_DIR={args["binary_dir"]}" '
129
properties +=
" "
.join(args[
"properties"
])
130
131
producers = defaultdict(list)
# test name -> list of fixtures it produces
132
consumers = defaultdict(list)
# test name -> list of fixtures it depends on
133
fixtures = defaultdict(list)
# fixture name -> list of tests that produce it
134
names = []
135
for
path
in
sorted(session.ctest_files):
136
name = os.path.relpath(path, args[
"pytest_root_dir"
]).replace(
"/"
,
"."
)
137
if
name.endswith(
".py"
):
138
name = name[:-3]
139
if
name ==
"."
:
140
# when we pass a single file to pytest, pytest_root_dir and path are the same
141
# and relpath returns ".", so we take the prefix and drop the final dot
142
# (see https://gitlab.cern.ch/gaudi/Gaudi/-/issues/354)
143
name = args[
"prefix"
][:-1]
144
else
:
145
name = args[
"prefix"
] + name
146
pytest_cmd = args[
"pytest_command"
]
147
148
output.write(
149
TEST_DESC_TEMPLATE.format(
150
name=name,
151
path=path,
152
pytest_cmd=pytest_cmd,
153
properties=properties,
154
)
155
)
156
157
if
session.ctest_fixture_setup.get(path):
158
for
fixture
in
session.ctest_fixture_setup[path]:
159
producers[name].append(fixture)
160
fixtures[fixture].append(name)
161
162
if
session.ctest_fixture_required.get(path):
163
for
fixture
in
session.ctest_fixture_required[path]:
164
consumers[name].append(fixture)
165
166
names.append(name)
167
168
for
name
in
names:
169
if
name
in
producers:
170
output.write(
171
'set_tests_properties('
172
f
'{name} PROPERTIES FIXTURES_SETUP "{";".join(producers[name])}")\n'
173
)
174
175
if
name
in
consumers:
176
producer_test_names = []
177
for
fixture
in
consumers[name]:
178
producer_test_names.extend(fixtures[fixture])
179
output.write(
180
'if (DEFINED ENV{PYTEST_DISABLE_FIXTURES_REQUIRED})\n'
181
f
' set_tests_properties({name} PROPERTIES DEPENDS "{";".join(producer_test_names)}")\n'
182
'else()\n'
183
f
' set_tests_properties({name} PROPERTIES FIXTURES_REQUIRED "{";".join(consumers[name])}")\n'
184
'endif()\n'
185
)
186
187
# we force one test to be run one by one
188
if
coverage:
189
# generate one coverage file per test
190
output.write(
191
f
"set_tests_properties({name} PROPERTIES ENVIRONMENT COVERAGE_FILE={output_rootdir}/.coverage.{name})\n"
192
f
"set_tests_properties({name} PROPERTIES FIXTURES_SETUP {name})\n"
193
)
194
195
if
coverage
and
names:
196
combine_test = (args[
"prefix"
] +
"coverage_combine"
).replace(
"/"
,
"."
)
197
combine_command = re.sub(
198
r" report .*"
,
199
f
" combine {' '.join(f'{output_rootdir}/.coverage.{n}' for n in names)}"
,
200
args[
"coverage_command"
],
201
)
202
output.write(
203
TEST_DESC_TEMPLATE.format(
204
name=combine_test,
205
path=
""
,
206
pytest_cmd=combine_command,
207
properties=properties,
208
)
209
)
210
output.write(
211
f
"set_tests_properties({combine_test} PROPERTIES ENVIRONMENT COVERAGE_FILE={output_rootdir}/.coverage)\n"
212
f
"set_tests_properties({combine_test} PROPERTIES FIXTURES_REQUIRED \"{';'.join(names)}\")\n"
213
f
"set_tests_properties({combine_test} PROPERTIES FIXTURES_SETUP {combine_test})\n"
214
)
215
216
name = (args[
"prefix"
] +
"coverage_report"
).replace(
"/"
,
"."
)
217
output.write(
218
TEST_DESC_TEMPLATE.format(
219
name=name,
220
path=
""
,
221
pytest_cmd=f
"{args['coverage_command']}"
,
222
properties=properties +
" LABELS coverage"
,
223
)
224
)
225
# coverage reports require all related tests to be run first
226
output.write(
227
f
"set_tests_properties({name} PROPERTIES ENVIRONMENT COVERAGE_FILE={output_rootdir}/.coverage)\n"
228
f
"set_tests_properties({name} PROPERTIES FIXTURES_REQUIRED {combine_test})\n"
229
)
230
231
output.close()
232
233
234
def
pytest_configure
(config):
235
config.addinivalue_line(
236
"markers"
,
237
"ctest_fixture_setup(name): mark test to set up a fixture needed by another test"
,
238
)
239
config.addinivalue_line(
240
"markers"
,
241
"ctest_fixture_required(name): mark test to require a fixture set up by another test"
,
242
)
format
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition
MsgStream.cpp:93
collect_for_ctest.pytest_collectstart
pytest_collectstart(collector)
Definition
collect_for_ctest.py:73
collect_for_ctest.pytest_collection_finish
pytest_collection_finish(session)
Definition
collect_for_ctest.py:109
collect_for_ctest.pytest_configure
pytest_configure(config)
Definition
collect_for_ctest.py:234
collect_for_ctest.pytest_addoption
pytest_addoption(parser, pluginmanager)
Definition
collect_for_ctest.py:24
collect_for_ctest.pytest_collection_modifyitems
pytest_collection_modifyitems(session, config, items)
Definition
collect_for_ctest.py:89
GaudiPolicy
python
GaudiTesting
pytest
collect_for_ctest.py
Generated on
for The Gaudi Framework by
1.17.0