Commit c56d8bfb authored by Bernard van Gastel's avatar Bernard van Gastel
Browse files

Initial commit

parents
cmake_minimum_required(VERSION 3.10) # 3.14 handles PIC differently
if (APPLE)
if (CMAKE_BUILD_TYPE EQUAL "Release" OR CMAKE_BUILD_TYPE EQUAL "MinSizeRel")
set(CMAKE_OSX_ARCHITECTURES arm64 x86_64) # needs to be set before project(), see https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_ARCHITECTURES.html
endif()
endif()
project (pep LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
if (CMAKE_SYSTEM_NAME MATCHES "Android")
set(CMAKE_CXX_STANDARD 17)
else()
set(CMAKE_CXX_STANDARD 20)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
OPTION(ALL_WARNINGS "Enable all possible warnings" ON)
OPTION(TEST "Build the tests" ON)
if(UNIX AND NOT APPLE)
# reduce binary size, by making it easier to garbage collect redundant/unused binary code.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections")
endif()
# Set default build type.
if(NOT CMAKE_BUILD_TYPE)
message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.")
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel."
FORCE)
endif()
if (CMAKE_POSITION_INDEPENDENT_CODE)
if(CMAKE_VERSION VERSION_LESS 3.14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie")
else()
include(CheckPIESupported)
endif()
endif()
if (NOT MSVC)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe")
else()
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
# enable C++ exceptions
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES")
# Allow multi-processor compilation: see https://blogs.msdn.microsoft.com/visualstudio/2010/03/07/tuning-c-build-parallelism-in-vs2010/
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
endif()
SET(SANITIZER "" CACHE STRING "Apply sanitizer")
if (SANITIZER)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=${SANITIZER} -g -fno-optimize-sibling-calls -O1")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=${SANITIZER} -g -fno-optimize-sibling-calls -O1")
MESSAGE("using sanitizer ${SANITIZER}")
if (SANITIZER STREQUAL "address")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-address-use-after-scope")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize-address-use-after-scope")
endif()
endif (SANITIZER)
# turn on link time optimisation (LTO) for MinSizeRel builds (helps)
# disabled for now, because it takes a long time
# if(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
# set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
# endif()
# faster linking
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang"
AND (NOT CMAKE_SYSTEM_NAME MATCHES "Darwin"))
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-parentheses-equality") # stb_image triggers this warning due to macro usage
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-reorder-blocks -fno-reorder-blocks-and-partition")
ENDIF()
SET(PACKAGE_MIRROR "http://packages.bitpowder.com/external/")
set(EXT_PROJECTS_DIR ${PROJECT_SOURCE_DIR}/ext)
add_library(extlib INTERFACE)
MESSAGE("using ext/ libsodium")
add_subdirectory(${EXT_PROJECTS_DIR}/libsodium)
target_link_libraries(extlib INTERFACE libsodium)
if (TEST)
add_subdirectory(${EXT_PROJECTS_DIR}/catch2)
add_subdirectory(${EXT_PROJECTS_DIR}/rapidcheck-ext)
target_include_directories(catch2 INTERFACE $<BUILD_INTERFACE: ${EXT_PROJECTS_DIR}/rapidcheck/extras/catch/include>)
set(TEST_LIBS
catch2
rapidcheck
)
endif()
if (ALL_WARNINGS)
# enable all warnings and disable non relevant stuff
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything -Wreorder -Woverloaded-virtual -Wcast-align -Wdeprecated")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-disabled-macro-expansion") # avoid warnings in lua defines such as lua_replace
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-prototypes -Wno-weak-vtables -Wno-zero-length-array -Wno-gnu-anonymous-struct -Wno-reserved-id-macro -Wno-nested-anon-types -Wno-missing-variable-declarations")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-padded")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-exit-time-destructors -Wno-global-constructors")
# possible fix in the future?
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-thread-safety-analysis -Wno-thread-safety-negative")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-documentation -Wno-documentation-unknown-command")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-over-aligned") # getting weird errors that the default allocator guarantees 4 bytes, and 8 bytes is needed if std::function's are used
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option -Wno-zero-as-null-pointer-constant") # lots of false positives
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-extra-semi-stmt") # lots of issues with checkAssert(..); (warning there is an additional ; at the end)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-ctad-maybe-unsupported") # disable errors about missing angle brackets
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c99-extensions") # disable warnings about designated initializers are a C99 extensions (as it is allowed in C++20) (needed for macOS Catalina)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-template") # needed for Catch2 TEMPLATE_LIST_TEST_CASE
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-alloca") # for now disabled, sqlite3 integration (only place alloca is used) is possibly removed anyway
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-undefined-func-template") # goes wrong with Prometheus imports (goes wrong on compile time)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas -Wno-undef")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reserved-identifier") # avoid _StartsWithCapital warnings (because that is reserved)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Woverloaded-virtual") # -Weffc++
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas")
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-final-methods -Wsuggest-final-types")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-maybe-uninitialized")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-variable") # GCC 7.3 complains wrongfully about "error: unused variable '_'" (code: "for (auto& [_, subscriptionInfo] : subscriptions) {")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds") # false positive in memcpy of NumberToStringObject (see https://bitpowder.com:2443/bitpowder/indigo/-/jobs/47193)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-redundant-move") # conflicts with clang 7.0; which requires the moves
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-stringop-overflow") # too many false positives in simplestring.h
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=missing-field-initializers") # needed for HTTPRequestOptions{}
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-mismatched-new-delete") # needed for debug.h InstanceDebug
endif()
if (NOT MSVC)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
endif()
endif (ALL_WARNINGS)
if (MSVC)
# Get rid of warnings about unsafe standard functions such as _open and _ftime64
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_CRT_SECURE_NO_WARNINGS")
# Get rid of warnings about using _close() instead of close() etc, see https://docs.microsoft.com/en-us/previous-versions/ms235384(v=vs.100)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_CRT_NONSTDC_NO_DEPRECATE")
# Get rid of warnings about passing "unchecked iterators" such as pointers to standard functions such as std::copy. See https://msdn.microsoft.com/en-us/library/aa985965.aspx and e.g. https://stackoverflow.com/a/1301343
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_SCL_SECURE_NO_WARNINGS")
# C4068: unknown pragma
# C4200: nonstandard extension used: zeo-sized array in struct/union
# C5030: attribute xxx is not recognized
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4068 /wd4200 /wd5030")
add_definitions(/D_SILENCE_CXX17_RESULT_OF_DEPRECATION_WARNING) # needed for rapidcheck includes
else()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi")
endif()
endif()
add_library(lib${PROJECT_NAME} src/base.cpp src/core.cpp src/zkp.cpp)
target_include_directories(lib${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(lib${PROJECT_NAME} extlib)
if (TEST)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/ext/catch2/contrib)
ENABLE_TESTING()
include(CTest)
include(Catch)
# unit test executable
set (test unit-tests/test.cpp)
FILE(GLOB_RECURSE UNITTESTS unit-tests/*.test.cpp)
add_executable (${PROJECT_NAME}test ${test} ${UNITTESTS})
target_link_libraries(${PROJECT_NAME}test PRIVATE lib${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME}test PRIVATE ${TEST_LIBS})
if (NOT CMAKE_CROSSCOMPILING)
catch_discover_tests(${PROJECT_NAME}test) # do not enable if gtest is part of this (will execute all the gtest tests)
endif()
endif(TEST)
# libpep: Library for polymorphic pseudonimisation and encryption
Author: Bernard van Gastel
Licence: Apache
This library implements the PEP encryption based on ElGamal, and operations on these encrypted messages. A message `M` can be encrypted for a receiver which has public key `Y` associated with it, belonging to secret key `y`. This encryption is random: every time a different random `r` is used, resulting in different ciphertexts (encrypted messages). We represent this encryption function as `EG(r, M, Y)`.
The library supports three operations on ciphertext `in` (= `EG(r, M, Y)`, encrypting message `M` for public key `Y` with random `r`):
- `out = rerandomize(in, s)`: scrambles a ciphertext. Both `in` and `out` can be decrypted by the same secret key `y`, both resulting in the same decrypted message `M`. However, the binary form of `in` and `out` differs. Spec: `in = EG(r, M, Y)` is transformed to `out = EG(r+s, M, Y)`;
- `out = reshuffle(in, n)`: modifies a ciphertext `in` (an encrypted form of `M`), so that after decryption of `out` the decrypted message will be equal to `n*M`. Spec: `in = EG(r, M, Y)` is transformed to `out = EG(r, n*M, Y)`.
- `out = rekey(in, k)`: if `in` can be decrypted by secret key `y`, then `out` can be decrypted by secret key `k*y`. Decryption will both result in message `M`. Spec: `in = EG(r, M, Y)` is transformed to `out = EG(r, M, k*Y)`.
The `rekey(in, k)` and `reshuffle(in, n)` can be combined in a `RKS(in, k, n)`.
There are also zero knowledge proof version of these operations. These are needed so that a party can prove to another party that it has applied the operation on the input data, without revealing the factors used in the operation.
When distributing trust over multiple central servers, these zero knowledge proofs are essential, so that a malfunctioning server can not violate security guarantees of the system.
## Applications
For pseudonimisation, the core operation is *reshuffle* with `n`. It modifies a main pseudonym with a factor `n` that is specific to a user (or user group) receiving the pseudonym. After applying a user specific factor, a pseudonym is called a *local pseudonym*.
Using only a reshuffle is insufficient, as the pseudonym is still encrypted with the public key `Y` (which can be decrypted by the secret key `y`). To allow a user to decrypt the encrypted pseudonym, a *rekey* with `k` is needed, in combination with a protocol to hand the user the secret key `k*y`. The factor `k` is typically tied to the *current session of a user*.
To make pseudonyms harder to trace, rerandomize is applied frequently. This way a binary compare of the encrypted pseudonym will not leak any information.
## Implementation
We are using the Ristretto encoding on a Curve25519. We are using the libsodium implementation. In the source code, scalars are lower case and group elements are upper case. There are a number of arithmetic rules for scalars and group elements: group elements can be added and subtracted from each other. Scalars support addition, subtraction, and multiplication. A scalar can be converted to a group element (by multiplying with the special generator `G`), but not the other way around. Group elements can also be multiplied by a scalar.
Group elements have an *almost* 32 byte range (top bit is always zero, and some other values are invalid). Therefore, not all AES-256 keys (using the full 32 bytes range) are valid group elements. But all group elements are valid AES-256 keys. Group elements can be generated by `GroupElement::Random()` or `GroupElement::FromHash(..)`. Scalars are also 32 bytes, and can be generated with `Scalar::Random()` or `Scalar::FromHash(..)`.
The zero knowledge proofs are offline Schnorr proofs, based on a Fiat-Shamir transform.
The key derivation function used in Blake2b. The hashing algorithm used is SHA512.
Unit tests can be easily added by adding a `unit-tests/foo.test.cpp` file.
## Background
Based on the article by Eric Verheul and Bart Jacobs, *Polymorphic Encryption and Pseudonymisation in Identity Management and Medical Research*. In **Nieuw Archief voor Wiskunde (NAW)**, 5/18, nr. 3, 2017, p. 168-172. A local copy is available in docs/naw5-2017-18-3-168.pdf. This article does not contain the zero knowledge proofs.
cmake_minimum_required(VERSION 3.10)
project(catch2_builder C CXX)
include(ExternalProject)
ExternalProject_Add(catch2_builder
URL
"${PACKAGE_MIRROR}/Catch2.v2.13.7.tar.gz"
"https://codeload.github.com/catchorg/Catch2/tar.gz/v2.13.7"
#URL_HASH SHA1=16621762ee1f8d7d07d29e42322d08ebd071b1fa
DOWNLOAD_NAME "Catch2.v2.13.7.tar.gz"
DOWNLOAD_DIR ../../cache
DOWNLOAD_NO_PROGRESS YES
PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
CMAKE_ARGS
-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_FLAGS=${BASE_CXX_FLAGS}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
-DCATCH_BUILD_TESTING=OFF
CMAKE_GENERATOR ${CMAKE_GENERATOR}
INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/install"
)
# Specify include dir
ExternalProject_Get_Property(catch2_builder install_dir)
set(CATCH2_INCLUDE_DIRS ${install_dir}/include)
add_library(catch2 INTERFACE)
add_dependencies(catch2 catch2_builder)
target_include_directories(catch2 INTERFACE
$<BUILD_INTERFACE:${CATCH2_INCLUDE_DIRS}>
$<INSTALL_INTERFACE:include>
)
target_compile_definitions(catch2 INTERFACE -DCATCH_CONFIG_ENABLE_BENCHMARKING)
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
Catch
-----
This module defines a function to help use the Catch test framework.
The :command:`catch_discover_tests` discovers tests by asking the compiled test
executable to enumerate its tests. This does not require CMake to be re-run
when tests change. However, it may not work in a cross-compiling environment,
and setting test properties is less convenient.
This command is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each Catch test case. Note
that this is in some cases less efficient, as common set-up and tear-down logic
cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: catch_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
catch_discover_tests(target
[TEST_SPEC arg1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[TEST_LIST var]
)
``catch_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--list-test-names-only`` argument. This ensures that the full
list of tests is obtained. Since test discovery occurs at build time, it is
not necessary to re-run CMake when the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the Catch executable, which must be a known CMake executable
target. CMake will substitute the location of the built executable when
running the test.
``TEST_SPEC arg1...``
Specifies test cases, wildcarded test cases, tags and tag expressions to
pass to the Catch executable with the ``--list-test-names-only`` argument.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``catch_discover_tests()`` but with different
``TEST_SPEC`` or ``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``catch_discover_tests``.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``catch_discover_tests()``.
Note that this variable is only available in CTest.
#]=======================================================================]
#------------------------------------------------------------------------------
function(catch_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_SPEC=${_TEST_SPEC}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}"
-D "CTEST_FILE=${ctest_tests_file}"
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if (NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR
"Cannot set more than one TEST_INCLUDE_FILE"
)
endif()
endif()
endfunction()
###############################################################################
set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
)
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
set(prefix "${TEST_PREFIX}")
set(suffix "${TEST_SUFFIX}")
set(spec ${TEST_SPEC})
set(extra_args ${TEST_EXTRA_ARGS})
set(properties ${TEST_PROPERTIES})
set(script)
set(suite)
set(tests)
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
# Run test executable to get list of available tests
if(NOT EXISTS "${TEST_EXECUTABLE}")
message(FATAL_ERROR
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
)
endif()
execute_process(
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
OUTPUT_VARIABLE output
RESULT_VARIABLE result
)
# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
if(${result} EQUAL 0)
message(WARNING
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
)
elseif(${result} LESS 0)
message(FATAL_ERROR
"Error running test executable '${TEST_EXECUTABLE}':\n"
" Result: ${result}\n"
" Output: ${output}\n"
)
endif()
string(REPLACE "\n" ";" output "${output}")
# Parse output
foreach(line ${output})
set(test ${line})
# Escape characters in test case names that would be parsed by Catch2
set(test_name ${test})
foreach(char , [ ])
string(REPLACE ${char} "\\${char}" test_name ${test_name})
endforeach(char)
# ...and add to script
add_command(add_test
"${prefix}${test}${suffix}"
${TEST_EXECUTOR}
"${TEST_EXECUTABLE}"
"${test_name}"
${extra_args}
)
add_command(set_tests_properties
"${prefix}${test}${suffix}"
PROPERTIES
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
${properties}
)
list(APPEND tests "${prefix}${test}${suffix}")
endforeach()
# Create a list of all discovered tests, which users may use to e.g. set
# properties on the tests
add_command(set ${TEST_LIST} ${tests})
# Write CTest script
file(WRITE "${CTEST_FILE}" "${script}")
#==================================================================================================#
# supported macros #
# - TEST_CASE, #
# - SCENARIO, #
# - TEST_CASE_METHOD, #
# - CATCH_TEST_CASE, #
# - CATCH_SCENARIO, #
# - CATCH_TEST_CASE_METHOD. #
# #
# Usage #
# 1. make sure this module is in the path or add this otherwise: #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# 2. make sure that you've enabled testing option for the project by the call: #
# enable_testing() #
# 3. add the lines to the script for testing target (sample CMakeLists.txt): #
# project(testing_target) #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# enable_testing() #
# #
# find_path(CATCH_INCLUDE_DIR "catch.hpp") #
# include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) #
# #
# file(GLOB SOURCE_FILES "*.cpp") #
# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) #
# #
# include(ParseAndAddCatchTests) #
# ParseAndAddCatchTests(${PROJECT_NAME}) #
# #
# The following variables affect the behavior of the script: #
# #
# PARSE_CATCH_TESTS_VERBOSE (Default OFF) #
# -- enables debug messages #
# PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) #
# -- excludes tests marked with [!hide], [.] or [.foo] tags #
# PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) #
# -- adds fixture class name to the test name #
# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) #
# -- adds cmake target name to the test name #
# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) #
# -- causes CMake to rerun when file with tests changes so that new tests will be discovered #
# #
# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way #
# a test should be run. For instance to use test MPI, one can write #
# set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) #
# just before calling this ParseAndAddCatchTests function #
# #
# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test #
# command. For example, to include successful tests in the output, one can write #
# set(AdditionalCatchParameters --success) #
# #
# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source #
# file in the target is set, and contains the list of the tests extracted from that target, or #
# from that file. This is useful, for example to add further labels or properties to the tests. #
# #
#==================================================================================================#
if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8)
message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
endif()
option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
function(ParseAndAddCatchTests_PrintDebugMessage)
if(PARSE_CATCH_TESTS_VERBOSE)
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
endif()
endfunction()
# This removes the contents between
# - block comments (i.e. /* ... */)
# - full line comments (i.e. // ... )
# contents have been read into '${CppCode}'.
# !keep partial line comments
function(ParseAndAddCatchTests_RemoveComments CppCode)
string(ASCII 2 CMakeBeginBlockComment)