The new feature in this recipe is the add_catch_test macro. The macro expects two arguments, _name and _cost, and we can use these arguments inside the macro to call add_test and set_tests_properties. The leading underscores are our choice, but with this we indicate to the reader that these arguments have local scope and can only be accessed within the macro. Also, note that the macro automatically populates ${ARGC} (number of arguments) and ${ARGV} (list of arguments), and we verified this in the output:
-- add_catch_test called with 2 arguments: short;1.5
-- add_catch_test called with 3 arguments: long;2.5;extra_argument
The macro also defines ${ARGN}, which holds the list of arguments past the last expected argument. In addition, we can also address arguments with ${ARGV0}, ${ARGV1}, and so on. Observe how we caught the unexpected argument (extra_argument) in this call:
add_catch_test(long 2.5 extra_argument)
We have done that using the following:
set(_argn "${ARGN}")
if(_argn)
message(STATUS "oops - macro received argument(s) we did not expect: ${ARGN}")
endif()
In this if-check, we had to introduce a new variable and could not query ARGN directly since it is not a variable in the usual CMake sense. With this macro, we were not only able to define tests by their name and command but also indicate the expected cost, which led to the "long" test being started before the "short" test thanks to the COST property.
We could have implemented this using a function instead of a macro with the same syntax:
function(add_catch_test _name _cost)
...
endfunction()
The difference between macros and functions is their variable scope. Macros are executed in the scope of the caller whereas functions have own variable scope. In other words, if we need to set or modify variables that should be available to the caller, we typically use a macro. If no output variables are set or modified, we preferably use a function. We remark that it is possible to modify parent scope variables also in a function, but this has to be explicitly indicated using PARENT_SCOPE:
set(variable_visible_outside "some value" PARENT_SCOPE)
To demonstrate the scope, we have written the following call after the definition of the macro:
set(num_macro_calls 0)
add_catch_test(short 1.5)
add_catch_test(long 2.5 extra_argument)
message(STATUS "in total there were ${num_macro_calls} calls to add_catch_test")
Inside the macro, we increase num_macro_calls by 1:
math(EXPR num_macro_calls "${num_macro_calls} + 1")
And this is the output produced:
-- in total there were 2 calls to add_catch_test
If we changed the macro to a function, the tests would still work but num_macro_calls would remain 0 throughout the calls in the parent scope. It is useful to imagine CMake macros as being like functions, which are substituted directly into the place where they are called (inlined in the C language sense). It is useful to imagine CMake functions as black boxes where nothing comes back unless you explicitly define it as PARENT_SCOPE. Functions in CMake do not have return values.