12 pytest plugin that report collected pytest files as CTest tests
14 This plugin is not meant to be used directly, but it is invoked by the
15 CMake function `gaudi_add_pytest()`
20 from collections
import defaultdict
21 from pathlib
import Path
26 "--ctest-output-file",
27 help=
"name of the file to write to communicate to ctest the discovered tests",
30 "--ctest-pytest-command",
32 help=
"how pytest has to be invoked (e.g. using wrapper commands)",
35 "--ctest-pytest-root-dir",
37 help=
"root directory to compute test names",
42 help=
"string to prefix to the generated test names",
48 help=
"labels to attach to the test (the label pytest is always added)",
54 help=
"test properties to set for all discovered tests",
59 help=
"value of CMAKE_CURRENT_BINARY_DIR from which gaudi_add_pytest was invoked",
64 help=
"select modules for which produce coverage reports",
67 "--ctest-coverage-command",
68 default=
"coverage report",
69 help=
"how coverage should be invoked to produce the final report",
74 session, config = collector.session, collector.config
76 name[6:]: getattr(config.option, name)
77 for name
in dir(config.option)
78 if name.startswith(
"ctest_")
81 if args.get(
"binary_dir"):
84 os.environ[
"CMAKE_CURRENT_BINARY_DIR"] = args[
"binary_dir"]
86 session.ctest_args = args
90 if not session.ctest_args.get(
"output_file"):
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)
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)
103 TEST_DESC_TEMPLATE =
"""
104 add_test({name} {pytest_cmd} {path})
105 set_tests_properties({name} PROPERTIES {properties})
110 args = session.ctest_args
111 output_filename = args.get(
"output_file")
113 if not output_filename:
117 output = open(output_filename,
"w")
118 output_rootdir =
Path(output_filename).parent
120 coverage = args[
"coverage"].split(
",")
if args[
"coverage"]
else []
122 args[
"pytest_command"] +=
" --cov-report= --cov-reset " +
" ".join(
123 f
"--cov={module}" for module
in coverage
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"])
131 producers = defaultdict(list)
132 consumers = defaultdict(list)
133 fixtures = defaultdict(list)
135 for path
in sorted(session.ctest_files):
136 name = os.path.relpath(path, args[
"pytest_root_dir"]).replace(
"/",
".")
137 if name.endswith(
".py"):
143 name = args[
"prefix"][:-1]
145 name = args[
"prefix"] + name
146 pytest_cmd = args[
"pytest_command"]
149 TEST_DESC_TEMPLATE.format(
152 pytest_cmd=pytest_cmd,
153 properties=properties,
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)
162 if session.ctest_fixture_required.get(path):
163 for fixture
in session.ctest_fixture_required[path]:
164 consumers[name].append(fixture)
169 if name
in producers:
171 'set_tests_properties('
172 f
'{name} PROPERTIES FIXTURES_SETUP "{";".join(producers[name])}")\n'
175 if name
in consumers:
176 producer_test_names = []
177 for fixture
in consumers[name]:
178 producer_test_names.extend(fixtures[fixture])
180 'if (DEFINED ENV{PYTEST_DISABLE_FIXTURES_REQUIRED})\n'
181 f
' set_tests_properties({name} PROPERTIES DEPENDS "{";".join(producer_test_names)}")\n'
183 f
' set_tests_properties({name} PROPERTIES FIXTURES_REQUIRED "{";".join(consumers[name])}")\n'
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"
195 if coverage
and names:
196 combine_test = (args[
"prefix"] +
"coverage_combine").replace(
"/",
".")
197 combine_command = re.sub(
199 f
" combine {' '.join(f'{output_rootdir}/.coverage.{n}' for n in names)}",
200 args[
"coverage_command"],
203 TEST_DESC_TEMPLATE.format(
206 pytest_cmd=combine_command,
207 properties=properties,
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"
216 name = (args[
"prefix"] +
"coverage_report").replace(
"/",
".")
218 TEST_DESC_TEMPLATE.format(
221 pytest_cmd=f
"{args['coverage_command']}",
222 properties=properties +
" LABELS coverage",
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"
235 config.addinivalue_line(
237 "ctest_fixture_setup(name): mark test to set up a fixture needed by another test",
239 config.addinivalue_line(
241 "ctest_fixture_required(name): mark test to require a fixture set up by another test",