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.
This commit is contained in:
Johannes Schmidt 2025-07-30 16:20:49 +02:00
parent 38ea1bb39c
commit b4681b10ec
18 changed files with 297 additions and 688 deletions

View File

@ -1,7 +1,7 @@
# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ # Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+
# CMake 3.8 is required, CMake policy compatibility was verified up to 3.17. # CMake 3.17 is required, CMake policy compatibility was verified up to 3.17.
cmake_minimum_required(VERSION 3.8...3.17) cmake_minimum_required(VERSION 3.17...3.17)
set(BOOST_MIN_VERSION "1.66.0") set(BOOST_MIN_VERSION "1.66.0")
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)

View File

@ -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 ";" "$<SEMICOLON>" test "${args}")
add_custom_command(TARGET "${target}" POST_BUILD
COMMAND ${CMAKE_COMMAND} -DCMD=$<TARGET_FILE:${target}> -DARGS="${test}" -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExecuteCommandQuietly.cmake"
)
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${testfile}"
)
endfunction()

View File

@ -0,0 +1,3 @@
# Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+
execute_process(COMMAND "${CMD}" ${ARGS} OUTPUT_QUIET)

17
test/BoostTestConfig.h.in Normal file
View File

@ -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 <boost/test/included/unit_test.hpp>
#else
//If a dynamic library was found, this will be defined before including the header.
#cmakedefine BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#endif

View File

@ -1,8 +1,43 @@
# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ # 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 icingaapplication-fixture.cpp
base-type.cpp base-type.cpp
${base_OBJS} ${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 # 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 # 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. # separate executable to not crash the base test cases.
add_boost_test(types add_executable(testtypes
SOURCES test-runner.cpp ${types_test_SOURCES} ${types_test_SOURCES}
LIBRARIES ${base_DEPS}
TESTS
types/gettype
types/assign
types/byname
types/instantiate
types/sort_by_load_after
) )
target_link_libraries(testtypes testdeps)
target_discover_boost_tests(testtypes)
set(base_test_SOURCES set(base_test_SOURCES
icingaapplication-fixture.cpp icingaapplication-fixture.cpp
@ -103,251 +133,11 @@ if(ICINGA2_UNITY_BUILD)
mkunity_target(base test base_test_SOURCES) mkunity_target(base test base_test_SOURCES)
endif() endif()
add_boost_test(base add_executable(testbase
SOURCES test-runner.cpp ${base_test_SOURCES} ${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
) )
target_link_libraries(testbase testdeps)
if(BUILD_TESTING) target_discover_boost_tests(testbase)
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()
if(ICINGA2_WITH_LIVESTATUS) if(ICINGA2_WITH_LIVESTATUS)
set(livestatus_test_SOURCES set(livestatus_test_SOURCES
@ -366,11 +156,12 @@ if(ICINGA2_WITH_LIVESTATUS)
mkunity_target(livestatus test livestatus_test_SOURCES) mkunity_target(livestatus test livestatus_test_SOURCES)
endif() endif()
add_boost_test(livestatus add_executable(testlivestatus
SOURCES test-runner.cpp ${livestatus_test_SOURCES} ${livestatus_test_SOURCES}
LIBRARIES ${base_DEPS}
TESTS livestatus/hosts livestatus/services
) )
target_link_libraries(testlivestatus testdeps)
target_discover_boost_tests(testlivestatus)
endif() endif()
set(icinga_checkable_test_SOURCES set(icinga_checkable_test_SOURCES
@ -388,11 +179,8 @@ if(ICINGA2_UNITY_BUILD)
mkunity_target(icinga_checkable test icinga_checkable_test_SOURCES) mkunity_target(icinga_checkable test icinga_checkable_test_SOURCES)
endif() endif()
add_boost_test(icinga_checkable add_executable(icinga_checkable
SOURCES test-runner.cpp ${icinga_checkable_test_SOURCES} ${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
) )
target_link_libraries(icinga_checkable testdeps)
target_discover_boost_tests(icinga_checkable)

View File

@ -1,7 +1,7 @@
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ /* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
#include "remote-certificate-fixture.hpp" #include "remote-certificate-fixture.hpp"
#include <BoostTestTargetConfig.h> #include "test/test-ctest.hpp"
using namespace icinga; 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 // Remove any existing left-overs of the persistent certificate directory from a previous
// test run. // test run.
CleanupPersistentCertificateDir(); CleanupPersistentCertificateDir();
} }
BOOST_FIXTURE_TEST_CASE(cleanup_certs, ConfigurationDataDirFixture) BOOST_FIXTURE_TEST_CASE(cleanup_certs, ConfigurationDataDirFixture, *CTestProperties("FIXTURES_CLEANUP ssl_certs"))
{ {
CleanupPersistentCertificateDir(); CleanupPersistentCertificateDir();
} }

View File

@ -2,10 +2,10 @@
#pragma once #pragma once
#include <BoostTestTargetConfig.h>
#include "remote/apilistener.hpp" #include "remote/apilistener.hpp"
#include "remote/pkiutility.hpp" #include "remote/pkiutility.hpp"
#include "test/base-configuration-fixture.hpp" #include "test/base-configuration-fixture.hpp"
#include <BoostTestTargetConfig.h>
namespace icinga { namespace icinga {

View File

@ -6,6 +6,7 @@
#include "remote/httpmessage.hpp" #include "remote/httpmessage.hpp"
#include "remote/httputility.hpp" #include "remote/httputility.hpp"
#include "test/base-tlsstream-fixture.hpp" #include "test/base-tlsstream-fixture.hpp"
#include "test/test-ctest.hpp"
#include <fstream> #include <fstream>
#include <utility> #include <utility>
@ -29,7 +30,11 @@ static std::future<void> SpawnSynchronizedCoroutine(std::function<void(boost::as
return future; return future;
} }
BOOST_FIXTURE_TEST_SUITE(remote_httpmessage, TlsStreamFixture) // clang-format off
BOOST_FIXTURE_TEST_SUITE(remote_httpmessage, TlsStreamFixture,
*CTestProperties("FIXTURES_REQUIRED ssl_certs")
*boost::unit_test::label("http"))
// clang-format on
BOOST_AUTO_TEST_CASE(request_parse) BOOST_AUTO_TEST_CASE(request_parse)
{ {

View File

@ -6,6 +6,7 @@
#include "remote/httphandler.hpp" #include "remote/httphandler.hpp"
#include "test/base-testloggerfixture.hpp" #include "test/base-testloggerfixture.hpp"
#include "test/base-tlsstream-fixture.hpp" #include "test/base-tlsstream-fixture.hpp"
#include "test/test-ctest.hpp"
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/beast/http.hpp> #include <boost/beast/http.hpp>
#include <utility> #include <utility>
@ -93,7 +94,11 @@ private:
REGISTER_URLHANDLER("/v1/test", UnitTestHandler); 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) BOOST_AUTO_TEST_CASE(expect_100_continue)
{ {

126
test/test-ctest.cpp Normal file
View File

@ -0,0 +1,126 @@
#include "test-ctest.hpp"
#include <boost/regex.hpp>
#include <boost/make_shared.hpp>
#include <iostream>
using namespace icinga;
BOOST_TEST_GLOBAL_FIXTURE(CTestFileGeneratorFixture);
boost::unit_test::decorator::base_ptr CTestProperties::clone() const
{
return boost::make_shared<CTestProperties>(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<CTestProperties>(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<CTestProperties>(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<std::string>& 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);
}
}
}

57
test/test-ctest.hpp Normal file
View File

@ -0,0 +1,57 @@
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
#pragma once
#include <BoostTestTargetConfig.h>
#include <boost/filesystem/path.hpp>
#include <boost/test/tree/traverse.hpp>
#include <fstream>
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<std::string>& labels);
std::ofstream m_Fp;
boost::filesystem::path m_WorkDir;
boost::filesystem::path m_TestExe;
std::vector<std::vector<std::string>> m_PropsStack;
};
struct CTestFileGeneratorFixture
{
CTestFileGeneratorFixture();
};
} // namespace icinga

View File

@ -4,9 +4,8 @@
#define BOOST_TEST_NO_MAIN #define BOOST_TEST_NO_MAIN
#define BOOST_TEST_ALTERNATIVE_INIT_API #define BOOST_TEST_ALTERNATIVE_INIT_API
#define TEST_INCLUDE_IMPLEMENTATION
#include <BoostTestTargetConfig.h> #include <BoostTestTargetConfig.h>
#include <boost/test/unit_test.hpp>
#include <cstdlib>
int BOOST_TEST_CALL_DECL int BOOST_TEST_CALL_DECL
main(int argc, char **argv) main(int argc, char **argv)

View File

@ -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 <BoostTestTargetConfig.h>
#
# 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(<testdriver_name> SOURCES <source1> [<more sources...>]
# [FAIL_REGULAR_EXPRESSION <additional fail regex>]
# [LAUNCHER <generic launcher script>]
# [LIBRARIES <library> [<library>...]]
# [RESOURCES <resource> [<resource>...]]
# [TESTS <testcasename> [<testcasename>...]]
# [DEPENDENCIES <dependency> [<dependency>...]])
#
# 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 <rpavlik@iastate.edu> <abiryan@ryand.net>
# 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 <BoostTestTargetConfig.h>")
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 <BoostTestTargetConfig.h>")
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} "\$<TARGET_FILE:${_target_name}>")
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()

View File

@ -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 <boost/test/unit_test.hpp>

View File

@ -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 <boost/test/included/unit_test.hpp>

View File

@ -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 <boost/test/unit_test.hpp>

View File

@ -1,83 +0,0 @@
# - Copy the resources your app needs to the build tree.
#
# copy_resources_to_build_tree(<target_name>)
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# 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()

View File

@ -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 <rpavlik@iastate.edu> <abiryan@ryand.net>
# 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()