From ba5371b0f73dada548e9ddb1d478b410627e7004 Mon Sep 17 00:00:00 2001 From: Steve Downey Date: Mon, 19 Oct 2026 12:57:17 -0400 Subject: [PATCH] Test the reflection path of type_name on gcc 16, with coherent flags testing/type_name.hpp has two implementations of the same contract, and picks the P2996 one only when the compiler and the library both offer reflection. Only cmake/gcc-16-toolchain.cmake adds -freflection, and nothing in CI or the presets uses it, so the reflection path was never tested there. -freflection changes the language, so it is set for the whole build, the library and the fetched Catch2 included, never per target or per directory: a header that branches on reflection must see the same answer in every translation unit, or the program is an ODR violation with no diagnostic required. - CI: a gcc 16 / c++26 Debug job passes -DCMAKE_CXX_FLAGS=-freflection. The reusable workflow's toolchain sets only the per-configuration flags, so this reaches every compile without displacing anything. - Presets: gcc-16-debug and gcc-16-release configure through cmake/gcc-16-toolchain.cmake, which pins g++-16 and C++26 and already adds the flag. gcc-16-debug is plain Debug: that toolchain does not implement the infra sanitizer selection. type_name.test.cpp declares its one check only when the compiler has reflection (`__cpp_impl_reflection`), and then requires that the reflection implementation was selected. Falling back is otherwise silent -- every other test still passes -- which is how a reflection build could go green while testing the fallback, for instance if the library macro went unseen. A build that forces an implementation is left alone. Every other configuration keeps covering the fallback. --- .github/workflows/ci_tests.yml | 3 +- CMakePresets.json | 80 +++++++++++++++++++++++++ tests/beman/expected/CMakeLists.txt | 1 + tests/beman/expected/type_name.test.cpp | 26 ++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 tests/beman/expected/type_name.test.cpp diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 4fb866f5..0b33ef0a 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -46,7 +46,8 @@ jobs: "tests": [ "Debug.Default", "Release.Default", "Release.TSan", "Release.MaxSan", "Debug.Werror", - "Debug.Coverage", "Debug.-DBEMAN_EXPECTED_USE_MODULES=On" + "Debug.Coverage", "Debug.-DBEMAN_EXPECTED_USE_MODULES=On", + "Debug.-DCMAKE_CXX_FLAGS=-freflection" ] } ] diff --git a/CMakePresets.json b/CMakePresets.json index 09df0e5c..08a4cb40 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -51,6 +51,28 @@ "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/infra/cmake/gnu-toolchain.cmake" } }, + { + "name": "gcc-16-debug", + "displayName": "GCC 16 Debug Build (C++26, reflection)", + "inherits": [ + "_root-config" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/gcc-16-toolchain.cmake" + } + }, + { + "name": "gcc-16-release", + "displayName": "GCC 16 Release Build (C++26, reflection)", + "inherits": [ + "_root-config", + "_release-base" + ], + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/gcc-16-toolchain.cmake" + } + }, { "name": "llvm-debug", "displayName": "Clang Debug Build", @@ -172,6 +194,20 @@ "_root-build" ] }, + { + "name": "gcc-16-debug", + "configurePreset": "gcc-16-debug", + "inherits": [ + "_root-build" + ] + }, + { + "name": "gcc-16-release", + "configurePreset": "gcc-16-release", + "inherits": [ + "_root-build" + ] + }, { "name": "llvm-debug", "configurePreset": "llvm-debug", @@ -237,6 +273,16 @@ "inherits": "_test_base", "configurePreset": "gcc-release" }, + { + "name": "gcc-16-debug", + "inherits": "_test_base", + "configurePreset": "gcc-16-debug" + }, + { + "name": "gcc-16-release", + "inherits": "_test_base", + "configurePreset": "gcc-16-release" + }, { "name": "llvm-debug", "inherits": "_test_base", @@ -303,6 +349,40 @@ } ] }, + { + "name": "gcc-16-debug", + "steps": [ + { + "type": "configure", + "name": "gcc-16-debug" + }, + { + "type": "build", + "name": "gcc-16-debug" + }, + { + "type": "test", + "name": "gcc-16-debug" + } + ] + }, + { + "name": "gcc-16-release", + "steps": [ + { + "type": "configure", + "name": "gcc-16-release" + }, + { + "type": "build", + "name": "gcc-16-release" + }, + { + "type": "test", + "name": "gcc-16-release" + } + ] + }, { "name": "llvm-debug", "steps": [ diff --git a/tests/beman/expected/CMakeLists.txt b/tests/beman/expected/CMakeLists.txt index 134c2bce..21250ec8 100644 --- a/tests/beman/expected/CMakeLists.txt +++ b/tests/beman/expected/CMakeLists.txt @@ -41,6 +41,7 @@ set(beman_expected_beman_only_tests expected_review_corrections.test.cpp expected_smf_regressions.test.cpp header_idempotence.test.cpp + type_name.test.cpp ) include(Catch) diff --git a/tests/beman/expected/type_name.test.cpp b/tests/beman/expected/type_name.test.cpp new file mode 100644 index 00000000..c88d8b0c --- /dev/null +++ b/tests/beman/expected/type_name.test.cpp @@ -0,0 +1,26 @@ +// tests/beman/expected/type_name.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Beman-only: a compiler with reflection gets the reflection implementation of +// testing/type_name.hpp. +// +// The selection needs both the compiler's __cpp_impl_reflection and the +// library's __cpp_lib_reflection, and falling back is silent: every other test +// still passes using the fallback. So whenever the compiler has reflection, +// check that it was used. A build that forces an implementation with +// BEMAN_EXPECTED_TESTING_TYPE_NAME_USE_REFLECTION has made its own choice, and +// is not second-guessed. + +#if defined(BEMAN_EXPECTED_TESTING_TYPE_NAME_USE_REFLECTION) + #define BEMAN_EXPECTED_TESTING_TYPE_NAME_FORCED +#endif + +#include + +#include + +#if defined(__cpp_impl_reflection) && !defined(BEMAN_EXPECTED_TESTING_TYPE_NAME_FORCED) +TEST_CASE("type_name: a compiler with reflection gets the reflection implementation", "[type_name]") { + CHECK(BEMAN_EXPECTED_TESTING_TYPE_NAME_USE_REFLECTION == 1); +} +#endif