[runtimes][GTest] LLVM-independent unittests (#164794)

The LLVM-customized GTest has a dependency on LLVM to support
`llvm::raw_ostream` and hence has to link to LLVMSupport. The runtimes
use the LLVMSupport from the bootstrapping LLVM build. The problem is
that the boostrapping compiler and the runtimes target can diverge in
their ABI, even in the runtimes default build. For instance, Clang is
built using gcc which uses libstdc++, but the runtimes is built by Clang
which can be configured to use libcxx by default. Altough it does not
use gcc, this issue has caused
[flang-aarch64-libcxx](https://lab.llvm.org/buildbot/#/builders/89)) to
break, and is still (again?) broken.

This patch makes the runtimes' GTest independent from LLVMSupport so we
do not link any runtimes component with LLVM components.

Runtime projects that use GTest unittests:
 * flang-rt
 * libc
* compiler-rt: Adds `gtest-all.cpp` with
[GTEST_NO_LLVM_SUPPORT=1](f801b6f67e/compiler-rt/CMakeLists.txt (L723))
to each unittest without using `llvm_gtest`. Not touched by this PR.
* openmp: Handled by #159416. Not touched for now by this PR to avoid
conflict.

The current state of this PR tries to reuse
https://github.com/llvm/llvm-project/blob/main/third-party/unittest/CMakeLists.txt
as much as possible, altough personally I would prefer to make it use
"modern CMake" style. third-party/unittest/CMakeLists.txt will detect
whether it is used in runtimes build and adjaust accordingly. It creates
a different target for LLVM (`llvm_gtest`, NFCI) and another one for the
runtimes (`runtimes_gtest`). It is not possible to reuse `llvm_gtest`
for both since `llvm_gtest` is imported using `find_package(LLVM)` if
configured using LLVM_INSTALL_GTEST. An alias `default_gtest` is used to
select between the two. `default_gtest` could also be used for openmp
which also supports standalone and
[LLVM_ENABLE_PROJECTS](https://github.com/llvm/llvm-project/pull/152189)
build mode.
This commit is contained in:
Michael Kruse 2025-11-12 11:50:33 +01:00 committed by GitHub
parent a276624b2e
commit 0957656a40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 109 additions and 84 deletions

View File

@ -22,12 +22,8 @@ if (CMAKE_CROSSCOMPILING)
return ()
endif ()
if (NOT TARGET llvm_gtest)
message(WARNING "Flang-RT unittests disabled due to GTest being unavailable; "
"Try LLVM_INSTALL_GTEST=ON for the LLVM build")
return ()
endif ()
# Make the targets default_gtest and default_gtest_main available.
build_gtest()
add_dependencies(flang-rt-test-depends
FlangRTUnitTests

View File

@ -9,7 +9,6 @@
#include "flang-rt/runtime/descriptor.h"
#include "flang/Common/ISO_Fortran_binding_wrapper.h"
#include "flang/Testing/testing.h"
#include "llvm/Support/raw_ostream.h"
#include <type_traits>
using namespace Fortran::runtime;
@ -73,26 +72,9 @@ static void AddNoiseToCdesc(CFI_cdesc_t *dv, CFI_rank_t rank) {
}
}
#ifdef VERBOSE
static void DumpTestWorld(const void *bAddr, CFI_attribute_t attr,
CFI_type_t ty, std::size_t eLen, CFI_rank_t rank,
const CFI_index_t *eAddr) {
llvm::outs() << " base_addr: ";
llvm::outs().write_hex(reinterpret_cast<std::intptr_t>(bAddr))
<< " attribute: " << static_cast<int>(attr)
<< " type: " << static_cast<int>(ty) << " elem_len: " << eLen
<< " rank: " << static_cast<int>(rank) << " extent: ";
llvm::outs().write_hex(reinterpret_cast<std::intptr_t>(eAddr)) << '\n';
llvm::outs().flush();
}
#endif
static void check_CFI_establish(CFI_cdesc_t *dv, void *base_addr,
CFI_attribute_t attribute, CFI_type_t type, std::size_t elem_len,
CFI_rank_t rank, const CFI_index_t extents[]) {
#ifdef VERBOSE
DumpTestWorld(base_addr, attribute, type, elem_len, rank, extent);
#endif
// CFI_establish reqs from F2018 section 18.5.5
int retCode{
CFI_establish(dv, base_addr, attribute, type, elem_len, rank, extents)};
@ -305,9 +287,6 @@ static void check_CFI_allocate(CFI_cdesc_t *dv,
const CFI_type_t type{dv->type};
const void *base_addr{dv->base_addr};
const int version{dv->version};
#ifdef VERBOSE
DumpTestWorld(base_addr, attribute, type, elem_len, rank, nullptr);
#endif
int retCode{CFI_allocate(dv, lower_bounds, upper_bounds, elem_len)};
Descriptor *desc = reinterpret_cast<Descriptor *>(dv);
if (retCode == CFI_SUCCESS) {

View File

@ -12,8 +12,8 @@
#include "CrashHandlerFixture.h"
#include "gtest/gtest.h"
#include "flang/Runtime/extensions.h"
#include "llvm/ADT/Twine.h"
#include <cstring>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
@ -82,8 +82,9 @@ static const char *temp_directory_path() {
static std::string createTemporaryFile(
const char *name, const AccessType &accessType) {
std::string path =
(llvm::Twine{temp_directory_path()} + "/" + addPIDSuffix(name)).str();
std::ostringstream pathS;
pathS << temp_directory_path() << "/" << addPIDSuffix(name);
std::string path = pathS.str();
// O_CREAT | O_EXCL enforces that this file is newly created by this call.
// This feels risky. If we don't have permission to create files in the

View File

@ -17,12 +17,11 @@
char buffer[1000];
std::vsnprintf(buffer, sizeof buffer, message, ap);
va_end(ap);
llvm::errs()
<< "Test "
<< ::testing::UnitTest::GetInstance()->current_test_info()->name()
<< " crashed in file "
<< (sourceFile ? sourceFile : "unknown source file") << '(' << sourceLine
<< "): " << buffer << '\n';
std::cerr << "Test "
<< ::testing::UnitTest::GetInstance()->current_test_info()->name()
<< " crashed in file "
<< (sourceFile ? sourceFile : "unknown source file") << '('
<< sourceLine << "): " << buffer << '\n';
std::exit(EXIT_FAILURE);
}

View File

@ -32,8 +32,8 @@ TEST(Descriptor, FixedStride) {
extent[0] = 8;
descriptor.Establish(integer, four, data, 1, extent);
ASSERT_EQ(descriptor.rank(), 1);
ASSERT_EQ(descriptor.Elements(), 8);
ASSERT_EQ(descriptor.ElementBytes(), four);
ASSERT_EQ(descriptor.Elements(), 8u);
ASSERT_EQ(descriptor.ElementBytes(), static_cast<unsigned>(four));
ASSERT_EQ(descriptor.GetDimension(0).LowerBound(), 0);
ASSERT_EQ(descriptor.GetDimension(0).ByteStride(), four);
ASSERT_EQ(descriptor.GetDimension(0).Extent(), 8);

View File

@ -16,7 +16,6 @@
#include "flang/Runtime/io-api.h"
#include "flang/Runtime/main.h"
#include "flang/Runtime/stop.h"
#include "llvm/Support/raw_ostream.h"
#include <cstring>
#include <string_view>

View File

@ -19,6 +19,8 @@ set(LLVM_LINK_COMPONENTS
# Add Unit Testing Support
#==============================================================================
make_gtest_target()
function(add_libc_benchmark_unittest target_name)
if(NOT LLVM_INCLUDE_TESTS)
return()
@ -38,8 +40,8 @@ function(add_libc_benchmark_unittest target_name)
)
target_link_libraries(${target_name}
PRIVATE
llvm_gtest_main
llvm_gtest
default_gtest_main
default_gtest
${LIBC_BENCHMARKS_UNITTEST_DEPENDS}
)
llvm_update_compile_flags(${target_name})

View File

@ -1340,9 +1340,7 @@ if( LLVM_INCLUDE_UTILS )
add_subdirectory(utils/mlgo-utils)
add_subdirectory(utils/llvm-test-mustache-spec)
if( LLVM_INCLUDE_TESTS )
set(LLVM_SUBPROJECT_TITLE "Third-Party/Google Test")
add_subdirectory(${LLVM_THIRD_PARTY_DIR}/unittest ${CMAKE_CURRENT_BINARY_DIR}/third-party/unittest)
set(LLVM_SUBPROJECT_TITLE)
endif()
else()
if ( LLVM_INCLUDE_TESTS )

View File

@ -1802,7 +1802,13 @@ function(add_unittest test_suite test_name)
# libpthreads overrides some standard library symbols, so main
# executable must be linked with it in order to provide consistent
# API for all shared libaries loaded by this executable.
target_link_libraries(${test_name} PRIVATE llvm_gtest_main llvm_gtest ${LLVM_PTHREAD_LIB})
# default_gtest should be an alias to either llvm_gtest or runtimes_gtest.
# If it is not defined, fall back to llvm_gtest.
if(TARGET default_gtest)
target_link_libraries(${test_name} PRIVATE default_gtest_main default_gtest ${LLVM_PTHREAD_LIB})
else ()
target_link_libraries(${test_name} PRIVATE llvm_gtest_main llvm_gtest ${LLVM_PTHREAD_LIB})
endif ()
add_dependencies(${test_suite} ${test_name})
endfunction()

View File

@ -256,6 +256,20 @@ endif()
# This can be used to detect whether we're in the runtimes build.
set(LLVM_RUNTIMES_BUILD ON)
# Make GTest available to all runtimes
# The runtime must call make_gtest_target() for it to become available. This is
# to avoid build failures when gtest is not actually needed. In particular,
# mingw-incomplete-sysroot is missing C++ header files that GTest needs to
# compile.
function(build_gtest)
if(TARGET default_gtest)
# Already available
return()
endif()
add_subdirectory("${LLVM_THIRD_PARTY_DIR}/unittest" "${CMAKE_BINARY_DIR}/third-party/runtimes_gtest")
endfunction()
foreach(entry ${runtimes})
get_filename_component(projName ${entry} NAME)

View File

@ -11,6 +11,34 @@
#
# Project-wide settings
set(LLVM_SUBPROJECT_TITLE "Third-Party/Google Test")
if(LLVM_RUNTIMES_BUILD)
# This instance of GTest is use for unittests for the runtime libraries. It
# must not link to LLVMSupport (used for llvm::raw_ostream and llvm::cl
# support) of the host build: It may be a different architecture and/or
# using a different C++ ABI such as libcxx/libstdc++.
set(GTEST_LLVM_COMPONENTS "")
# We cannot use llvm_gtest for the target; it would clash with
# find_package(LLVM) with LLVM_EXPORT_GTEST=ON. Instead, we define an alias
# default_gtest that points to llvm_gtest in the LLVM build and
# runtimes_gtest in an runtimes build.
set(gtest_name "runtimes_gtest")
# Override locally; never install the runtimes-GTest.
set(LLVM_INSTALL_GTEST OFF)
# Build the library containing main() so unittests need less boilerplate.
# UnitTestMain/TestMain.cpp always needs LLVMSupport, use GTest's original
# main when unavailable.
set(gtest_main_src googletest/src/gtest_main.cc)
else()
set(GTEST_LLVM_COMPONENTS "Support")
set(gtest_name "llvm_gtest")
set(gtest_main_src UnitTestMain/TestMain.cpp)
endif()
if(WIN32)
add_definitions(-DGTEST_OS_WINDOWS=1)
endif()
@ -38,17 +66,13 @@ if (HAVE_LIBPTHREAD)
list(APPEND LIBS pthread)
endif()
# Make available for runtimes using the LLVM buildtree
# (required for unittests in bootstrapping builds)
set(EXCLUDE_FROM_ALL OFF)
# Install GTest only if requested.
set(BUILDTREE_ONLY BUILDTREE_ONLY)
if (LLVM_INSTALL_GTEST)
set(BUILDTREE_ONLY "")
endif ()
add_llvm_library(llvm_gtest
add_llvm_library("${gtest_name}"
googletest/src/gtest-all.cc
googlemock/src/gmock-all.cc
@ -56,7 +80,7 @@ add_llvm_library(llvm_gtest
${LIBS}
LINK_COMPONENTS
Support # Depends on llvm::raw_ostream
${GTEST_LLVM_COMPONENTS}
# This is a library meant only for the build tree.
${BUILDTREE_ONLY}
@ -67,15 +91,14 @@ add_llvm_library(llvm_gtest
# that warning here for any targets that link to gtest.
if(CXX_SUPPORTS_SUGGEST_OVERRIDE_FLAG)
add_definitions("-Wno-suggest-override")
set_target_properties(llvm_gtest PROPERTIES INTERFACE_COMPILE_OPTIONS "-Wno-suggest-override")
set_target_properties("${gtest_name}" PROPERTIES INTERFACE_COMPILE_OPTIONS "-Wno-suggest-override")
endif()
if (NOT LLVM_ENABLE_THREADS)
target_compile_definitions(llvm_gtest PUBLIC GTEST_HAS_PTHREAD=0)
target_compile_definitions("${gtest_name}" PUBLIC GTEST_HAS_PTHREAD=0)
endif ()
# Top-level include directory required for "llvm/Support/raw_os_ostream.h"
target_include_directories(llvm_gtest
target_include_directories("${gtest_name}"
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/googletest/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/googlemock/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
@ -84,21 +107,36 @@ target_include_directories(llvm_gtest
PRIVATE googletest googlemock
)
# When used from the buildtree, also force use of buildtree LLVM headers,
# (instead locally installed version)
# FIXME: Shouldn't this be done for all LLVM libraries? Currently, LLVM uses a
# big giant `include_directories( ${LLVM_INCLUDE_DIR} ${LLVM_MAIN_INCLUDE_DIR})`
# which CMake does not add to the import library.
target_include_directories(llvm_gtest BEFORE
PUBLIC $<BUILD_INTERFACE:${LLVM_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${LLVM_BINARY_DIR}/include>
)
if(LLVM_RUNTIMES_BUILD)
target_compile_definitions("${gtest_name}" PUBLIC GTEST_NO_LLVM_SUPPORT=1)
else()
# When used from the buildtree, also force use of buildtree LLVM headers,
# (instead locally installed version)
# FIXME: Shouldn't this be done for all LLVM libraries? Currently, LLVM uses a
# big giant `include_directories( ${LLVM_INCLUDE_DIR} ${LLVM_MAIN_INCLUDE_DIR})`
# which CMake does not add to the import library.
target_include_directories("${gtest_name}" BEFORE
PUBLIC $<BUILD_INTERFACE:${LLVM_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${LLVM_BINARY_DIR}/include>
)
endif()
add_subdirectory(UnitTestMain)
add_llvm_library("${gtest_name}_main"
${gtest_main_src}
LINK_LIBS
"${gtest_name}"
LINK_COMPONENTS
${GTEST_LLVM_COMPONENTS}
${BUILDTREE_ONLY}
)
if (LLVM_INSTALL_GTEST)
install(DIRECTORY googletest/include/gtest/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/llvm-gtest/gtest/" COMPONENT llvm_gtest)
install(DIRECTORY googlemock/include/gmock/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/llvm-gmock/gmock/" COMPONENT llvm_gtest)
install(DIRECTORY googletest/include/gtest/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/llvm-gtest/gtest/" COMPONENT "${gtest_name}")
install(DIRECTORY googlemock/include/gmock/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/llvm-gmock/gmock/" COMPONENT "${gtest_name}")
endif()
# When LLVM_LINK_LLVM_DYLIB is enabled, libLLVM.so is added to the interface
@ -118,5 +156,14 @@ function (gtest_remove_dylib_from_link_interface target)
endif()
endfunction()
gtest_remove_dylib_from_link_interface(llvm_gtest)
gtest_remove_dylib_from_link_interface(llvm_gtest_main)
if (NOT LLVM_RUNTIMES_BUILD)
gtest_remove_dylib_from_link_interface("${gtest_name}")
gtest_remove_dylib_from_link_interface("${gtest_name}_main")
endif ()
# The build processing this file always uses this GTest for unittests.
# Projects that do not use the LLVM_ENABLE_PROJECTS mechanism, but are
# standalone-builds using find_package(LLVM) can define these aliases
# explicitly.
add_library(default_gtest ALIAS "${gtest_name}")
add_library(default_gtest_main ALIAS "${gtest_name}_main")

View File

@ -1,16 +0,0 @@
set(BUILDTREE_ONLY BUILDTREE_ONLY)
if (LLVM_INSTALL_GTEST)
set(BUILDTREE_ONLY "")
endif ()
add_llvm_library(llvm_gtest_main
TestMain.cpp
LINK_LIBS
llvm_gtest
LINK_COMPONENTS
Support # Depends on llvm::cl
${BUILDTREE_ONLY}
)