File: DuneTestMacros.cmake

package info (click to toggle)
dune-common 2.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 5,112 kB
  • sloc: cpp: 44,714; python: 3,480; sh: 1,590; makefile: 17
file content (453 lines) | stat: -rw-r--r-- 18,923 bytes parent folder | download
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# SPDX-FileCopyrightInfo: Copyright (C) DUNE Project contributors, see file LICENSE.md in module root
# SPDX-License-Identifier: LicenseRef-GPL-2.0-only-with-DUNE-exception

# Module that provides tools for testing the Dune way.
#
# Note that "the Dune way" of doing this has changed after
# the 2.4 release. See the build system documentation for details.
#
# .. cmake_function:: dune_declare_test_label
#
#    .. cmake_brief::
#
#       Declare labels for :ref:`dune_add_test`.
#
#    .. cmake_param:: LABELS
#       :multi:
#
#       The names of labels to declare.  Label names must be nonempty and
#       consist only of alphanumeric characters plus :code:`-` and :code:`_`
#       to make sure it is easy to construct regular expressions from them for
#       :code:`ctest -L ${label_regex}`.
#
#    Labels need to be declared to ensure that the target
#    :code:`build_${label}_tests` exists.  They will normally be declared
#    on-demand by :ref:`dune_add_test`.  But sometimes it is useful to be able to
#    run :code:`make build_${label}_tests` whether or not any tests with that
#    label exists in a module.  For these cases :ref:`dune_declare_test_label` can
#    be called explicitly.
#
#    The label :code:`quick` is always predeclared.
#
# .. cmake_function:: dune_add_test
#
#    .. cmake_brief::
#
#       Adds a test to the Dune testing suite!
#
#    .. cmake_param:: NAME
#       :single:
#
#       The name of the test that should be added. If an executable
#       is also added (by specifying SOURCES), the executable is also
#       named accordingly. If omitted, the name will be deduced from
#       the (single) sources parameter or from the given target. Note
#       that this requires you to take care, that you only use a target
#       or source file for but one such test.
#
#    .. cmake_param:: SOURCES
#       :multi:
#
#       The source files that this test depends on. These are the
#       sources that will be passed to :ref:`add_executable`.
#
#       You *must* specify either :code:`SOURCES` or :code:`TARGET`.
#
#    .. cmake_param:: TARGET
#       :single:
#
#       An executable target which should be used for the test. Use
#       this option over the :code:`SOURCES` parameter if you want to
#       reuse already added targets.
#
#       You *must* specify either :code:`SOURCES` or :code:`TARGET`.
#
#    .. cmake_param:: COMPILE_DEFINITIONS
#       :multi:
#       :argname: def
#
#       A set of compile definitions to add to the target.
#       Only definitions beyond the application of :ref:`add_dune_all_flags`
#       have to be stated.
#       This is only used, if :code:`dune_add_test` adds the executable itself.
#
#    .. cmake_param:: COMPILE_FLAGS
#       :multi:
#       :argname: flag
#
#       A set of non-definition compile flags to add to the target.
#       Only flags beyond the application of :ref:`add_dune_all_flags`
#       have to be stated.
#       This is only used, if :code:`dune_add_test` adds the executable itself.
#
#    .. cmake_param:: LINK_LIBRARIES
#       :multi:
#       :argname: lib
#
#       A list of libraries to link the target to.
#       Only libraries beyond the application of :ref:`add_dune_all_flags`
#       have to be stated.
#       This is only used, if :code:`dune_add_test` adds the executable itself.
#
#    .. cmake_param:: EXPECT_COMPILE_FAIL
#       :option:
#
#       If given, the test is expected to not compile successfully!
#
#    .. cmake_param:: EXPECT_FAIL
#       :option:
#
#       If given, this test is expected to compile, but fail to run.
#
#    .. cmake_param:: CMD_ARGS
#       :multi:
#       :argname: arg
#
#       Command line arguments that should be passed to this test.
#
#    .. cmake_param:: MPI_RANKS
#       :multi:
#       :argname: ranks
#
#       The numbers of cores that this test should be executed with.
#       Note that one test (in the ctest sense) is created for each number
#       given here. Any number exceeding the user-specified processor maximum
#       :ref:`DUNE_MAX_TEST_CORES` will be ignored. Tests with a
#       processor number :code:`n` higher than one will have the suffix
#       :code:`-mpi-n` appended to their name. You need to specify the
#       TIMEOUT option when specifying the MPI_RANKS option.
#
#    .. cmake_param:: CMAKE_GUARD
#       :multi:
#       :argname: condition
#
#       A number of conditions that CMake should evaluate before adding this
#       test. If one of the conditions fails, the test should be shown
#       as skipped in the test summary. Use this feature instead of guarding
#       the call to :code:`dune_add_test` with an :code:`if` clause.
#
#       The passed condition can be a complex expression like
#       `( A OR B ) AND ( C OR D )`. Mind the spaces around the parentheses.
#
#       Example: Write CMAKE_GUARD dune-foo_FOUND if you want your test to only
#       build and run when the dune-foo module is present.
#
#    .. cmake_param:: COMMAND
#       :multi:
#       :argname: cmd
#
#       You may specify the COMMAND option to give the exact command line to be
#       executed when running the test. This defaults to the name of the executable
#       added by dune_add_test for this test or the name of the executable of the given TARGET.
#       Note that if you specify both CMD_ARGS
#       and COMMAND, the given CMD_ARGS will be put behind your COMMAND. If you use
#       this in combination with the MPI_RANKS parameter, the call to mpi will still be
#       wrapped around the given commands.
#
#    .. cmake_param:: COMPILE_ONLY
#       :option:
#
#       Set if the given test should only be compiled during :code:`make build_tests`,
#       but not run during :code:`make test`. This is useful if you compile the same
#       executable twice, but with different compile flags, where you want to assure that
#       it compiles with both sets of flags, but you already know they will produce the
#       same result.
#
#    .. cmake_param:: TIMEOUT
#       :single:
#
#       If set, the test will time out after the given number of seconds. This supersedes
#       any timeout setting in ctest (see `cmake --help-property TIMEOUT`). If you
#       specify the MPI_RANKS option, you need to specify a TIMEOUT.
#
#    .. cmake_param:: WORKING_DIRECTORY
#       :single:
#
#       Set the WORKING_DIRECTORY test property to specify the working directory in which to execute the test.
#       If not specified the test will be run with the current working directory set to the build directory corresponding to the current source directory.
#
#    .. cmake_param:: LABELS
#       :multi:
#
#       A list of labels to add to the test.  This has two effects: it sets
#       the LABELS property on the test so :code:`ctest -L ${label_regex}` can
#       be used to run all tests with certain labels.  It also adds any
#       targets created as dependencies to a custom target, so you can build
#       all tests with a particular label by doing :code:`make
#       build_${label}_tests` without having to build all the other tests as
#       well.
#
#       The :code:`build_${label}_tests` targets are created on-demand the
#       first time a test with that label is added.  In some situations it can
#       depend on the values of cmake cache variables whether a test is added,
#       and then it can happen that the :code:`build_${target}_tests` target
#       exists only sometimes.  If your workflow relies on the existence of
#       these targets, even if building them just returns successfully without
#       doing anything, you can ensure they exist by calling
#       :ref:`dune_declare_test_label` unconditionally.  The label
#       :code:`quick` is always predeclared in this way.
#
#       The label names must be non-empty, and must only contain alphanumeric
#       characters other than :code:`-` or :code:`_`.  This restriction is in
#       place to make it easy to construct regular expressions from the label
#       names for :code:`ctest -L ${label_regex}`.
#
#    This function defines the Dune way of adding a test to the testing suite.
#    You may either add the executable yourself through :ref:`add_executable`
#    and pass it to the :code:`TARGET` option, or you may rely on :ref:`dune_add_test`
#    to do so.
#
# .. cmake_variable:: DUNE_REENABLE_ADD_TEST
#
#    You may set this variable to True either through your opts file or in your module
#    (before the call to :code:`include(DuneMacros)`) to suppress the error that is thrown if
#    :code:`add_test` is used. You should only do that if you have proper reason to do so.
#
# .. cmake_variable:: DUNE_MAX_TEST_CORES
#
#    You may set this variable to give an upperbound to the number of processors, that
#    a single test may use. Defaults to 2, when MPI is found and to 1 otherwise.
#
# .. cmake_variable:: DUNE_BUILD_TESTS_ON_MAKE_ALL
#
#    You may set this variable through your opts file or on a per module level (in the toplevel
#    :code:`CMakeLists.txt` before :code:`include(DuneMacros)`) to have the Dune build system
#    build all tests during `make all`. Note, that this may take quite some time for some modules.
#    If not in use, you have to build tests through the target :code:`build_tests`.
#
# .. cmake_variable:: PYTHON_TEST
#
#    This flag specifies a python test and is set by the dune_python_add_test command. It disables the check on the existence of the target file.
#
include_guard(GLOBAL)

# enable the testing suite on the CMake side.
enable_testing()
include(CTest)

# Introduce a target that triggers the building of all tests
add_custom_target(build_tests)

function(dune_declare_test_label)
  cmake_parse_arguments(arg "" "" "LABELS" ${ARGN})

  if( (DEFINED arg_UNPARSED_ARGUMENTS) AND NOT ( arg_UNPARSED_ARGUMENTS STREQUAL "" ) )
    message(FATAL_ERROR "Unhandled extra arguments given to dune_declare_test_label(): "
      "<${arg_UNPARSED_ARGUMENTS}>")
  endif()

  foreach(label IN LISTS arg_LABELS)
    # Make sure the label is not empty, and does not contain any funny
    # characters, in particular regex characters
    if(NOT (label MATCHES "[-_0-9a-zA-Z]+"))
      message(FATAL_ERROR "Refusing to add label \"${label}\" since it is "
        "empty or contains funny characters (characters other than "
        "alphanumeric ones and \"-\" or \"_\"; the intent of this restriction "
        "is to make construction of the argument to \"ctest -L\" easier")
    endif()
    set(target "build_${label}_tests")
    if(NOT TARGET "${target}")
      add_custom_target("${target}")
    endif()
  endforeach()
endfunction(dune_declare_test_label)

# predefine "quick" test label so build_quick_tests can be built
# unconditionally
dune_declare_test_label(LABELS quick)

# Set the default on the variable DUNE_MAX_TEST_CORES
if(NOT DUNE_MAX_TEST_CORES)
  set(DUNE_MAX_TEST_CORES 2)
endif()

function(dune_add_test)
  set(OPTIONS EXPECT_COMPILE_FAIL EXPECT_FAIL SKIP_ON_77 COMPILE_ONLY PYTHON_TEST)
  set(SINGLEARGS NAME TARGET TIMEOUT WORKING_DIRECTORY)
  set(MULTIARGS SOURCES COMPILE_DEFINITIONS COMPILE_FLAGS LINK_LIBRARIES CMD_ARGS MPI_RANKS COMMAND CMAKE_GUARD LABELS)
  cmake_parse_arguments(ADDTEST "${OPTIONS}" "${SINGLEARGS}" "${MULTIARGS}" ${ARGN})

  # Check whether the parser produced any errors
  if(ADDTEST_UNPARSED_ARGUMENTS)
    message(WARNING "Unrecognized arguments ('${ADDTEST_UNPARSED_ARGUMENTS}') for dune_add_test!")
  endif()

  # Check input for validity and apply defaults
  if(NOT ADDTEST_SOURCES AND NOT ADDTEST_TARGET)
    message(FATAL_ERROR "You need to specify either the SOURCES or the TARGET option for dune_add_test!")
  endif()
  if(ADDTEST_SOURCES AND ADDTEST_TARGET)
    message(FATAL_ERROR "You cannot specify both SOURCES and TARGET for dune_add_test")
  endif()
  if(NOT ADDTEST_NAME)
    # try deducing the test name from the executable name
    if(ADDTEST_TARGET)
      set(ADDTEST_NAME ${ADDTEST_TARGET})
    endif()
    # try deducing the test name form the source name
    if(ADDTEST_SOURCES)
      # deducing a name is only possible with a single source argument
      list(LENGTH ADDTEST_SOURCES len)
      if(NOT len STREQUAL "1")
        message(FATAL_ERROR "Cannot deduce test name from multiple sources!")
      endif()
      # strip file extension
      get_filename_component(ADDTEST_NAME ${ADDTEST_SOURCES} NAME_WE)
    endif()
  endif()
  if(NOT ADDTEST_COMMAND)
    if(ADDTEST_TARGET)
      set(ADDTEST_COMMAND ${ADDTEST_TARGET})
    else()
      set(ADDTEST_COMMAND ${ADDTEST_NAME})
    endif()
  endif()
  if(ADDTEST_MPI_RANKS AND (NOT ADDTEST_TIMEOUT))
    message(FATAL_ERROR "dune_add_test: You need to specify the TIMEOUT parameter if using the MPI_RANKS parameter.")
  endif()
  if(NOT ADDTEST_MPI_RANKS)
    set(ADDTEST_MPI_RANKS 1)
  endif()
  if(NOT ADDTEST_TIMEOUT)
    set(ADDTEST_TIMEOUT 300)
  endif()
  if(NOT ADDTEST_WORKING_DIRECTORY)
    set(ADDTEST_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
  endif()
  foreach(num ${ADDTEST_MPI_RANKS})
    if(NOT "${num}" MATCHES "[1-9][0-9]*")
      message(FATAL_ERROR "${num} was given to the MPI_RANKS argument of dune_add_test, but it does not seem like a correct processor number")
    endif()
  endforeach()
  if(ADDTEST_SKIP_ON_77)
    message(WARNING "The SKIP_ON_77 option for dune_add_test is obsolete, it is now enabled by default.")
  endif()

  # Discard all parallel tests if MPI was not found
  if(NOT HAVE_MPI)
    set(DUNE_MAX_TEST_CORES 1)
  endif()

  # Find out whether this test should be a dummy
  set(SHOULD_SKIP_TEST FALSE)
  set(FAILED_CONDITION_PRINTING "")
  foreach(condition ${ADDTEST_CMAKE_GUARD})
    separate_arguments(condition)
    if(NOT (${condition}))
      set(SHOULD_SKIP_TEST TRUE)
      set(FAILED_CONDITION_PRINTING "${FAILED_CONDITION_PRINTING}std::cout << \"  ${condition}\" << std::endl;\n")
    endif()
  endforeach()

  # If we do nothing, switch the sources for a dummy source
  if(SHOULD_SKIP_TEST)
    dune_module_path(MODULE dune-common RESULT scriptdir SCRIPT_DIR)
    set(ADDTEST_TARGET)
    set(dummymain ${CMAKE_CURRENT_BINARY_DIR}/main77_${ADDTEST_NAME}.cc)
    configure_file(${scriptdir}/main77.cc.in ${dummymain})
    set(ADDTEST_SOURCES ${dummymain})
  endif()

  # Add the executable if it is not already present
  if(ADDTEST_SOURCES)
    add_executable(${ADDTEST_NAME} ${ADDTEST_SOURCES})
    # add all flags to the target!
    add_dune_all_flags(${ADDTEST_NAME})
    # This is just a placeholder
    target_compile_definitions(${ADDTEST_NAME} PUBLIC ${ADDTEST_COMPILE_DEFINITIONS})
    target_compile_options(${ADDTEST_NAME} PUBLIC ${ADDTEST_COMPILE_FLAGS})
    target_link_libraries(${ADDTEST_NAME} PUBLIC ${ADDTEST_LINK_LIBRARIES})
    set(ADDTEST_TARGET ${ADDTEST_NAME})
  endif()

  # Make sure to exclude the target from all, even when it is user-provided
  if(DUNE_BUILD_TESTS_ON_MAKE_ALL AND (NOT ADDTEST_EXPECT_COMPILE_FAIL))
    set_property(TARGET ${ADDTEST_TARGET} PROPERTY EXCLUDE_FROM_ALL 0)
  else()
    set_property(TARGET ${ADDTEST_TARGET} PROPERTY EXCLUDE_FROM_ALL 1)
  endif()

  # make sure each label exists and its name is acceptable
  dune_declare_test_label(LABELS ${ADDTEST_LABELS})

  # Have build_tests and build_${label}_tests depend on the given target in
  # order to trigger the build correctly
  if(NOT ADDTEST_EXPECT_COMPILE_FAIL)
    add_dependencies(build_tests ${ADDTEST_TARGET})
    foreach(label IN LISTS ADDTEST_LABELS)
      add_dependencies(build_${label}_tests ${ADDTEST_TARGET})
    endforeach()
  endif()

  # Process the EXPECT_COMPILE_FAIL option
  if(ADDTEST_EXPECT_COMPILE_FAIL)
    set(ADDTEST_COMMAND "${CMAKE_COMMAND}")
    set(ADDTEST_CMD_ARGS --build . --target ${ADDTEST_TARGET} --config "$<CONFIGURATION>")
  endif()

  # Add one test for each specified processor number
  foreach(procnum ${ADDTEST_MPI_RANKS})
    if((NOT "${procnum}" GREATER "${DUNE_MAX_TEST_CORES}") AND (NOT ADDTEST_COMPILE_ONLY))
      set(ACTUAL_NAME ${ADDTEST_NAME})
      set(ACTUAL_CMD_ARGS ${ADDTEST_CMD_ARGS})
      if(TARGET "${ADDTEST_COMMAND}")
        # if the target name is specified as command, expand to full path using the TARGET_FILE generator expression
        set(ACTUAL_TESTCOMMAND "$<TARGET_FILE:${ADDTEST_COMMAND}>")
      else()
        set(ACTUAL_TESTCOMMAND "${ADDTEST_COMMAND}")
      endif()

      # modify test name and command for parallel tests
      if(NOT ${procnum} STREQUAL "1")
        set(ACTUAL_NAME "${ACTUAL_NAME}-mpi-${procnum}")
        set(ACTUAL_CMD_ARGS ${MPIEXEC_PREFLAGS} ${MPIEXEC_NUMPROC_FLAG} ${procnum} "${ACTUAL_TESTCOMMAND}" ${MPIEXEC_POSTFLAGS} ${ACTUAL_CMD_ARGS})
        set(ACTUAL_TESTCOMMAND "${MPIEXEC}")
      endif()

      # if this is a skipped test because a guard was false, overwrite the command
      if(SHOULD_SKIP_TEST)
        set(ACTUAL_TESTCOMMAND "$<TARGET_FILE:${ADDTEST_TARGET}>")
        set(ACTUAL_CMD_ARGS)
      endif()

      # Now add the actual test
      _add_test(NAME ${ACTUAL_NAME}
                COMMAND "${ACTUAL_TESTCOMMAND}" ${ACTUAL_CMD_ARGS}
                WORKING_DIRECTORY "${ADDTEST_WORKING_DIRECTORY}"
               )

      # Make the test depend on the existence of the target to trigger "Not Run" response
      if(NOT ADDTEST_EXPECT_COMPILE_FAIL AND NOT ADDTEST_PYTHON_TEST)
        set_tests_properties(${ACTUAL_NAME} PROPERTIES REQUIRED_FILES $<TARGET_FILE:${ADDTEST_TARGET}>)
      endif()
      # Define the number of processors (ctest will coordinate this with the -j option)
      set_tests_properties(${ACTUAL_NAME} PROPERTIES PROCESSORS ${procnum})
      # Apply the timeout (which was defaulted to 5 minutes if not specified)
      set_tests_properties(${ACTUAL_NAME} PROPERTIES TIMEOUT ${ADDTEST_TIMEOUT})
      # Process the EXPECT_FAIL option
      if(ADDTEST_EXPECT_COMPILE_FAIL OR ADDTEST_EXPECT_FAIL)
        set_tests_properties(${ACTUAL_NAME} PROPERTIES WILL_FAIL true)
      endif()
      # When using ninja, we must call the build command from ${PROJECT_BINARY_DIR}
      if(ADDTEST_EXPECT_COMPILE_FAIL)
        set_tests_properties(${ACTUAL_NAME} PROPERTIES WORKING_DIRECTORY "${PROJECT_BINARY_DIR}")
      endif()
      # Skip the test if the return code is 77!
      set_tests_properties(${ACTUAL_NAME} PROPERTIES SKIP_RETURN_CODE 77)
      # Set the labels on the test
      set_tests_properties(${ACTUAL_NAME} PROPERTIES LABELS "${ADDTEST_LABELS}")
    endif()
  endforeach()
endfunction()

macro(add_directory_test_target)
  message(FATAL_ERROR "The function add_directory_test_target has been removed alongside all testing magic in dune-common. Check dune_add_test for the new way!")
endmacro()

macro(add_test)
  if(NOT DUNE_REENABLE_ADD_TEST)
    message(SEND_ERROR "Please use dune_add_test instead of add_test! If you need add_test in a downstream project, set the variable DUNE_REENABLE_ADD_TEST to True in that project to suppress this error.")
  else()
    _add_test(${ARGN})
  endif()
endmacro()