-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathCMakeLists.txt
More file actions
286 lines (247 loc) · 10.3 KB
/
CMakeLists.txt
File metadata and controls
286 lines (247 loc) · 10.3 KB
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
find_package(Python 3.12 COMPONENTS Interpreter REQUIRED)
# Verify installation of necessary python modules for specific tests
function(check_python_module_version MODULE_NAME MIN_VERSION OUT_VAR)
execute_process(
COMMAND
${Python_EXECUTABLE} -c
"import sys
try:
import ${MODULE_NAME}
installed_version = getattr(${MODULE_NAME}, '__version__', None)
if not installed_version:
sys.exit(2)
def parse(v):
return tuple(map(int, v.split('.')[:3]))
if parse(installed_version) >= parse('${MIN_VERSION}'):
sys.exit(0)
else:
sys.exit(2) # Version too low
except ImportError as e:
print(f'ImportError: {e}')
sys.exit(1)
except Exception as e:
print(f'Exception: {e}')
sys.exit(1)"
RESULT_VARIABLE _module_check_result
OUTPUT_VARIABLE _module_check_out
ERROR_VARIABLE _module_check_err
)
message(
STATUS
"Check ${MODULE_NAME}: Res=${_module_check_result} Out=${_module_check_out} Err=${_module_check_err}"
)
if(_module_check_result EQUAL 0)
set(${OUT_VAR} TRUE PARENT_SCOPE)
elseif(_module_check_result EQUAL 1)
set(${OUT_VAR} FALSE PARENT_SCOPE) # silent b/c common
elseif(_module_check_result EQUAL 2)
message(
WARNING
"Python module '${MODULE_NAME}' found but version too low (min required: ${MIN_VERSION})."
)
set(${OUT_VAR} FALSE PARENT_SCOPE)
else()
message(WARNING "Unknown error while checking Python module '${MODULE_NAME}'.")
set(${OUT_VAR} FALSE PARENT_SCOPE)
endif()
endfunction()
check_python_module_version("cppyy" "3.6.0" HAS_CPPYY)
check_python_module_version("numba" "0.61.0" HAS_NUMBA)
check_python_module_version("numpy" "2.0.0" HAS_NUMPY)
check_python_module_version("pytest_cov" "4.0.0" HAS_PYTEST_COV)
if(HAS_CPPYY)
# Explicitly define Python executable variable to ensure it is visible in
# the test environment
set(PYTHON_TEST_EXECUTABLE ${Python_EXECUTABLE})
# Export the location of phlex include headers
if(DEFINED ENV{PHLEX_INSTALL})
set(PYTHON_TEST_PHLEX_INSTALL $ENV{PHLEX_INSTALL})
else()
set(PYTHON_TEST_PHLEX_INSTALL ${CMAKE_SOURCE_DIR})
endif()
set(PYTHON_TEST_FILES test_phlex.py unit_test_variant.py)
# Determine pytest command based on coverage support
if(HAS_PYTEST_COV AND ENABLE_COVERAGE)
set(
PYTEST_COMMAND
${PYTHON_TEST_EXECUTABLE}
-m
pytest
--cov=${CMAKE_CURRENT_SOURCE_DIR}
--cov=${PROJECT_SOURCE_DIR}/plugins/python/python
--cov-report=term-missing
--cov-report=xml:${CMAKE_BINARY_DIR}/coverage-python.xml
--cov-report=html:${CMAKE_BINARY_DIR}/coverage-python-html
--cov-config=${CMAKE_CURRENT_SOURCE_DIR}/.coveragerc
${PYTHON_TEST_FILES}
)
message(STATUS "Python tests will run with coverage reporting (pytest-cov)")
else()
set(PYTEST_COMMAND ${PYTHON_TEST_EXECUTABLE} -m pytest ${PYTHON_TEST_FILES})
if(ENABLE_COVERAGE AND NOT HAS_PYTEST_COV)
message(WARNING "ENABLE_COVERAGE is ON but pytest-cov not found; Python coverage disabled")
endif()
endif()
# tests of the python support modules (relies on cppyy)
add_test(NAME py:phlex COMMAND ${PYTEST_COMMAND} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TEST py:phlex PROPERTY ENVIRONMENT "PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL}")
endif()
set(ACTIVE_PY_CPHLEX_TESTS "")
# numpy support if installed
if(HAS_NUMPY)
# phlex-based tests that require numpy support
add_test(NAME py:vec COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyvec.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:vec)
add_test(NAME py:vectypes COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyvectypes.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:vectypes)
add_test(NAME py:callback3 COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pycallback3.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:callback3)
# Expect failure for these tests (check for error propagation and type checking)
add_test(NAME py:raise COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyraise.jsonnet)
set_tests_properties(
py:raise
PROPERTIES PASS_REGULAR_EXPRESSION "RuntimeError: Intentional failure"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:raise)
add_test(NAME py:badbool COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pybadbool.jsonnet)
set_tests_properties(
py:badbool
PROPERTIES
PASS_REGULAR_EXPRESSION
"Python conversion error for type bool: boolean value should be bool, or integer 1 or 0"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:badbool)
add_test(NAME py:badint COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pybadint.jsonnet)
set_tests_properties(
py:badint
PROPERTIES
PASS_REGULAR_EXPRESSION
"Python conversion error for type long: int/long conversion expects an integer object"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:badint)
add_test(NAME py:baduint COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pybaduint.jsonnet)
set_tests_properties(
py:baduint
PROPERTIES
PASS_REGULAR_EXPRESSION
"Python conversion error for type uint: can't convert negative value to unsigned long"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:baduint)
add_test(
NAME py:mismatch_variant
COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pymismatch_variant.jsonnet
)
set_tests_properties(
py:mismatch_variant
PROPERTIES
PASS_REGULAR_EXPRESSION "number of inputs .* does not match number of annotation types"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:mismatch_variant)
add_test(NAME py:veclists COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyveclists.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:veclists)
add_test(NAME py:types COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pytypes.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:types)
endif()
# C++ helper to provide a driver
add_library(cppsource4py MODULE source.cpp)
target_link_libraries(cppsource4py PRIVATE phlex::module)
# phlex-based tests (no cppyy dependency)
add_test(NAME py:add COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyadd.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:add)
add_test(NAME py:config COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyconfig.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:config)
add_test(NAME py:reduce COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyreduce.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:reduce)
add_test(NAME py:coverage COMMAND phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pycoverage.jsonnet)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:coverage)
add_test(
NAME py:mismatch
COMMAND ${PROJECT_BINARY_DIR}/bin/phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pymismatch.jsonnet
)
set_tests_properties(
py:mismatch
PROPERTIES
PASS_REGULAR_EXPRESSION "number of inputs .* does not match number of annotation types"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:mismatch)
# "failing" tests for checking error paths
add_test(
NAME py:failure
COMMAND ${PROJECT_BINARY_DIR}/bin/phlex -c ${CMAKE_CURRENT_SOURCE_DIR}/pyfailure.jsonnet
)
set_tests_properties(
py:failure
PROPERTIES PASS_REGULAR_EXPRESSION "property \"input\" does not exist"
)
list(APPEND ACTIVE_PY_CPHLEX_TESTS py:failure)
set(TEST_PYTHONPATH ${CMAKE_CURRENT_SOURCE_DIR})
# Add the python plugin source directory to PYTHONPATH so tests can use phlex package
set(TEST_PYTHONPATH ${TEST_PYTHONPATH}:${PROJECT_SOURCE_DIR}/plugins/python/python)
# Always add site-packages to PYTHONPATH for tests, as embedded python might
# not find them especially in spack environments where they are in
# non-standard locations
if(Python_SITELIB)
set(TEST_PYTHONPATH ${TEST_PYTHONPATH}:${Python_SITELIB})
endif()
if(Python_SITEARCH AND NOT "${Python_SITEARCH}" STREQUAL "${Python_SITELIB}")
set(TEST_PYTHONPATH ${TEST_PYTHONPATH}:${Python_SITEARCH})
endif()
if(DEFINED ENV{VIRTUAL_ENV})
# Keep this for backward compatibility or if it adds something else
endif()
set(TEST_PYTHONPATH ${TEST_PYTHONPATH}:$ENV{PYTHONPATH})
# Environment variables required:
set(
PYTHON_TEST_ENVIRONMENT
"SPDLOG_LEVEL=debug;PHLEX_PLUGIN_PATH=${PROJECT_BINARY_DIR};PYTHONPATH=${TEST_PYTHONPATH};PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL}"
)
set_tests_properties(${ACTIVE_PY_CPHLEX_TESTS} PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}")
# phlex-based test to ensure that sys.path is sanely set for virtual environment
set(PY_VIRTUAL_ENV_DIR ${CMAKE_CURRENT_BINARY_DIR}/py_virtual_env)
execute_process(COMMAND python -m venv ${PY_VIRTUAL_ENV_DIR})
configure_file(pysyspath.jsonnet.in pysyspath.jsonnet @ONLY)
add_test(NAME py:syspath COMMAND phlex -c ${CMAKE_CURRENT_BINARY_DIR}/pysyspath.jsonnet)
# Activate the Python virtual environment "by hand". Requires setting the VIRTUAL_ENV
# environment variable and prepending to the PATH environment variable.
set_tests_properties(
py:syspath
PROPERTIES
ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT};VIRTUAL_ENV=${PY_VIRTUAL_ENV_DIR}"
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${PY_VIRTUAL_ENV_DIR}/bin"
)
# Unit tests for the phlex python package
add_test(
NAME py:unit_variant
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/unit_test_variant.py
)
set_tests_properties(py:unit_variant PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}")
# Python coverage target
if(HAS_PYTEST_COV AND ENABLE_COVERAGE)
if(HAS_CPPYY)
# When cppyy is available, use the full pytest command
add_custom_target(
coverage-python
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${TEST_PYTHONPATH}
PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL} ${PYTEST_COMMAND}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Python coverage report"
)
else()
# When cppyy is not available, run a simpler pytest command
# for standalone Python tests (like unit_test_variant.py)
set(PYTHON_TEST_PHLEX_INSTALL_FALLBACK ${CMAKE_SOURCE_DIR})
add_custom_target(
coverage-python
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${TEST_PYTHONPATH}
PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL_FALLBACK} ${Python_EXECUTABLE} -m pytest
--cov=${CMAKE_CURRENT_SOURCE_DIR} --cov=${PROJECT_SOURCE_DIR}/plugins/python/python
--cov-report=term-missing --cov-report=xml:${CMAKE_BINARY_DIR}/coverage-python.xml
--cov-report=html:${CMAKE_BINARY_DIR}/coverage-python-html
--cov-config=${CMAKE_CURRENT_SOURCE_DIR}/.coveragerc unit_test_variant.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Python coverage report (standalone tests only)"
)
endif()
endif()