From 0dafafbc9087ec15d85b89f7f7af026f962e7902 Mon Sep 17 00:00:00 2001 From: Nuno Marques Date: Thu, 7 May 2026 18:05:36 -0700 Subject: [PATCH] build(testing): skip fuzztest on MSVC-driver toolchains fuzztest's CMakeLists hard-fails with a FATAL_ERROR for any compiler that is not GCC, AppleClang, or Clang (test/fuzztest/CMakeLists.txt:30). Native MSVC (cl.exe, CMAKE_CXX_COMPILER_ID="MSVC") trips that branch directly. clang-cl (CMAKE_CXX_COMPILER_ID="Clang", CMAKE_CXX_SIMULATE_ID="MSVC") makes it past that gate, but then abseil/re2/fuzztest end up emitting unresolved external references to `_mm_loadu_si128`, `_mm_set1_epi8`, and other SSE intrinsics that the LLVM driver does not inline through the MSVC link.exe pipeline. Treat both MSVC-driver toolchains the same way: introduce a PX4_HAS_FUZZTEST flag that turns OFF whenever CMAKE_CXX_COMPILER_ID == MSVC OR CMAKE_CXX_SIMULATE_ID == MSVC (WIN32 only). When fuzztest is off: - skip add_subdirectory(test) and FetchContent a standalone googletest 1.12.1 instead so px4_add_unit_gtest() targets still have gtest/gtest_main to link against; - stub link_fuzztest()/link_fuzztest_core() so existing call sites compile cleanly and route to gtest_main; - emit a minimal test_results target so `ctest` still runs; - gate platforms/posix/CMakeLists.txt and platforms/posix/src/px4/common/CMakeLists.txt's references to the fuzztest-only mavlink_fuzz_tests / gtest_runner subdirectory on PX4_HAS_FUZZTEST so configure does not reference targets that were never built; - gate px4_add_functional_gtest() on PX4_HAS_FUZZTEST so functional tests are skipped (they pull fuzztest::fuzztest directly). Suppress the `-Wcharacter-conversion` error that googletest 1.12.1's gtest-printers.h triggers under clang-cl (the implicit char8_t -> char32_t conversion is fixed in newer gtest releases). The flag is applied only when CMAKE_CXX_COMPILER_ID == "Clang", so cl.exe never sees the unknown switch. Signed-off-by: Nuno Marques --- CMakeLists.txt | 90 +++++++++++++++++-- cmake/px4_add_gtest.cmake | 8 ++ platforms/posix/CMakeLists.txt | 2 +- platforms/posix/src/px4/common/CMakeLists.txt | 8 +- 4 files changed, 97 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 467623a309..1ee0e76ccd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -422,16 +422,88 @@ set(TESTFILTER "" CACHE STRING "Filter string for ctest to selectively only run include(px4_add_gtest) if(BUILD_TESTING) - # Setting FUZZTEST_FUZZING_MODE=on enables ASAN, and is only supported with Clang. - # clang-cl on Windows uses the MSVC driver: ASAN there is incompatible with the - # debug C runtime (/MDd) emitted by CMake's Debug config, so keep fuzzing mode - # off on that combination and just compile fuzztest as a plain dependency. - if ((("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang")) - AND NOT (WIN32 AND "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC")) - set(FUZZTEST_FUZZING_MODE ON) + # fuzztest's CMakeLists hard-fails with FATAL_ERROR on any compiler that + # is not GCC, AppleClang, or Clang (test/fuzztest/CMakeLists.txt:30). + # Native MSVC (cl.exe) trips that branch directly, and clang-cl (which + # fuzztest does configure for) pulls in abseil/re2/fuzztest with + # inline SSE intrinsics that the LLVM driver leaves as unresolved + # externals against the MSVC link.exe. Skip the fuzztest subdirectory + # entirely on either MSVC-driver toolchain and fetch a standalone + # googletest so the unit-test targets still get gtest/gtest_main. + if(WIN32 AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" + OR "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC")) + set(PX4_HAS_FUZZTEST OFF) + else() + set(PX4_HAS_FUZZTEST ON) + endif() + + if(PX4_HAS_FUZZTEST) + # Setting FUZZTEST_FUZZING_MODE=on enables ASAN, and is only supported with Clang. + # clang-cl on Windows uses the MSVC driver: ASAN there is incompatible with the + # debug C runtime (/MDd) emitted by CMake's Debug config, so keep fuzzing mode + # off on that combination and just compile fuzztest as a plain dependency. + if ((("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang")) + AND NOT (WIN32 AND "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC")) + set(FUZZTEST_FUZZING_MODE ON) + endif() + add_subdirectory(test) + fuzztest_setup_fuzzing_flags() + else() + # MSVC fallback: pull in googletest directly so px4_add_unit_gtest() + # targets can still link gtest/gtest_main. Provide no-op stubs for + # the fuzztest helpers so existing call sites compile cleanly. + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 + ) + # Match the runtime that CMake selects for our targets so gtest can + # be linked into the test executables without C-runtime mismatch. + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + + # googletest 1.12.1 contains an implicit char8_t -> char32_t + # conversion in gtest-printers.h that clang-cl flags as + # -Wcharacter-conversion (and the Clang frontend defaults + # -Werror for it). Newer googletest releases fix this; suppress + # it on clang-cl only so we don't have to bump the pinned + # version just for the unit-test fallback path. Real cl.exe + # does not understand the flag, so guard it. + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(TARGET gtest) + target_compile_options(gtest PRIVATE -Wno-character-conversion) + endif() + if(TARGET gtest_main) + target_compile_options(gtest_main PRIVATE -Wno-character-conversion) + endif() + endif() + + function(link_fuzztest name) + # fuzztest is unavailable on this toolchain; tests still need a + # main(), so wire them to the stock gtest entry point. + target_link_libraries(${name} PRIVATE gtest_main) + endfunction() + function(link_fuzztest_core name) + target_link_libraries(${name} PRIVATE gtest_main) + endfunction() + macro(fuzztest_setup_fuzzing_flags) + endmacro() + + # Mirror the test_results plumbing that test/CMakeLists.txt would + # otherwise add, minus the fuzztest dependency that we cannot build. + if(TESTFILTER) + set(TESTFILTERARG "-R") + else() + set(TESTFILTERARG "") + endif() + add_custom_target(test_results + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -T Test ${TESTFILTERARG} ${TESTFILTER} + USES_TERMINAL + COMMENT "Running tests" + WORKING_DIRECTORY ${PX4_BINARY_DIR}) + set_target_properties(test_results PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() - add_subdirectory(test) - fuzztest_setup_fuzzing_flags() endif() #============================================================================= diff --git a/cmake/px4_add_gtest.cmake b/cmake/px4_add_gtest.cmake index 2cb605e061..a3120a1dc0 100644 --- a/cmake/px4_add_gtest.cmake +++ b/cmake/px4_add_gtest.cmake @@ -88,6 +88,14 @@ function(px4_add_unit_gtest) endfunction() function(px4_add_functional_gtest) + # Functional tests link fuzztest::fuzztest directly (without + # link_fuzztest() so they pull only the runtime, not the gtest main). + # When fuzztest is unavailable for the active toolchain (e.g. native + # MSVC) we skip these definitions entirely so configure does not + # reference targets that were never built. + if(NOT PX4_HAS_FUZZTEST) + return() + endif() # skip if unit testing is not configured if(BUILD_TESTING) # parse source file and library dependencies from arguments diff --git a/platforms/posix/CMakeLists.txt b/platforms/posix/CMakeLists.txt index b70002f82f..28fe07268b 100644 --- a/platforms/posix/CMakeLists.txt +++ b/platforms/posix/CMakeLists.txt @@ -56,7 +56,7 @@ if(WIN32) px4_posix_windows_configure_target(px4) endif() -if (BUILD_TESTING) +if (BUILD_TESTING AND PX4_HAS_FUZZTEST) # Build mavlink fuzz tests. These run other modules and thus cannot be a functional/unit test add_executable(mavlink_fuzz_tests EXCLUDE_FROM_ALL src/px4/common/mavlink_fuzz_tests.cpp diff --git a/platforms/posix/src/px4/common/CMakeLists.txt b/platforms/posix/src/px4/common/CMakeLists.txt index d8e8638bcb..915d404f21 100644 --- a/platforms/posix/src/px4/common/CMakeLists.txt +++ b/platforms/posix/src/px4/common/CMakeLists.txt @@ -69,5 +69,11 @@ endif() if(BUILD_TESTING) add_subdirectory(test_stubs) - add_subdirectory(gtest_runner) + # gtest_runner provides gtest_functional_main which links + # fuzztest::init_fuzztest. On toolchains without fuzztest (e.g. + # native MSVC) we skip it because px4_add_functional_gtest() is + # also gated off there, so nothing references the target. + if(PX4_HAS_FUZZTEST) + add_subdirectory(gtest_runner) + endif() endif()