From 202aac2eb3d80217a3a7160bf31a4a3571014d6b Mon Sep 17 00:00:00 2001 From: mcmarius <23401453+mcmarius@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:06:19 +0200 Subject: [PATCH 1/2] patch(ext/ut): Keep the command line, report unknown options Needed by ctest registering one test per case: - parse_arg_with_fallback cleared the command line captured by the cmd_line_args constructor (GCC/Clang) and by __argv (MSVC), so an executable that runs the suites from a destructor, the default here, had no option working: --help, --list-test-names-only, selecting a case by name - find_arg matched option names as substrings, so an unknown option matched nothing, became a test name pattern and the run reported success: a typo in the listing arguments of tests/CMakeLists.txt registered nothing and passed --- ext/include/boost/ut.hpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ext/include/boost/ut.hpp b/ext/include/boost/ut.hpp index 8869d9fb..1b6200da 100644 --- a/ext/include/boost/ut.hpp +++ b/ext/include/boost/ut.hpp @@ -742,10 +742,16 @@ struct cfg { // clang-format on }; + // LOCAL PATCH: options were matched as substrings, so an unknown option + // matched nothing, became a test name pattern and reported success static std::optional find_arg(std::string_view arg) { for (const auto& option : cfg::options) { - if (std::get<0>(option).find(arg) != std::string::npos) { - return option; + for (const auto name : utility::split(std::get<0>(option), ' ')) { + const auto token = + name.ends_with(',') ? name.substr(0, name.size() - 1) : name; + if (token == arg) { + return option; + } } } return std::nullopt; @@ -780,11 +786,8 @@ struct cfg { cfg::largc = argc; cfg::largv = argv; } - else - { - cfg::largc = 0; - cfg::largv = nullptr; - } + // LOCAL PATCH: clearing them here discarded the command line captured by + // the cmd_line_args constructor (GCC/Clang) and by __argv (MSVC) parse(cfg::largc, cfg::largv); } @@ -794,25 +797,25 @@ struct cfg { executable_name = argv[0]; } query_pattern = ""; - bool found_first_option = false; for (auto i = 1U; i < n_args && argv != nullptr; i++) { std::string cmd(argv[i]); auto cmd_option = find_arg(cmd); if (!cmd_option.has_value()) { - if (found_first_option) { + // LOCAL PATCH: '-' starts an option wherever it appears, the usage + // line promises "[ ... ] options" + if (not cmd.empty() and cmd.front() == '-') { std::cerr << "unknown option: '" << cmd << "' run:" << std::endl; std::cerr << "'" << executable_name << " --help'" << std::endl; std::cerr << "for additional help" << std::endl; std::exit(-1); } else { - if (i > 1U) { + if (not query_pattern.empty()) { query_pattern.append(" "); } query_pattern.append(cmd); } continue; } - found_first_option = true; auto var = std::get(*cmd_option); const bool has_option_arg = !std::get<1>(*cmd_option).empty(); if (!has_option_arg && From 6a945552bf454fb1320de6ab91e8626c303a5518 Mon Sep 17 00:00:00 2001 From: mcmarius <23401453+mcmarius@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:04:48 +0200 Subject: [PATCH 2/2] One CTest test per test case, discovered by ctest Ported from fix-gtest, with the googletest specific parts turned into arguments, so these files are identical for any test framework: * cmake/RegisterTests.cmake / cmake/RegisterTestsAddTests.cmake: how to list the test cases and how to select one are arguments of register_tests(); without LIST_ARGS an executable is registered as a single test, which is all a plain assert() based test needs * discovery runs inside ctest, never as part of the build; working directory, per test timeout and refusing to report success for zero tests as in fix-gtest * tests/CMakeLists.txt: test target out of the root CMakeLists, one switch for tests, BUILD_TESTING * scripts/run_tests.sh, scripts/cmake.sh: copied from fix-gtest unchanged * tests/main.cpp: ut runs the suites from a destructor, where it does not see argv, so every option, the case listing and the case selection included, was ignored; it now runs them from main * test case names must be unique across suites, and a name holding a wildcard character is refused: it selects another case, or none, and reports success Rename lib, static linking Disable asan on tests Fix static linking Backport from main Trim prose --- CMakeLists.txt | 55 +++--- cmake/RegisterTests.cmake | 122 ++++++++++++ cmake/RegisterTestsAddTests.cmake | 312 ++++++++++++++++++++++++++++++ scripts/cmake.sh | 32 ++- scripts/run_tests.sh | 47 +++++ tests/CMakeLists.txt | 30 +++ tests/README_Tests.md | 17 +- tests/main.cpp | 14 +- 8 files changed, 582 insertions(+), 47 deletions(-) create mode 100644 cmake/RegisterTests.cmake create mode 100644 cmake/RegisterTestsAddTests.cmake create mode 100755 scripts/run_tests.sh create mode 100644 tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index e724b4dd..d82124a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ -cmake_minimum_required(VERSION 3.26) +cmake_minimum_required(VERSION 3.28) -# NOTE: update executable name in .github/workflows/cmake.yml:25 when changing executable name in this file +# NOTE: update executable name in .github/workflows/cmake.yml when changing executable name in this file # for now, the project name is used as the executable name set(MAIN_PROJECT_NAME "oop") set(MAIN_EXECUTABLE_NAME "${MAIN_PROJECT_NAME}") -set(MAIN_PROJECT_LIB_NAME "${MAIN_PROJECT_NAME}_lib") -set(MAIN_PROJECT_NO_SAN_LIB_NAME "${MAIN_PROJECT_NAME}_nslib") +set(MAIN_LIBRARY_NAME "${MAIN_PROJECT_NAME}_lib") +set(MAIN_LIBRARY_NO_SAN_NAME "${MAIN_PROJECT_NAME}_nslib") set(TESTS_PROJECT_NAME "${MAIN_PROJECT_NAME}_test") set(TESTS_EXECUTABLE_NAME "${TESTS_PROJECT_NAME}") @@ -22,9 +22,10 @@ set(CMAKE_CXX_EXTENSIONS OFF) include(cmake/Options.cmake) include(cmake/CompilerFlags.cmake) include(cmake/CopyHelper.cmake) +include(cmake/RegisterTests.cmake) +# include(CTest) declares BUILD_TESTING (default ON) include(CTest) -enable_testing() ############################################################################### # external dependencies with FetchContent @@ -55,15 +56,15 @@ enable_testing() ############################################################################### -# NOTE: update executable name in .github/workflows/cmake.yml:25 when changing name here -add_library(${MAIN_PROJECT_LIB_NAME}) -target_sources(${MAIN_PROJECT_LIB_NAME} +# NOTE: update executable name in .github/workflows/cmake.yml when changing name here +add_library(${MAIN_LIBRARY_NAME} STATIC) +target_sources(${MAIN_LIBRARY_NAME} PRIVATE src/exemplu_test_oop/BankAccount.cpp ) -add_library(${MAIN_PROJECT_NO_SAN_LIB_NAME}) -target_sources(${MAIN_PROJECT_NO_SAN_LIB_NAME} +add_library(${MAIN_LIBRARY_NO_SAN_NAME} STATIC) +target_sources(${MAIN_LIBRARY_NO_SAN_NAME} PRIVATE src/exemplu_test_oop/BankAccount.cpp ) @@ -80,34 +81,34 @@ target_sources(${MAIN_EXECUTABLE_NAME} # NOTE: Add all defined targets (e.g. executables, libraries, etc. ) # NOTE: RUN_SANITIZERS is optional, if it's not present it will default to true -set_compiler_flags(RUN_SANITIZERS ${USE_ASAN} TARGET_NAMES ${MAIN_EXECUTABLE_NAME} ${MAIN_PROJECT_LIB_NAME}) -set_compiler_flags(RUN_SANITIZERS FALSE TARGET_NAMES ${MAIN_PROJECT_NO_SAN_LIB_NAME}) +set_compiler_flags(RUN_SANITIZERS ${USE_ASAN} TARGET_NAMES ${MAIN_EXECUTABLE_NAME} ${MAIN_LIBRARY_NAME}) +set_compiler_flags(RUN_SANITIZERS FALSE TARGET_NAMES ${MAIN_LIBRARY_NO_SAN_NAME}) # set_compiler_flags(TARGET_NAMES ${MAIN_EXECUTABLE_NAME} ${FOO} ${BAR}) # where ${FOO} and ${BAR} represent additional executables or libraries # you want to compile with the set compiler flags ############################################################################### -target_include_directories(${MAIN_PROJECT_LIB_NAME} PUBLIC +target_include_directories(${MAIN_LIBRARY_NAME} PUBLIC $ ) -target_include_directories(${MAIN_PROJECT_NO_SAN_LIB_NAME} PUBLIC +target_include_directories(${MAIN_LIBRARY_NO_SAN_NAME} PUBLIC $ ) target_include_directories(${MAIN_EXECUTABLE_NAME} PUBLIC $ ) -target_include_directories(${MAIN_PROJECT_LIB_NAME} SYSTEM PRIVATE +target_include_directories(${MAIN_LIBRARY_NAME} SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/ext ) -target_include_directories(${MAIN_PROJECT_NO_SAN_LIB_NAME} SYSTEM PRIVATE +target_include_directories(${MAIN_LIBRARY_NO_SAN_NAME} SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/ext ) target_include_directories(${MAIN_EXECUTABLE_NAME} SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/ext ) -target_link_libraries(${MAIN_EXECUTABLE_NAME} PRIVATE ${MAIN_PROJECT_LIB_NAME}) +target_link_libraries(${MAIN_EXECUTABLE_NAME} PRIVATE ${MAIN_LIBRARY_NAME}) ############################################################################### @@ -119,26 +120,14 @@ target_link_libraries(${MAIN_EXECUTABLE_NAME} PRIVATE ${MAIN_PROJECT_LIB_NAME}) ############################################################################### -set(TEST_SOURCES_DIR ${CMAKE_SOURCE_DIR}/tests) -set(BOOST_UT_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/ext/include/boost") - -add_executable(${TESTS_EXECUTABLE_NAME} - ${TEST_SOURCES_DIR}/main.cpp - ${TEST_SOURCES_DIR}/test_oop.cpp - ${TEST_SOURCES_DIR}/test_simplu.cpp -) - -target_link_libraries(${TESTS_EXECUTABLE_NAME} PRIVATE ${MAIN_PROJECT_NO_SAN_LIB_NAME}) -target_include_directories(${TESTS_EXECUTABLE_NAME} SYSTEM PRIVATE ${BOOST_UT_INCLUDE_DIR}) - -set_compiler_flags(RUN_SANITIZERS FALSE TARGET_NAMES ${TESTS_EXECUTABLE_NAME}) - -add_test(NAME ${TESTS_EXECUTABLE_NAME} COMMAND ${TESTS_PROJECT_NAME}) +if(BUILD_TESTING) + add_subdirectory(tests) +endif () ############################################################################### # copy binaries to "bin" folder; these are uploaded as artifacts on each release -# DESTINATION_DIR is set as "bin" in cmake/Options.cmake:6 +# DESTINATION_DIR is set as "bin" in cmake/Options.cmake install(TARGETS ${MAIN_EXECUTABLE_NAME} DESTINATION ${DESTINATION_DIR}) if(APPLE) install(FILES launcher.command DESTINATION ${DESTINATION_DIR}) diff --git a/cmake/RegisterTests.cmake b/cmake/RegisterTests.cmake new file mode 100644 index 00000000..d5439904 --- /dev/null +++ b/cmake/RegisterTests.cmake @@ -0,0 +1,122 @@ +############################################################################### +# Framework agnostic registration of test executables with CTest. +# No test framework code here, keep identical on every branch. +# +# register_tests() one CTest test for the executable +# register_tests( LIST_ARGS ...) one CTest test per test case +# +# Options (all optional): +# LIST_ARGS argument that prints the test cases, one per line +# LIST_NOISE removed from every line of the listing, a line left +# empty is not a test case +# SELECT_ARG