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 <n.marques21@hotmail.com>
This commit is contained in:
Nuno Marques
2026-05-07 18:05:36 -07:00
parent ceb095351f
commit 0dafafbc90
4 changed files with 97 additions and 11 deletions
+81 -9
View File
@@ -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()
#=============================================================================
+8
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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()