From b4681b10eca47ccf626b887a207b927e6dbf60e2 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 30 Jul 2025 16:20:49 +0200 Subject: [PATCH] Discover Boost test cases automatically after build This adds a global fixture that can parse an additional argument to the test executables (`--generate_ctest_config`). When run by CMake during build, this generates a CTest script containing all the tests and their properties. An additional decorator, that defines CTest properties for a test case or suite that will be added to the tests during config generation. This version needs no hacks, no huge CMake scripts, just a bit of additional C++ code that iterates over all test-cases and collects the information CTest needs. One caveat is still that this does not work with cross-compilation, which probably isn't an issue to begin with, but there are also ways to fix that if necessary. --- CMakeLists.txt | 4 +- cmake/DiscoverBoostTests.cmake | 21 ++ cmake/ExecuteCommandQuietly.cmake | 3 + test/BoostTestConfig.h.in | 17 + test/CMakeLists.txt | 320 +++--------------- test/remote-certificate-fixture.cpp | 6 +- test/remote-certificate-fixture.hpp | 2 +- test/remote-httpmessage.cpp | 7 +- test/remote-httpserverconnection.cpp | 7 +- test/test-ctest.cpp | 126 +++++++ test/test-ctest.hpp | 57 ++++ test/test-runner.cpp | 3 +- third-party/cmake/BoostTestTargets.cmake | 262 -------------- third-party/cmake/BoostTestTargetsDynamic.h | 9 - third-party/cmake/BoostTestTargetsIncluded.h | 7 - third-party/cmake/BoostTestTargetsStatic.h | 7 - .../cmake/CopyResourcesToBuildTree.cmake | 83 ----- .../cmake/GetForceIncludeDefinitions.cmake | 44 --- 18 files changed, 297 insertions(+), 688 deletions(-) create mode 100644 cmake/DiscoverBoostTests.cmake create mode 100644 cmake/ExecuteCommandQuietly.cmake create mode 100644 test/BoostTestConfig.h.in create mode 100644 test/test-ctest.cpp create mode 100644 test/test-ctest.hpp delete mode 100644 third-party/cmake/BoostTestTargets.cmake delete mode 100644 third-party/cmake/BoostTestTargetsDynamic.h delete mode 100644 third-party/cmake/BoostTestTargetsIncluded.h delete mode 100644 third-party/cmake/BoostTestTargetsStatic.h delete mode 100644 third-party/cmake/CopyResourcesToBuildTree.cmake delete mode 100644 third-party/cmake/GetForceIncludeDefinitions.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a087b8ed..f392e51a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ -# CMake 3.8 is required, CMake policy compatibility was verified up to 3.17. -cmake_minimum_required(VERSION 3.8...3.17) +# CMake 3.17 is required, CMake policy compatibility was verified up to 3.17. +cmake_minimum_required(VERSION 3.17...3.17) set(BOOST_MIN_VERSION "1.66.0") set(CMAKE_CXX_STANDARD 17) diff --git a/cmake/DiscoverBoostTests.cmake b/cmake/DiscoverBoostTests.cmake new file mode 100644 index 000000000..f9b54bed1 --- /dev/null +++ b/cmake/DiscoverBoostTests.cmake @@ -0,0 +1,21 @@ +# Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ + +# - Discover tests defined in the Boost.Test executable target +# +# Boost.Test executables should be defined like any other CMake executable target. +# Then, this function can be used on the executable target to discover all the unit +# tests in that executable. +# This relies on the additional commandline argument added in 'test/test-ctest.hpp' +function(target_discover_boost_tests target) + set(testfile "${CMAKE_CURRENT_BINARY_DIR}/${target}_tests.cmake") + set(args -- --generate_ctest_config "${testfile}") + string(REPLACE ";" "$" test "${args}") + + add_custom_command(TARGET "${target}" POST_BUILD + COMMAND ${CMAKE_COMMAND} -DCMD=$ -DARGS="${test}" -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExecuteCommandQuietly.cmake" + ) + + set_property(DIRECTORY + APPEND PROPERTY TEST_INCLUDE_FILES "${testfile}" + ) +endfunction() diff --git a/cmake/ExecuteCommandQuietly.cmake b/cmake/ExecuteCommandQuietly.cmake new file mode 100644 index 000000000..43b1697be --- /dev/null +++ b/cmake/ExecuteCommandQuietly.cmake @@ -0,0 +1,3 @@ +# Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ + +execute_process(COMMAND "${CMD}" ${ARGS} OUTPUT_QUIET) diff --git a/test/BoostTestConfig.h.in b/test/BoostTestConfig.h.in new file mode 100644 index 000000000..50d9663ee --- /dev/null +++ b/test/BoostTestConfig.h.in @@ -0,0 +1,17 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#pragma once + +// This will only be defined if cmake couldn't find a static or dynamic library to link against. +#cmakedefine BOOST_TEST_USE_INCLUDED + +// Only one file needs to include the implementation files +#if defined(BOOST_TEST_USE_INCLUDED) && defined(TEST_INCLUDE_IMPLEMENTATION) +#include + +#else + +//If a dynamic library was found, this will be defined before including the header. +#cmakedefine BOOST_TEST_DYN_LINK +#include +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5498a6d83..cb815da73 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,8 +1,43 @@ # Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ -include(BoostTestTargets) +include(DiscoverBoostTests) -set(types_test_SOURCES +add_library(testdeps INTERFACE) + +if(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY) + find_package(Boost QUIET COMPONENTS unit_test_framework) +endif() + +if(Boost_UNIT_TEST_FRAMEWORK_LIBRARY) + target_link_libraries(testdeps + INTERFACE + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + ) + if(NOT Boost_USE_STATIC_LIBS) + set(BOOST_TEST_DYN_LINK ON) + endif() +else() + set(BOOST_TEST_USE_INCLUDED ON) +endif() + +target_link_libraries(testdeps + INTERFACE + ${base_DEPS} +) + +configure_file(BoostTestConfig.h.in BoostTestTargetConfig.h @ONLY) + +target_include_directories(testdeps + INTERFACE + "${CMAKE_CURRENT_BINARY_DIR}" +) + +target_sources(testdeps INTERFACE + test-runner.cpp + test-ctest.cpp +) + +Set(types_test_SOURCES icingaapplication-fixture.cpp base-type.cpp ${base_OBJS} @@ -44,16 +79,11 @@ endif() # but this results in boost signals e.g. in dbevents.cpp being triggered by icinga-checkresult.cpp test cases that # only pass partially initialised objects. Therefore, the types test cases are decoupled from base and moved to a # separate executable to not crash the base test cases. -add_boost_test(types - SOURCES test-runner.cpp ${types_test_SOURCES} - LIBRARIES ${base_DEPS} - TESTS - types/gettype - types/assign - types/byname - types/instantiate - types/sort_by_load_after +add_executable(testtypes + ${types_test_SOURCES} ) +target_link_libraries(testtypes testdeps) +target_discover_boost_tests(testtypes) set(base_test_SOURCES icingaapplication-fixture.cpp @@ -103,251 +133,11 @@ if(ICINGA2_UNITY_BUILD) mkunity_target(base test base_test_SOURCES) endif() -add_boost_test(base - SOURCES test-runner.cpp ${base_test_SOURCES} - LIBRARIES ${base_DEPS} - TESTS - base_array/construct - base_array/getset - base_array/resize - base_array/insert - base_array/remove - base_array/unique - base_array/foreach - base_array/clone - base_array/json - base_base64/base64 - base_convert/tolong - base_convert/todouble - base_convert/tostring - base_convert/tobool - base_dictionary/construct - base_dictionary/initializer1 - base_dictionary/initializer2 - base_dictionary/get1 - base_dictionary/get2 - base_dictionary/foreach - base_dictionary/remove - base_dictionary/clone - base_dictionary/json - base_dictionary/keys_ordered - base_fifo/construct - base_fifo/io - base_io_engine/timeout_run - base_io_engine/timeout_cancelled - base_io_engine/timeout_scope - base_io_engine/timeout_due_cancelled - base_io_engine/timeout_due_scope - base_json/encode - base_json/decode - base_json/invalid1 - base_object_packer/pack_null - base_object_packer/pack_false - base_object_packer/pack_true - base_object_packer/pack_number - base_object_packer/pack_string - base_object_packer/pack_array - base_object_packer/pack_object - base_match/tolong - base_netstring/netstring - base_object/construct - base_object/getself - base_serialize/scalar - base_serialize/array - base_serialize/dictionary - base_serialize/object - base_shellescape/escape_basic - base_shellescape/escape_quoted - base_stacktrace/stacktrace - base_stream/readline_stdio - base_string/construct - base_string/equal - base_string/clear - base_string/append - base_string/trim - base_string/contains - base_string/replace - base_string/index - base_string/find - base_string/vector_move - base_string/move_string_out_of_Value_type - base_timer/construct - base_timer/interval - base_timer/invoke - base_timer/scope - base_tlsutility/sha1 - base_tlsutility/iscauptodate_ok - base_tlsutility/iscauptodate_expiring - base_tlsutility/iscertuptodate_ok - base_tlsutility/iscertuptodate_expiring - base_tlsutility/iscertuptodate_old - base_tlsutility/VerifyCertificate_revalidate - base_utility/parse_version - base_utility/compare_version - base_utility/comparepasswords_works - base_utility/comparepasswords_issafe - base_utility/validateutf8 - base_utility/EscapeCreateProcessArg - base_utility/TruncateUsingHash - base_utility/FormatDateTime - base_utility/NormalizeTm - base_value/scalar - base_value/convert - base_value/format - config_apply/gettargethosts_literal - config_apply/gettargethosts_const - config_apply/gettargethosts_swapped - config_apply/gettargethosts_two - config_apply/gettargethosts_three - config_apply/gettargethosts_mixed - config_apply/gettargethosts_redundant - config_apply/gettargethosts_badconst - config_apply/gettargethosts_notliteral - config_apply/gettargethosts_wrongop - config_apply/gettargethosts_wrongattr - config_apply/gettargethosts_wrongvar - config_apply/gettargethosts_noindexer - config_apply/gettargetservices_literal - config_apply/gettargetservices_const - config_apply/gettargetservices_swapped_outer - config_apply/gettargetservices_swapped_inner - config_apply/gettargetservices_two - config_apply/gettargetservices_three - config_apply/gettargetservices_mixed - config_apply/gettargetservices_redundant - config_apply/gettargetservices_badconst - config_apply/gettargetservices_notliteral - config_apply/gettargetservices_wrongop_outer - config_apply/gettargetservices_wrongop_host - config_apply/gettargetservices_wrongop_service - config_apply/gettargetservices_wrongattr_host - config_apply/gettargetservices_wrongattr_service - config_apply/gettargetservices_wrongvar_host - config_apply/gettargetservices_wrongvar_service - config_apply/gettargetservices_noindexer_host - config_apply/gettargetservices_noindexer_service - config_ops/simple - config_ops/advanced - icinga_checkresult/host_1attempt - icinga_checkresult/host_2attempts - icinga_checkresult/host_3attempts - icinga_checkresult/service_1attempt - icinga_checkresult/service_2attempts - icinga_checkresult/service_3attempts - icinga_checkresult/host_flapping_notification - icinga_checkresult/service_flapping_notification - icinga_checkresult/suppressed_notification - icinga_dependencies/multi_parent - icinga_dependencies/push_dependency_groups_to_registry - icinga_dependencies/default_redundancy_group_registration_unregistration - icinga_dependencies/simple_redundancy_group_registration_unregistration - icinga_dependencies/mixed_redundancy_group_registration_unregsitration - icinga_notification/strings - icinga_notification/state_filter - icinga_notification/type_filter - icinga_notification/no_filter_problem_no_duplicate - icinga_notification/filter_problem_no_duplicate - icinga_notification/volatile_filter_problem_duplicate - icinga_notification/no_recovery_filter_no_duplicate - icinga_notification/recovery_filter_duplicate - icinga_macros/simple - icinga_legacytimeperiod/simple - icinga_legacytimeperiod/is_in_range - icinga_legacytimeperiod/out_of_range_segments - icinga_legacytimeperiod/include_exclude_timeperiods - icinga_legacytimeperiod/advanced - icinga_legacytimeperiod/dst - icinga_legacytimeperiod/dst_isinside - icinga_legacytimeperiod/find_nth_weekday - icinga_perfdata/empty - icinga_perfdata/simple - icinga_perfdata/quotes - icinga_perfdata/multiple - icinga_perfdata/multiline - icinga_perfdata/normalize - icinga_perfdata/uom - icinga_perfdata/warncritminmax - icinga_perfdata/ignore_warn_crit_ranges - icinga_perfdata/invalid - icinga_perfdata/multi - icinga_perfdata/scientificnotation - icinga_perfdata/parse_edgecases - icinga_perfdata/empty_warn_crit_min_max - methods_pluginnotificationtask/truncate_long_output - remote_certs_fixture/prepare_directory - remote_certs_fixture/cleanup_certs - remote_httpmessage/request_parse - remote_httpmessage/request_params - remote_httpmessage/response_clear - remote_httpmessage/response_flush_nothrow - remote_httpmessage/response_flush_throw - remote_httpmessage/response_write_empty - remote_httpmessage/response_write_fixed - remote_httpmessage/response_write_chunked - remote_httpmessage/response_sendjsonbody - remote_httpmessage/response_sendjsonerror - remote_httpmessage/response_sendfile - remote_httpserverconnection/expect_100_continue - remote_httpserverconnection/bad_request - remote_httpserverconnection/error_access_control - remote_httpserverconnection/error_accept_header - remote_httpserverconnection/authenticate_cn - remote_httpserverconnection/authenticate_passwd - remote_httpserverconnection/authenticate_error_wronguser - remote_httpserverconnection/authenticate_error_wrongpasswd - remote_httpserverconnection/reuse_connection - remote_httpserverconnection/wg_abort - remote_httpserverconnection/client_shutdown - remote_httpserverconnection/handler_throw_error - remote_httpserverconnection/handler_throw_streaming - remote_httpserverconnection/liveness_disconnect - remote_configpackageutility/ValidateName - remote_url/id_and_path - remote_url/parameters - remote_url/get_and_set - remote_url/format - remote_url/illegal_legal_strings +add_executable(testbase + ${base_test_SOURCES} ) - -if(BUILD_TESTING) - set_tests_properties( - base-remote_httpmessage/request_parse - base-remote_httpmessage/request_params - base-remote_httpmessage/response_clear - base-remote_httpmessage/response_flush_nothrow - base-remote_httpmessage/response_flush_throw - base-remote_httpmessage/response_write_empty - base-remote_httpmessage/response_write_fixed - base-remote_httpmessage/response_write_chunked - base-remote_httpmessage/response_sendjsonbody - base-remote_httpmessage/response_sendjsonerror - base-remote_httpmessage/response_sendfile - base-remote_httpserverconnection/expect_100_continue - base-remote_httpserverconnection/bad_request - base-remote_httpserverconnection/error_access_control - base-remote_httpserverconnection/error_accept_header - base-remote_httpserverconnection/authenticate_cn - base-remote_httpserverconnection/authenticate_passwd - base-remote_httpserverconnection/authenticate_error_wronguser - base-remote_httpserverconnection/authenticate_error_wrongpasswd - base-remote_httpserverconnection/reuse_connection - base-remote_httpserverconnection/wg_abort - base-remote_httpserverconnection/client_shutdown - base-remote_httpserverconnection/handler_throw_error - base-remote_httpserverconnection/handler_throw_streaming - base-remote_httpserverconnection/liveness_disconnect - PROPERTIES FIXTURES_REQUIRED ssl_certs) - - set_tests_properties( - base-remote_certs_fixture/prepare_directory - PROPERTIES FIXTURES_SETUP ssl_certs - ) - - set_tests_properties( - base-remote_certs_fixture/cleanup_certs - PROPERTIES FIXTURES_CLEANUP ssl_certs - ) -endif() +target_link_libraries(testbase testdeps) +target_discover_boost_tests(testbase) if(ICINGA2_WITH_LIVESTATUS) set(livestatus_test_SOURCES @@ -366,11 +156,12 @@ if(ICINGA2_WITH_LIVESTATUS) mkunity_target(livestatus test livestatus_test_SOURCES) endif() - add_boost_test(livestatus - SOURCES test-runner.cpp ${livestatus_test_SOURCES} - LIBRARIES ${base_DEPS} - TESTS livestatus/hosts livestatus/services + add_executable(testlivestatus + ${livestatus_test_SOURCES} ) + + target_link_libraries(testlivestatus testdeps) + target_discover_boost_tests(testlivestatus) endif() set(icinga_checkable_test_SOURCES @@ -388,11 +179,8 @@ if(ICINGA2_UNITY_BUILD) mkunity_target(icinga_checkable test icinga_checkable_test_SOURCES) endif() -add_boost_test(icinga_checkable - SOURCES test-runner.cpp ${icinga_checkable_test_SOURCES} - LIBRARIES ${base_DEPS} - TESTS icinga_checkable_flapping/host_not_flapping - icinga_checkable_flapping/host_flapping - icinga_checkable_flapping/host_flapping_recover - icinga_checkable_flapping/host_flapping_docs_example +add_executable(icinga_checkable + ${icinga_checkable_test_SOURCES} ) +target_link_libraries(icinga_checkable testdeps) +target_discover_boost_tests(icinga_checkable) diff --git a/test/remote-certificate-fixture.cpp b/test/remote-certificate-fixture.cpp index adb260740..0a1d48b18 100644 --- a/test/remote-certificate-fixture.cpp +++ b/test/remote-certificate-fixture.cpp @@ -1,7 +1,7 @@ /* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ #include "remote-certificate-fixture.hpp" -#include +#include "test/test-ctest.hpp" using namespace icinga; @@ -27,14 +27,14 @@ static void CleanupPersistentCertificateDir() } } -BOOST_FIXTURE_TEST_CASE(prepare_directory, ConfigurationDataDirFixture) +BOOST_FIXTURE_TEST_CASE(prepare_directory, ConfigurationDataDirFixture, *CTestProperties("FIXTURES_SETUP ssl_certs")) { // Remove any existing left-overs of the persistent certificate directory from a previous // test run. CleanupPersistentCertificateDir(); } -BOOST_FIXTURE_TEST_CASE(cleanup_certs, ConfigurationDataDirFixture) +BOOST_FIXTURE_TEST_CASE(cleanup_certs, ConfigurationDataDirFixture, *CTestProperties("FIXTURES_CLEANUP ssl_certs")) { CleanupPersistentCertificateDir(); } diff --git a/test/remote-certificate-fixture.hpp b/test/remote-certificate-fixture.hpp index 1e8ad645a..61c2f154f 100644 --- a/test/remote-certificate-fixture.hpp +++ b/test/remote-certificate-fixture.hpp @@ -2,10 +2,10 @@ #pragma once +#include #include "remote/apilistener.hpp" #include "remote/pkiutility.hpp" #include "test/base-configuration-fixture.hpp" -#include namespace icinga { diff --git a/test/remote-httpmessage.cpp b/test/remote-httpmessage.cpp index 5c79d1cc2..30adb44e3 100644 --- a/test/remote-httpmessage.cpp +++ b/test/remote-httpmessage.cpp @@ -6,6 +6,7 @@ #include "remote/httpmessage.hpp" #include "remote/httputility.hpp" #include "test/base-tlsstream-fixture.hpp" +#include "test/test-ctest.hpp" #include #include @@ -29,7 +30,11 @@ static std::future SpawnSynchronizedCoroutine(std::function #include #include @@ -93,7 +94,11 @@ private: REGISTER_URLHANDLER("/v1/test", UnitTestHandler); -BOOST_FIXTURE_TEST_SUITE(remote_httpserverconnection, HttpServerConnectionFixture) +// clang-format off +BOOST_FIXTURE_TEST_SUITE(remote_httpserverconnection, HttpServerConnectionFixture, + *CTestProperties("FIXTURES_REQUIRED ssl_certs") + *boost::unit_test::label("http")) +// clang-format on BOOST_AUTO_TEST_CASE(expect_100_continue) { diff --git a/test/test-ctest.cpp b/test/test-ctest.cpp new file mode 100644 index 000000000..43b2a14f3 --- /dev/null +++ b/test/test-ctest.cpp @@ -0,0 +1,126 @@ +#include "test-ctest.hpp" +#include +#include +#include + +using namespace icinga; + +BOOST_TEST_GLOBAL_FIXTURE(CTestFileGeneratorFixture); + +boost::unit_test::decorator::base_ptr CTestProperties::clone() const +{ + return boost::make_shared(m_Props); +} + +CTestFileGenerator::CTestFileGenerator(const boost::filesystem::path& testexe, const boost::filesystem::path& outfile) + : m_Fp(outfile.string(), std::iostream::trunc), m_WorkDir(outfile.parent_path().string()), + m_TestExe(testexe.string()) +{ + m_Fp.exceptions(std::iostream::badbit | std::iostream::failbit); +} + +void CTestFileGenerator::visit(const boost::unit_test::test_case& test) +{ + m_Fp << "add_test(" << test.full_name() << " " << m_TestExe << " --run_test=" << test.full_name() << ")\n"; + m_Fp << "set_tests_properties(" << test.full_name() << "\n PROPERTIES" + << " WORKING_DIRECTORY " << m_WorkDir << "\n"; + + auto labels = test.p_labels.get(); + if (!labels.empty()) { + m_Fp << " " << LabelsToProp(labels) << "\n"; + } + + for (auto& props : m_PropsStack) { + for (auto& prop : props) { + m_Fp << " " << prop << "\n"; + } + } + + if (!test.is_enabled()) { + m_Fp << " DISABLED\n"; + } + + auto decs = test.p_decorators.get(); + for (auto& dec : decs) { + auto ctp = boost::dynamic_pointer_cast(dec); + if (ctp) { + m_Fp << " " << ctp->m_Props << "\n"; + } + } + + m_Fp << ")\n"; + m_Fp.flush(); +} + +bool CTestFileGenerator::test_suite_start(const boost::unit_test::test_suite& suite) +{ + m_PropsStack.emplace_back(); + + auto decs = suite.p_decorators.get(); + for (auto& dec : decs) { + auto ctp = boost::dynamic_pointer_cast(dec); + if (ctp) { + m_PropsStack.back().push_back(ctp->m_Props); + } + } + + auto labels = suite.p_labels.get(); + if (!labels.empty()) { + m_PropsStack.back().push_back(LabelsToProp(labels)); + } + + return true; +} + +void CTestFileGenerator::test_suite_finish(const boost::unit_test::test_suite&) +{ + m_PropsStack.pop_back(); +} + +std::string CTestFileGenerator::LabelsToProp(const std::vector& labels) +{ + std::string labelsProp{"LABELS \""}; + for (auto it = labels.begin(); it != labels.end(); ++it) { + if (it != labels.begin()) { + labelsProp.push_back(';'); + } + + for (auto c : *it) { + if (c == ';') { + labelsProp.push_back('\\'); + } + labelsProp.push_back(c); + } + } + labelsProp.push_back('"'); + return labelsProp; +} + +CTestFileGeneratorFixture::CTestFileGeneratorFixture() +{ + auto& mts = boost::unit_test::framework::master_test_suite(); + int argc = mts.argc; + for (int i = 1; i < argc; i++) { + std::string argument(mts.argv[i]); + + if (argument == "--generate_ctest_config") { + if (argc >= i + 1) { + boost::filesystem::path testbin(mts.argv[0]); + boost::filesystem::path file(mts.argv[i + 1]); + + try { + CTestFileGenerator visitor{testbin, file}; + traverse_test_tree(mts, visitor); + } catch (const std::exception& ex) { + std::cerr << "Error: --generate_ctest_config failed with: " << ex.what() << '\n'; + std::_Exit(EXIT_FAILURE); + } + } else { + std::cerr << "Error: --generate_ctest_config specified with no argument.\n"; + std::_Exit(EXIT_FAILURE); + } + + std::_Exit(EXIT_SUCCESS); + } + } +} diff --git a/test/test-ctest.hpp b/test/test-ctest.hpp new file mode 100644 index 000000000..c4b121f1a --- /dev/null +++ b/test/test-ctest.hpp @@ -0,0 +1,57 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include +#include +#include +#include + +namespace icinga { + +/** + * A boost test decorator to set additional ctest properties for the test. + */ +class CTestProperties : public boost::unit_test::decorator::base +{ +public: + explicit CTestProperties(std::string props) : m_Props(std::move(props)) {} + + std::string m_Props; + +private: + /** + * Doesn't do anything to the case/suite it's applied to. + * + * However this gets added to the list so we can later find it by iterating the + * decorators and dynamic_casting them to this type and get the m_props value. + */ + void apply(boost::unit_test::test_unit&) override {} + [[nodiscard]] boost::unit_test::decorator::base_ptr clone() const override; +}; + +class CTestFileGenerator : public boost::unit_test::test_tree_visitor +{ +public: + CTestFileGenerator(const boost::filesystem::path& testexe, const boost::filesystem::path& outfile); + + void visit(const boost::unit_test::test_case& test) override; + + bool test_suite_start(const boost::unit_test::test_suite& suite) override; + void test_suite_finish(const boost::unit_test::test_suite& suite) override; + +private: + static std::string LabelsToProp(const std::vector& labels); + + std::ofstream m_Fp; + boost::filesystem::path m_WorkDir; + boost::filesystem::path m_TestExe; + std::vector> m_PropsStack; +}; + +struct CTestFileGeneratorFixture +{ + CTestFileGeneratorFixture(); +}; + +} // namespace icinga diff --git a/test/test-runner.cpp b/test/test-runner.cpp index fac41ea70..1b987dfd9 100644 --- a/test/test-runner.cpp +++ b/test/test-runner.cpp @@ -4,9 +4,8 @@ #define BOOST_TEST_NO_MAIN #define BOOST_TEST_ALTERNATIVE_INIT_API +#define TEST_INCLUDE_IMPLEMENTATION #include -#include -#include int BOOST_TEST_CALL_DECL main(int argc, char **argv) diff --git a/third-party/cmake/BoostTestTargets.cmake b/third-party/cmake/BoostTestTargets.cmake deleted file mode 100644 index 13d6e8088..000000000 --- a/third-party/cmake/BoostTestTargets.cmake +++ /dev/null @@ -1,262 +0,0 @@ -# - Add tests using boost::test -# -# Add this line to your test files in place of including a basic boost test header: -# #include -# -# If you cannot do that and must use the included form for a given test, -# include the line -# // OVERRIDE_BOOST_TEST_INCLUDED_WARNING -# in the same file with the boost test include. -# -# include(BoostTestTargets) -# add_boost_test( SOURCES [] -# [FAIL_REGULAR_EXPRESSION ] -# [LAUNCHER ] -# [LIBRARIES [...]] -# [RESOURCES [...]] -# [TESTS [...]] -# [DEPENDENCIES [...]]) -# -# If for some reason you need access to the executable target created, -# it can be found in ${${testdriver_name}_TARGET_NAME} as specified when -# you called add_boost_test -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Requires: -# GetForceIncludeDefinitions -# CopyResourcesToBuildTree -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2009-2010. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -if(__add_boost_test) - return() -endif() -set(__add_boost_test YES) - -set(BOOST_TEST_TARGET_PREFIX "boosttest") - -if(NOT Boost_FOUND) - find_package(Boost 1.34.0 QUIET) -endif() - -include(GetForceIncludeDefinitions) -include(CopyResourcesToBuildTree) - -if(Boost_FOUND) - set(_boosttesttargets_libs) - set(_boostConfig "BoostTestTargetsIncluded.h") - if(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY) - find_package(Boost 1.34.0 QUIET COMPONENTS unit_test_framework) - endif() - if(Boost_UNIT_TEST_FRAMEWORK_LIBRARY) - set(_boosttesttargets_libs "${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}") - if(Boost_USE_STATIC_LIBS) - set(_boostConfig "BoostTestTargetsStatic.h") - else() - set(_boostConfig "BoostTestTargetsDynamic.h") - endif() - endif() - get_filename_component(_moddir ${CMAKE_CURRENT_LIST_FILE} PATH) - configure_file("${_moddir}/${_boostConfig}" - "${CMAKE_CURRENT_BINARY_DIR}/BoostTestTargetConfig.h" - COPYONLY) - include_directories("${CMAKE_CURRENT_BINARY_DIR}") -endif() - -function(add_boost_test _name) - if(NOT BUILD_TESTING) - return() - endif() - if("${CMAKE_VERSION}" VERSION_LESS "2.8.0") - if(NOT "${_boost_test_cmakever_pestered}x" EQUAL "${CMAKE_VERSION}x") - message(STATUS - "Not adding boost::test targets - CMake 2.8.0 or newer required, using ${CMAKE_VERSION}") - set(_boost_test_cmakever_pestered - "${CMAKE_VERSION}" - CACHE - INTERNAL - "" - FORCE) - endif() - return() - endif() - - # parse arguments - set(_nowhere) - set(_curdest _nowhere) - set(_val_args - SOURCES - FAIL_REGULAR_EXPRESSION - LAUNCHER - LIBRARIES - RESOURCES - TESTS - DEPENDENCIES) - set(_bool_args - USE_COMPILED_LIBRARY) - foreach(_arg ${_val_args} ${_bool_args}) - set(${_arg}) - endforeach() - foreach(_element ${ARGN}) - list(FIND _val_args "${_element}" _val_arg_find) - list(FIND _bool_args "${_element}" _bool_arg_find) - if("${_val_arg_find}" GREATER "-1") - set(_curdest "${_element}") - elseif("${_bool_arg_find}" GREATER "-1") - set("${_element}" ON) - set(_curdest _nowhere) - else() - list(APPEND ${_curdest} "${_element}") - endif() - endforeach() - - if(_nowhere) - message(FATAL_ERROR "Syntax error in use of add_boost_test!") - endif() - - if(NOT SOURCES) - message(FATAL_ERROR - "Syntax error in use of add_boost_test: at least one source file required!") - endif() - - if(Boost_FOUND) - - include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) - - set(includeType) - foreach(src ${SOURCES}) - file(READ ${src} thefile) - if("${thefile}" MATCHES ".*BoostTestTargetConfig.h.*") - set(includeType CONFIGURED) - set(includeFileLoc ${src}) - break() - elseif("${thefile}" MATCHES ".*boost/test/included/unit_test.hpp.*") - set(includeType INCLUDED) - set(includeFileLoc ${src}) - set(_boosttesttargets_libs) # clear this out - linking would be a bad idea - if(NOT - "${thefile}" - MATCHES - ".*OVERRIDE_BOOST_TEST_INCLUDED_WARNING.*") - message("Please replace the include line in ${src} with this alternate include line instead:") - message(" \#include ") - message("Once you've saved your changes, re-run CMake. (See BoostTestTargets.cmake for more info)") - endif() - break() - endif() - endforeach() - - if(NOT _boostTestTargetsNagged${_name} STREQUAL "${includeType}") - if("${includeType}" STREQUAL "CONFIGURED") - message(STATUS - "Test '${_name}' uses the CMake-configurable form of the boost test framework - congrats! (Including File: ${includeFileLoc})") - elseif("${includeType}" STREQUAL "INCLUDED") - message("In test '${_name}': ${includeFileLoc} uses the 'included' form of the boost unit test framework.") - else() - message("In test '${_name}': Didn't detect the CMake-configurable boost test include.") - message("Please replace your existing boost test include in that test with the following:") - message(" \#include ") - message("Once you've saved your changes, re-run CMake. (See BoostTestTargets.cmake for more info)") - endif() - endif() - set(_boostTestTargetsNagged${_name} - "${includeType}" - CACHE - INTERNAL - "" - FORCE) - - - if(RESOURCES) - list(APPEND SOURCES ${RESOURCES}) - endif() - - # Generate a unique target name, using the relative binary dir - # and provided name. (transform all / into _ and remove all other - # non-alphabet characters) - file(RELATIVE_PATH - targetpath - "${CMAKE_BINARY_DIR}" - "${CMAKE_CURRENT_BINARY_DIR}") - string(REGEX REPLACE "[^A-Za-z/_]" "" targetpath "${targetpath}") - string(REPLACE "/" "_" targetpath "${targetpath}") - - set(_target_name ${BOOST_TEST_TARGET_PREFIX}-${targetpath}-${_name}) - set(${_name}_TARGET_NAME "${_target_name}" PARENT_SCOPE) - - # Build the test. - add_executable(${_target_name} ${SOURCES}) - - list(APPEND LIBRARIES ${_boosttesttargets_libs}) - - if(LIBRARIES) - target_link_libraries(${_target_name} ${LIBRARIES}) - endif() - - if(RESOURCES) - set_property(TARGET ${_target_name} PROPERTY RESOURCE ${RESOURCES}) - copy_resources_to_build_tree(${_target_name}) - endif() - - if(NOT Boost_TEST_FLAGS) -# set(Boost_TEST_FLAGS --catch_system_error=yes --output_format=XML) - set(Boost_TEST_FLAGS --catch_system_error=yes) - endif() - - # TODO: Figure out why only recent boost handles individual test running properly - - if(LAUNCHER) - set(_test_command ${LAUNCHER} "\$") - else() - set(_test_command ${_target_name}) - endif() - - if(TESTS) - foreach(_test ${TESTS}) - add_test(NAME - ${_name}-${_test} - COMMAND - ${_test_command} - --run_test=${_test} - ${Boost_TEST_FLAGS}) - if(FAIL_REGULAR_EXPRESSION) - set_tests_properties(${_name}-${_test} - PROPERTIES - FAIL_REGULAR_EXPRESSION - "${FAIL_REGULAR_EXPRESSION}") - endif() - endforeach() - else() - add_test(NAME - ${_name}-boost_test - COMMAND - ${_test_command} - ${Boost_TEST_FLAGS}) - if(FAIL_REGULAR_EXPRESSION) - set_tests_properties(${_name}-${_test} - PROPERTIES - FAIL_REGULAR_EXPRESSION - "${FAIL_REGULAR_EXPRESSION}") - endif() - endif() - - if (DEPENDENCIES) - add_dependencies(${_target_name} ${DEPENDENCIES}) - endif() - - # CppCheck the test if we can. - if(COMMAND add_cppcheck) - add_cppcheck(${_target_name} STYLE UNUSED_FUNCTIONS) - endif() - - endif() -endfunction() diff --git a/third-party/cmake/BoostTestTargetsDynamic.h b/third-party/cmake/BoostTestTargetsDynamic.h deleted file mode 100644 index ae1f38e74..000000000 --- a/third-party/cmake/BoostTestTargetsDynamic.h +++ /dev/null @@ -1,9 +0,0 @@ -// Small header computed by CMake to set up boost test. -// include AFTER #define BOOST_TEST_MODULE whatever -// but before any other boost test includes. - -// Using the Boost UTF dynamic library - -#define BOOST_TEST_DYN_LINK -#include - diff --git a/third-party/cmake/BoostTestTargetsIncluded.h b/third-party/cmake/BoostTestTargetsIncluded.h deleted file mode 100644 index 253133ce2..000000000 --- a/third-party/cmake/BoostTestTargetsIncluded.h +++ /dev/null @@ -1,7 +0,0 @@ -// Small header computed by CMake to set up boost test. -// include AFTER #define BOOST_TEST_MODULE whatever -// but before any other boost test includes. - -// Using the Boost UTF included framework - -#include diff --git a/third-party/cmake/BoostTestTargetsStatic.h b/third-party/cmake/BoostTestTargetsStatic.h deleted file mode 100644 index dd3cddae1..000000000 --- a/third-party/cmake/BoostTestTargetsStatic.h +++ /dev/null @@ -1,7 +0,0 @@ -// Small header computed by CMake to set up boost test. -// include AFTER #define BOOST_TEST_MODULE whatever -// but before any other boost test includes. - -// Using the Boost UTF static library - -#include diff --git a/third-party/cmake/CopyResourcesToBuildTree.cmake b/third-party/cmake/CopyResourcesToBuildTree.cmake deleted file mode 100644 index 3512cc484..000000000 --- a/third-party/cmake/CopyResourcesToBuildTree.cmake +++ /dev/null @@ -1,83 +0,0 @@ -# - Copy the resources your app needs to the build tree. -# -# copy_resources_to_build_tree() -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2009-2010. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -if(__copy_resources_to_build_tree) - return() -endif() -set(__copy_resources_to_build_tree YES) - -function(copy_resources_to_build_tree _target) - get_target_property(_resources ${_target} RESOURCE) - if(NOT _resources) - # Bail if no resources - message(STATUS - "Told to copy resources for target ${_target}, but " - "no resources are set!") - return() - endif() - - get_target_property(_path ${_target} LOCATION) - get_filename_component(_path "${_path}" PATH) - - if(NOT MSVC AND NOT "${CMAKE_GENERATOR}" MATCHES "Makefiles") - foreach(_config ${CMAKE_CONFIGURATION_TYPES}) - get_target_property(_path${_config} ${_target} LOCATION_${_config}) - get_filename_component(_path${_config} "${_path${_config}}" PATH) - add_custom_command(TARGET ${_target} - POST_BUILD - COMMAND - ${CMAKE_COMMAND} - ARGS -E make_directory "${_path${_config}}/" - COMMENT "Creating directory ${_path${_config}}/") - endforeach() - endif() - - foreach(_res ${_resources}) - if(NOT IS_ABSOLUTE "${_res}") - get_filename_component(_res "${_res}" ABSOLUTE) - endif() - get_filename_component(_name "${_res}" NAME) - - if(MSVC) - # Working dir is solution file dir, not exe file dir. - add_custom_command(TARGET ${_target} - POST_BUILD - COMMAND - ${CMAKE_COMMAND} - ARGS -E copy "${_res}" "${CMAKE_BINARY_DIR}/" - COMMENT "Copying ${_name} to ${CMAKE_BINARY_DIR}/ for MSVC") - else() - if("${CMAKE_GENERATOR}" MATCHES "Makefiles") - add_custom_command(TARGET ${_target} - POST_BUILD - COMMAND - ${CMAKE_COMMAND} - ARGS -E copy "${_res}" "${_path}/" - COMMENT "Copying ${_name} to ${_path}/") - else() - foreach(_config ${CMAKE_CONFIGURATION_TYPES}) - add_custom_command(TARGET ${_target} - POST_BUILD - COMMAND - ${CMAKE_COMMAND} - ARGS -E copy "${_res}" "${_path${_config}}" - COMMENT "Copying ${_name} to ${_path${_config}}") - endforeach() - - endif() - endif() - endforeach() -endfunction() diff --git a/third-party/cmake/GetForceIncludeDefinitions.cmake b/third-party/cmake/GetForceIncludeDefinitions.cmake deleted file mode 100644 index efcca047b..000000000 --- a/third-party/cmake/GetForceIncludeDefinitions.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# - Get the platform-appropriate flags to add to force inclusion of a file -# -# The most common use of this is to use a generated config.h-type file -# placed out of the source tree in all files. -# -# get_force_include_definitions(var forcedincludefiles...) - -# where var is the name of your desired output variable, and everything -# else is a source file to forcibly include. -# a list item to be filtered. -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2009-2010. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -if(__get_force_include_definitions) - return() -endif() -set(__get_force_include_definitions YES) - -function(get_force_include_definitions var) - set(_flagprefix) - if(CMAKE_COMPILER_IS_GNUCXX) - set(_flag "-include") - elseif(MSVC) - set(_flag "/FI") - else() - message(SEND_ERROR "You don't seem to be using MSVC or GCC, but") - message(SEND_ERROR "the project called get_force_include_definitions.") - message(SEND_ERROR "Contact this project with the name of your") - message(FATAL_ERROR "compiler and preferably the flag to force includes") - endif() - - set(_out) - foreach(_item ${ARGN}) - list(APPEND _out "${_flag} \"${_item}\"") - endforeach() - set(${var} "${_out}" PARENT_SCOPE) -endfunction()