[libc-commits] [libc] [libc][cmake] Unify unit and hermetic test rules into add_libc_test (PR #233512)

via libc-commits libc-commits at lists.llvm.org
Tue Oct 20 21:13:39 PDT 2026


https://github.com/lntue updated https://github.com/llvm/llvm-project/pull/233512

>From 77f94fe1daba183a06c92ab64bf66c5e1ec9a6f0 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Tue, 15 Sep 2026 17:40:13 +0000
Subject: [PATCH 1/3] [libc][cmake] Unify unit and hermetic test rules into
 add_libc_test

- Consolidate _add_libc_unittest and add_libc_hermetic into mode-aware
  add_libc_test and create_libc_test rules using llvm_libc_parse_arguments
  and forward_arguments (#232089), keeping add_libc_unittest,
  add_libc_hermetic, and add_fp_unittest as thin forwarding wrappers.
- Replace separate libc-unit-tests and libc-hermetic-tests with unified
  libc-tests and libc-tests-build targets.
- Introduce add_libc_testsuite to create both <suite> and <suite>-build
  targets wired into libc-tests and libc-tests-build.
- Move add_fp_unittest into LLVMLibCTestRules.cmake via the FP_TEST option
  and rename MPFR/MPC helper libraries to follow standard libc target
  naming (libc.utils.MPCommon, libc.utils.MPFRWrapper, libc.utils.MPCWrapper).
- Streamline _add_libc_test_runner: replace $<TARGET_FILE:...> with
  configure-time binary paths, fast-path .params generation, and define
  test runner targets directly via add_custom_target(... COMMAND ... JOB_POOL ...)
  instead of intermediate .cmd stamp rules.
- Update docs and GPU benchmarks to use the unified test targets and rules.
---
 libc/benchmarks/gpu/CMakeLists.txt         |    9 +-
 libc/cmake/modules/LLVMLibCTestRules.cmake | 1080 ++++++++++----------
 libc/docs/dev/code_coverage.md             |   12 +-
 libc/docs/full_cross_build.md              |    6 +-
 libc/docs/gpu/testing.rst                  |    4 +-
 libc/test/CMakeLists.txt                   |   10 +-
 libc/test/include/CMakeLists.txt           |    3 +-
 libc/test/shared/CMakeLists.txt            |    2 +-
 libc/test/src/CMakeLists.txt               |   77 --
 libc/test/src/__support/CMakeLists.txt     |    3 +-
 libc/test/src/math/CMakeLists.txt          |    5 +-
 libc/test/src/math/smoke/CMakeLists.txt    |    3 +-
 libc/utils/MPCWrapper/CMakeLists.txt       |   20 +-
 libc/utils/MPFRWrapper/CMakeLists.txt      |   33 +-
 14 files changed, 600 insertions(+), 667 deletions(-)

diff --git a/libc/benchmarks/gpu/CMakeLists.txt b/libc/benchmarks/gpu/CMakeLists.txt
index e7ad4f59bf7180..c7befd869fe27e 100644
--- a/libc/benchmarks/gpu/CMakeLists.txt
+++ b/libc/benchmarks/gpu/CMakeLists.txt
@@ -14,9 +14,10 @@ function(add_benchmark benchmark_name)
   if(NOT libc.src.time.clock IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
     message(FATAL_ERROR "target does not support clock")
   endif()
-  add_libc_hermetic(
+  add_libc_test(
     ${benchmark_name}
     IS_GPU_BENCHMARK
+    FULL_BUILD_ONLY
     LINK_LIBRARIES
       LibcGpuBenchmark
       ${BENCHMARK_LINK_LIBRARIES}
@@ -28,9 +29,9 @@ function(add_benchmark benchmark_name)
       -flto
   )
   get_fq_target_name(${benchmark_name} fq_target_name)
-  set(fq_build_target_name ${fq_target_name}.__build__)
-
-  add_dependencies(gpu-benchmark ${fq_target_name})
+  if(TARGET ${fq_target_name})
+    add_dependencies(gpu-benchmark ${fq_target_name})
+  endif()
 endfunction()
 
 function(add_benchmark_framework_library name)
diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 56a51e161fcdd2..e6736153256fb2 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -286,66 +286,453 @@ function(get_link_options link_options)
   set(${link_options} "${link_opts}" PARENT_SCOPE)
 endfunction()
 
-# Rule to add a libc unittest.
-# Usage
-#    create_libc_unittest(
-#      <target name>
-#      SUITE <name of the suite this test belongs to>
-#      SRCS  <list of .cpp files for the test>
-#      HDRS  <list of .h files for the test>
-#      DEPENDS <list of dependencies>
-#      ENV <list of environment variables to set before running the test>
-#      COMPILE_OPTIONS <list of special compile options for this target>
-#      LINK_LIBRARIES <list of linking libraries for this target>
-#    )
-function(create_libc_unittest fq_target_name)
-  if(NOT LLVM_INCLUDE_TESTS)
-    return()
+# Get libgcc_s to be used in hermetic and integration tests.
+if(NOT MSVC AND NOT LIBC_CC_SUPPORTS_NOSTDLIBPP)
+  execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=libgcc_s.so.1
+    OUTPUT_VARIABLE LIBGCC_S_LOCATION)
+  string(STRIP ${LIBGCC_S_LOCATION} LIBGCC_S_LOCATION)
+endif()
+
+# Hermetic and integration tests link with -nodefaultlibs, so the compiler
+# runtime builtins have to be linked explicitly. -print-libgcc-file-name
+# reports whichever library the driver would use (libgcc.a or
+# libclang_rt.builtins.a). The query uses the same flags as the test link so
+# that the driver selects the same multilib. GPU and UEFI targets do not link a
+# compiler runtime.
+if(LLVM_INCLUDE_TESTS AND LLVM_LIBC_FULL_BUILD AND NOT LIBC_TARGET_OS_IS_GPU AND
+  NOT LIBC_TARGET_OS_IS_UEFI AND
+  NOT LIBC_TEST_BUILTINS_LIBRARY AND NOT LIBC_TEST_BUILTINS_TARGET)
+  separate_arguments(libc_cxx_flags NATIVE_COMMAND "${CMAKE_CXX_FLAGS}")
+  execute_process(
+    COMMAND ${CMAKE_CXX_COMPILER} ${libc_cxx_flags}
+      ${LIBC_COMPILE_OPTIONS_DEFAULT} ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
+      -print-libgcc-file-name
+    OUTPUT_VARIABLE LIBC_TEST_BUILTINS_LIBRARY
+    OUTPUT_STRIP_TRAILING_WHITESPACE)
+  if(NOT EXISTS "${LIBC_TEST_BUILTINS_LIBRARY}")
+    message(FATAL_ERROR "Compiler runtime builtins library "
+      "'${LIBC_TEST_BUILTINS_LIBRARY}' not found. Set "
+      "LIBC_TEST_BUILTINS_LIBRARY or LIBC_TEST_BUILTINS_TARGET.")
   endif()
+endif()
 
-  cmake_parse_arguments(
-    "LIBC_UNITTEST"
-    "NO_RUN_POSTBUILD;C_TEST" # Optional arguments
-    "SUITE;CXX_STANDARD" # Single value arguments
-    "SRCS;HDRS;DEPENDS;ENV;COMPILE_OPTIONS;LINK_LIBRARIES;FLAGS" # Multi-value arguments
-    ${ARGN}
-  )
-  if(NOT LIBC_UNITTEST_SRCS)
-    message(FATAL_ERROR "'create_libc_unittest' target requires a SRCS list of .cpp "
-      "files.")
+# Rule to add a test suite in the hierarchy.
+# Automatically creates <suite_name> (run target) and <suite_name>-build (build target).
+# Connects them to libc-tests and libc-tests-build.
+function(add_libc_testsuite suite_name)
+  if(NOT TARGET ${suite_name})
+    add_custom_target(${suite_name})
   endif()
-  if(NOT LIBC_UNITTEST_DEPENDS)
-    message(FATAL_ERROR "'create_libc_unittest' target requires a DEPENDS list of "
-      "'add_entrypoint_object' targets.")
+
+  set(created_build_target FALSE)
+  if(NOT TARGET ${suite_name}-build)
+    add_custom_target(${suite_name}-build)
+    set(created_build_target TRUE)
   endif()
 
-  get_fq_deps_list(fq_deps_list ${LIBC_UNITTEST_DEPENDS})
-  list(APPEND fq_deps_list libc.test.UnitTest.LibcTest)
-  if(LIBC_UNITTEST_C_TEST)
+  if(created_build_target)
+    if(TARGET libc-tests)
+      add_dependencies(libc-tests ${suite_name})
+    endif()
+    if(TARGET libc-tests-build)
+      add_dependencies(libc-tests-build ${suite_name}-build)
+    endif()
+  endif()
+endfunction()
+
+function(add_libc_exhaustive_testsuite suite_name)
+  add_libc_testsuite(${suite_name})
+  add_dependencies(exhaustive-check-libc ${suite_name})
+endfunction()
+
+function(add_libc_long_running_testsuite suite_name)
+  add_libc_testsuite(${suite_name})
+  add_dependencies(libc-long-running-tests ${suite_name})
+endfunction()
+
+# Helper function for create_libc_test: returns the list of libc entrypoints
+# needed to support code coverage profiling in hermetic/full-build mode.
+function(_get_libc_coverage_dependencies output_var)
+  set(${output_var}
+    libc.src.stdio.fclose
+    libc.src.stdio.fdopen
+    libc.src.stdio.feof
+    libc.src.stdio.fflush
+    libc.src.stdio.fileno
+    libc.src.stdio.fopen
+    libc.src.stdio.fprintf
+    libc.src.stdio.fread
+    libc.src.stdio.fseek
+    libc.src.stdio.ftell
+    libc.src.stdio.fwrite
+    libc.src.stdio.snprintf
+    libc.src.stdio.stderr
+    libc.src.stdio.vfprintf
+    libc.src.stdio.vsnprintf
+    libc.src.fcntl.fcntl
+    libc.src.fcntl.open
+    libc.src.stdlib.getenv
+    libc.src.stdlib.setenv
+    libc.src.stdlib.strtol
+    libc.src.string.strchr
+    libc.src.string.strcmp
+    libc.src.string.strdup
+    libc.src.string.strerror
+    libc.src.string.strlen
+    libc.src.string.strncpy
+    libc.src.string.strrchr
+    libc.src.sys.mman.madvise
+    libc.src.sys.mman.mmap
+    libc.src.sys.mman.munmap
+    libc.src.sys.prctl.prctl
+    libc.src.sys.stat.mkdir
+    libc.src.sys.utsname.uname
+    libc.src.unistd.fork
+    libc.src.unistd.ftruncate
+    libc.src.unistd.getpagesize
+    libc.src.unistd.getpid
+    PARENT_SCOPE
+  )
+endfunction()
+
+# Helper function for create_libc_test: collects fully qualified dependencies
+# for a test target, including base test framework libraries, hermetic runtime
+# entrypoints, subprocess support, and coverage dependencies.
+function(_get_libc_test_dependencies output_var user_depends startup_target_dep is_c_test)
+  get_fq_deps_list(fq_deps_list ${user_depends})
+  list(APPEND fq_deps_list
+    libc.src.__support.StringUtil.error_to_string
+    libc.test.UnitTest.ErrnoSetterMatcher
+    libc.test.UnitTest.LibcTest
+  )
+  if(is_c_test)
     list(APPEND fq_deps_list libc.test.UnitTest.LibcCTest)
   else()
+    list(APPEND fq_deps_list libc.test.UnitTest.LibcDeathTestExecutors)
+  endif()
+
+  if(LLVM_LIBC_FULL_BUILD)
     list(APPEND fq_deps_list
-      libc.src.__support.StringUtil.error_to_string
-      libc.test.UnitTest.ErrnoSetterMatcher
-      libc.test.UnitTest.LibcDeathTestExecutors
+      ${startup_target_dep}
+      libc.src.string.memcmp
+      libc.src.string.memcpy
+      libc.src.string.memmove
+      libc.src.string.memset
+      libc.src.strings.bcmp
+      libc.src.strings.bzero
+      libc.test.UnitTest.HermeticTestUtils
     )
+    if(LIBC_TARGET_ARCHITECTURE_IS_AARCH64 AND NOT(LIBC_TARGET_OS_IS_BAREMETAL))
+      list(APPEND fq_deps_list libc.src.sys.auxv.getauxval)
+    endif()
+    if(LIBC_TEST_SUBPROCESS_TESTS AND NOT is_c_test)
+      list(APPEND fq_deps_list
+        libc.src.poll.poll
+        libc.src.signal.kill
+        libc.src.stdio.fflush
+        libc.src.stdio.stderr
+        libc.src.stdio.stdout
+        libc.src.stdlib.exit
+        libc.src.string.strsignal
+        libc.src.sys.wait.waitpid
+        libc.src.unistd.close
+        libc.src.unistd.fork
+        libc.src.unistd.pipe
+      )
+    endif()
+    if(NOT LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP AND LIBC_TARGET_OS_IS_BAREMETAL)
+      # External baremetal startup objects still need libc's process startup and
+      # termination entrypoints, even when the test itself does not depend on them.
+      list(APPEND fq_deps_list
+        libc.src.stdlib.atexit
+        libc.src.stdlib.exit
+        libc.startup.baremetal.init
+      )
+    endif()
+    if(libc.src.compiler.__stack_chk_fail IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
+      list(APPEND fq_deps_list libc.src.compiler.__stack_chk_fail)
+    endif()
+    if(libc.src.time.clock IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
+      list(APPEND fq_deps_list libc.src.time.clock)
+    endif()
+    if(LIBC_ENABLE_COVERAGE)
+      _get_libc_coverage_dependencies(coverage_deps)
+      list(APPEND fq_deps_list ${coverage_deps})
+    endif()
   endif()
   list(REMOVE_DUPLICATES fq_deps_list)
+  set(${output_var} ${fq_deps_list} PARENT_SCOPE)
+endfunction()
+
+# Helper function for create_libc_test: inspects dependencies and extracts
+# standard CMake library targets (STATIC, SHARED, INTERFACE) that do not have
+# a custom TARGET_TYPE (e.g. libc.utils.MPFRWrapper, libc.utils.MPCWrapper).
+function(_get_libc_test_link_libraries output_var fq_deps_list)
+  set(link_libs "")
+  foreach(dep IN LISTS fq_deps_list)
+    if(TARGET ${dep})
+      get_target_property(aliased_target ${dep} "ALIASED_TARGET")
+      if(aliased_target)
+        set(dep ${aliased_target})
+      endif()
+      get_target_property(dep_type ${dep} "TARGET_TYPE")
+      get_target_property(cmake_type ${dep} TYPE)
+      if(NOT dep_type AND cmake_type MATCHES ".*_LIBRARY")
+        list(APPEND link_libs ${dep})
+      endif()
+    endif()
+  endforeach()
+  if(link_libs)
+    list(REMOVE_DUPLICATES link_libs)
+  endif()
+  set(${output_var} ${link_libs} PARENT_SCOPE)
+endfunction()
+
+# Helper function for create_libc_test: configures linking for full-build hermetic
+# tests by creating the static .__libc__ archive, setting hermetic/GPU link
+# options, and wiring link libraries and compiler runtime.
+function(_link_libc_hermetic_test fq_target_name fq_build_target_name startup_target_dep link_object_files test_link_libraries)
+  if(LIBC_ENABLE_COVERAGE)
+    _get_libc_coverage_dependencies(coverage_deps)
+    foreach(cov_dep IN LISTS coverage_deps)
+      get_target_property(is_alias ${cov_dep} "IS_ALIAS")
+      if(is_alias)
+        get_target_property(real_target ${cov_dep} "DEPS")
+      else()
+        set(real_target ${cov_dep})
+      endif()
+      string(REPLACE "${real_target}.__internal__" "${real_target}" link_object_files "${link_object_files}")
+    endforeach()
+  endif()
+
+  add_library(
+    ${fq_target_name}.__libc__
+    STATIC
+    EXCLUDE_FROM_ALL
+      ${link_object_files}
+  )
+  set_target_properties(${fq_target_name}.__libc__
+    PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+  set_target_properties(${fq_target_name}.__libc__
+    PROPERTIES ARCHIVE_OUTPUT_NAME ${fq_target_name}.libc)
+
+  set(compiler_runtime "")
+  if(LIBC_TARGET_ARCHITECTURE_IS_AMDGPU)
+    target_link_options(${fq_build_target_name} PRIVATE
+      ${LIBC_COMPILE_OPTIONS_DEFAULT} -Wno-multi-gpu
+      -mcpu=${LIBC_GPU_TARGET_ARCHITECTURE} -flto -nostdlib -static
+      "-Wl,-mllvm,-amdhsa-code-object-version=${LIBC_GPU_CODE_OBJECT_VERSION}")
+  elseif(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+    target_link_options(${fq_build_target_name} PRIVATE
+      ${LIBC_COMPILE_OPTIONS_DEFAULT} -Wno-multi-gpu
+      "-Wl,--suppress-stack-size-warning"
+      "-Wl,-mllvm,-nvptx-emit-init-fini-kernel"
+      -march=${LIBC_GPU_TARGET_ARCHITECTURE} -nostdlib -static
+      "--cuda-path=${LIBC_CUDA_ROOT}")
+  elseif(LIBC_CC_SUPPORTS_NOSTDLIBPP)
+    set(link_options
+      -nostartfiles
+      -nodefaultlibs
+      -nostdlib++
+      -static
+      ${LIBC_LINK_OPTIONS_DEFAULT}
+      ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
+    )
+    if(LIBC_ENABLE_COVERAGE)
+      list(APPEND link_options
+        -noprofilelib
+        -u__llvm_profile_runtime
+      )
+    endif()
+    target_link_options(${fq_build_target_name} PRIVATE ${link_options})
+  else()
+    # Older version of gcc does not support `nostdlib++` flag.  We use
+    # `nostdlib` and link against libgcc_s, which cannot be linked statically.
+    set(link_options
+      -nostartfiles
+      -nodefaultlibs
+      -nostdlib
+      ${LIBC_LINK_OPTIONS_DEFAULT}
+      ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
+    )
+    if(LIBC_ENABLE_COVERAGE)
+      list(APPEND link_options
+        -noprofilelib
+        -u__llvm_profile_runtime
+      )
+    endif()
+    target_link_options(${fq_build_target_name} PRIVATE ${link_options})
+    list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
+  endif()
 
-  _get_common_test_compile_options(compile_options "${LIBC_UNITTEST_C_TEST}"
-    "${LIBC_UNITTEST_FLAGS}")
+  set(coverage_link_libs "")
+  if(LIBC_ENABLE_COVERAGE)
+    set(coverage_link_libs
+      "${LIBC_CLANG_PROFILE_LIB}"
+      ${fq_target_name}.__libc__
+    )
+  endif()
 
+  target_link_libraries(
+    ${fq_build_target_name}
+    PRIVATE
+      ${startup_target_dep}
+      ${test_link_libraries}
+      ${fq_target_name}.__libc__
+      ${coverage_link_libs}
+      ${LIBC_TEST_BUILTINS_LIBRARY}
+      ${compiler_runtime}
+  )
+  if(LIBC_TEST_BUILTINS_TARGET)
+    target_link_libraries(${fq_build_target_name} PRIVATE $<TARGET_FILE:${LIBC_TEST_BUILTINS_TARGET}>)
+    add_dependencies(${fq_build_target_name} ${LIBC_TEST_BUILTINS_TARGET})
+  endif()
+endfunction()
+
+# Helper function for create_libc_test: configures linking for overlay mode tests
+# using host link options and linking object files and extra libraries.
+function(_link_libc_overlay_test fq_build_target_name compile_options link_object_files test_link_libraries)
   get_link_options(link_options
     ${compile_options}
     ${LIBC_LINK_OPTIONS_DEFAULT}
     ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
   )
-  list(APPEND compile_options ${LIBC_UNITTEST_COMPILE_OPTIONS})
+  target_link_options(${fq_build_target_name} PRIVATE ${link_options})
+
+  set(link_libraries ${link_object_files})
+  list(APPEND link_libraries ${test_link_libraries})
+  target_link_libraries(${fq_build_target_name} PRIVATE ${link_libraries})
+endfunction()
+
+# Helper function for create_libc_test: sets up the post-build custom target
+# and command to run the test binary (handling emulators, argument passing,
+# and parameter files).
+function(_add_libc_test_runner fq_target_name fq_build_target_name test_args loader_args test_env)
+  set(test_binary "${CMAKE_CURRENT_BINARY_DIR}/${fq_build_target_name}${CMAKE_EXECUTABLE_SUFFIX}")
+  if(LIBC_TEST_CMD)
+    string(REPLACE "@BINARY@" "${test_binary}" test_cmd_parsed ${LIBC_TEST_CMD})
+    string(REPLACE " " ";" test_cmd "${test_cmd_parsed}")
+  else()
+    set(test_cmd ${test_env})
+    if(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+      list(APPEND test_cmd "LIBOMPTARGET_STACK_SIZE=3072")
+    endif()
+    list(APPEND test_cmd
+      ${CMAKE_CROSSCOMPILING_EMULATOR} ${loader_args}
+      "${test_binary}" ${test_args})
+  endif()
+
+  if(loader_args OR test_args OR test_env OR LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+    set(_params_content "")
+    foreach(_arg IN LISTS loader_args)
+      string(APPEND _params_content "${_arg}\n")
+    endforeach()
+    string(APPEND _params_content "---\n")
+    foreach(_arg IN LISTS test_args)
+      string(APPEND _params_content "${_arg}\n")
+    endforeach()
+    string(APPEND _params_content "---\n")
+    foreach(_env_entry IN LISTS test_env)
+      string(APPEND _params_content "${_env_entry}\n")
+    endforeach()
+    if(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+      string(APPEND _params_content "LIBOMPTARGET_STACK_SIZE=3072\n")
+    endif()
+
+    file(GENERATE
+      OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${fq_build_target_name}.params"
+      CONTENT "${_params_content}"
+    )
+  endif()
+
+  add_custom_target(
+    ${fq_target_name}
+    COMMAND ${test_cmd}
+    COMMAND_EXPAND_LISTS
+    DEPENDS ${fq_build_target_name}
+    COMMENT "Running test ${fq_target_name}"
+    ${LIBC_HERMETIC_TEST_JOB_POOL}
+  )
+endfunction()
+
+# Helper function for create_libc_test: registers the test and its build target
+# with the appropriate test suite (or libc-tests / libc-tests-build).
+function(_register_libc_test_with_suite fq_target_name fq_build_target_name suite)
+  if(suite)
+    add_libc_testsuite(${suite})
+    add_dependencies(${suite} ${fq_target_name})
+    if(TARGET ${suite}-build)
+      add_dependencies(${suite}-build ${fq_build_target_name})
+    endif()
+  else()
+    if(TARGET libc-tests)
+      add_dependencies(libc-tests ${fq_target_name})
+    endif()
+    if(TARGET libc-tests-build)
+      add_dependencies(libc-tests-build ${fq_build_target_name})
+    endif()
+  endif()
+endfunction()
+
+# Core rule to create a libc test executable and runner (overlay or hermetic).
+function(create_libc_test fq_target_name)
+  if(NOT LLVM_INCLUDE_TESTS)
+    return()
+  endif()
+
+  llvm_libc_parse_arguments(TEST ${ARGN})
+
+  # 1. Validate build mode and target platform
+  if(LLVM_LIBC_FULL_BUILD AND TEST_OVERLAY_ONLY)
+    return()
+  endif()
+  if(NOT LLVM_LIBC_FULL_BUILD AND TEST_FULL_BUILD_ONLY)
+    return()
+  endif()
+
+  set(startup_target_dep "")
+  if(LLVM_LIBC_FULL_BUILD)
+    set(startup_target libc.startup.${LIBC_TARGET_OS}.crt1)
+    if(LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP)
+      if(TEST_STARTUP)
+        message(FATAL_ERROR
+          "Hermetic test ${fq_target_name} provides an external STARTUP target "
+          "while internal startup is enabled.")
+      endif()
+      if(NOT TARGET ${startup_target})
+        if(LIBC_CMAKE_VERBOSE_LOGGING)
+          message(STATUS "Skipping ${fq_target_name} as ${startup_target} is not available on ${LIBC_TARGET_OS}.")
+        endif()
+        return()
+      endif()
+      set(startup_target_dep ${startup_target})
+    elseif(TEST_STARTUP)
+      get_fq_dep_name(startup_target_dep ${TEST_STARTUP})
+      if(NOT TARGET ${startup_target_dep})
+        message(FATAL_ERROR
+          "Hermetic test ${fq_target_name} startup target ${startup_target_dep} does not exist.")
+      endif()
+    endif()
+
+    if(NOT startup_target_dep AND NOT LIBC_TEST_LINK_OPTIONS_DEFAULT)
+      if(LIBC_CMAKE_VERBOSE_LOGGING)
+        message(STATUS "Skipping ${fq_target_name} as it has no startup provider.")
+      endif()
+      return()
+    endif()
+  endif()
+
+  if(NOT TEST_SRCS)
+    message(FATAL_ERROR "'create_libc_test' target requires a SRCS list of .cpp files.")
+  endif()
+
+  # 2. Resolve dependencies and link objects
+  _get_libc_test_dependencies(fq_deps_list "${TEST_DEPENDS}" "${startup_target_dep}" "${TEST_C_TEST}")
 
   if(SHOW_INTERMEDIATE_OBJECTS)
-    message(STATUS "Adding unit test ${fq_target_name}")
-    if(${SHOW_INTERMEDIATE_OBJECTS} STREQUAL "DEPS")
-      foreach(dep IN LISTS LIBC_UNITTEST_DEPENDS)
+    message(STATUS "Adding test ${fq_target_name}")
+    if("${SHOW_INTERMEDIATE_OBJECTS}" STREQUAL "DEPS")
+      foreach(dep IN LISTS TEST_DEPENDS)
         message(STATUS "  ${fq_target_name} depends on ${dep}")
       endforeach()
     endif()
@@ -354,32 +741,24 @@ function(create_libc_unittest fq_target_name)
   get_object_files_for_test(
     link_object_files skipped_entrypoints_list ${fq_deps_list})
   if(skipped_entrypoints_list)
-    # If a test is OS/target machine independent, it has to be skipped if the
-    # OS/target machine combination does not provide any dependent entrypoints.
-    # If a test is OS/target machine specific, then such a test will live is a
-    # OS/target machine specific directory and will be skipped at the directory
-    # level if required.
-    #
-    # There can potentially be a setup like this: A unittest is setup for a
-    # OS/target machine independent object library, which in turn depends on a
-    # machine specific object library. Such a test would be testing internals of
-    # the libc and it is assumed that they will be rare in practice. So, they
-    # can be skipped in the corresponding CMake files using platform specific
-    # logic. This pattern is followed in the startup tests for example.
-    #
-    # Another pattern that is present currently is to detect machine
-    # capabilities and add entrypoints and tests accordingly. That approach is
-    # much lower level approach and is independent of the kind of skipping that
-    # is happening here at the entrypoint level.
     if(LIBC_CMAKE_VERBOSE_LOGGING)
-      set(msg "Skipping unittest ${fq_target_name} as it has missing deps: "
+      set(msg "Skipping test ${fq_target_name} as it has missing deps: "
         "${skipped_entrypoints_list}.")
       message(STATUS ${msg})
     endif()
     return()
   endif()
+  list(REMOVE_DUPLICATES link_object_files)
 
-  if(LIBC_UNITTEST_NO_RUN_POSTBUILD)
+  # Collect standard library targets from dependencies that do not provide raw object files.
+  _get_libc_test_link_libraries(dep_link_libraries "${fq_deps_list}")
+  list(APPEND TEST_LINK_LIBRARIES ${dep_link_libraries})
+  if(TEST_LINK_LIBRARIES)
+    list(REMOVE_DUPLICATES TEST_LINK_LIBRARIES)
+  endif()
+
+  # 3. Create the test executable
+  if(TEST_NO_RUN_POSTBUILD)
     set(fq_build_target_name ${fq_target_name})
   else()
     set(fq_build_target_name ${fq_target_name}.__build__)
@@ -388,80 +767,86 @@ function(create_libc_unittest fq_target_name)
   add_executable(
     ${fq_build_target_name}
     EXCLUDE_FROM_ALL
-    ${LIBC_UNITTEST_SRCS}
-    ${LIBC_UNITTEST_HDRS}
+    ${TEST_SRCS}
+    ${TEST_HDRS}
   )
+
   target_include_directories(${fq_build_target_name} SYSTEM PRIVATE ${LIBC_INCLUDE_DIR})
   target_include_directories(${fq_build_target_name} PRIVATE ${LIBC_SOURCE_DIR})
   target_include_directories(${fq_build_target_name} PRIVATE ${LIBC_BUILD_DIR})
+
+  if(LLVM_LIBC_FULL_BUILD)
+    _get_hermetic_test_compile_options(compile_options "${TEST_C_TEST}"
+      "${TEST_FLAGS}")
+  else()
+    _get_common_test_compile_options(compile_options "${TEST_C_TEST}"
+      "${TEST_FLAGS}")
+  endif()
+  list(APPEND compile_options ${TEST_COMPILE_OPTIONS})
   target_compile_options(${fq_build_target_name} PRIVATE ${compile_options})
-  target_link_options(${fq_build_target_name} PRIVATE ${link_options})
 
-  if(NOT LIBC_UNITTEST_CXX_STANDARD)
-    set(LIBC_UNITTEST_CXX_STANDARD ${CMAKE_CXX_STANDARD})
+  if(NOT TEST_CXX_STANDARD)
+    set(TEST_CXX_STANDARD ${CMAKE_CXX_STANDARD})
   endif()
   set_target_properties(
     ${fq_build_target_name}
     PROPERTIES
-      CXX_STANDARD ${LIBC_UNITTEST_CXX_STANDARD}
+      RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+      CXX_STANDARD ${TEST_CXX_STANDARD}
   )
 
-  set(link_libraries ${link_object_files})
-  # Test object files will depend on LINK_LIBRARIES passed down from `add_fp_unittest`
-  list(APPEND link_libraries ${LIBC_UNITTEST_LINK_LIBRARIES})
-
-  set_target_properties(${fq_build_target_name}
-    PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+  # 4. Configure linking
+  if(LLVM_LIBC_FULL_BUILD)
+    _link_libc_hermetic_test(
+      ${fq_target_name}
+      ${fq_build_target_name}
+      "${startup_target_dep}"
+      "${link_object_files}"
+      "${TEST_LINK_LIBRARIES}"
+    )
+  else()
+    _link_libc_overlay_test(
+      ${fq_build_target_name}
+      "${compile_options}"
+      "${link_object_files}"
+      "${TEST_LINK_LIBRARIES}"
+    )
+  endif()
 
   add_dependencies(
     ${fq_build_target_name}
     ${fq_deps_list}
   )
 
-  target_link_libraries(${fq_build_target_name} PRIVATE ${link_libraries})
-
-  if(NOT LIBC_UNITTEST_NO_RUN_POSTBUILD)
-    add_custom_target(
+  # 5. Add test runner target if post-build run is enabled
+  if(NOT TEST_NO_RUN_POSTBUILD)
+    _add_libc_test_runner(
       ${fq_target_name}
-      COMMAND ${LIBC_UNITTEST_ENV} ${CMAKE_CROSSCOMPILING_EMULATOR} ${CMAKE_CURRENT_BINARY_DIR}/${fq_build_target_name}
-      COMMENT "Running unit test ${fq_target_name}"
-      DEPENDS ${fq_build_target_name}
+      ${fq_build_target_name}
+      "${TEST_ARGS}"
+      "${TEST_LOADER_ARGS}"
+      "${TEST_ENV}"
     )
   endif()
 
-  # The SUITE variable can be used to group tests into a custom target.
-  # If a target named ${LIBC_UNITTEST_SUITE}-build exists, we add the
-  # test executable to it as a dependency. This allows building the
-  # test binaries for Lit without triggering their execution.
-  if(LIBC_UNITTEST_SUITE)
-    add_dependencies(${LIBC_UNITTEST_SUITE} ${fq_target_name})
-    if(TARGET ${LIBC_UNITTEST_SUITE}-build)
-      add_dependencies(${LIBC_UNITTEST_SUITE}-build ${fq_build_target_name})
+  # 6. Register with test suite
+  if(TEST_IS_GPU_BENCHMARK)
+    if(TEST_SUITE)
+      add_dependencies(${TEST_SUITE} ${fq_target_name})
     endif()
-  endif()
-  add_dependencies(libc-unit-tests ${fq_target_name})
-  if(TARGET libc-unit-tests-build)
-    add_dependencies(libc-unit-tests-build ${fq_build_target_name})
+  else()
+    _register_libc_test_with_suite(${fq_target_name} ${fq_build_target_name} "${TEST_SUITE}")
   endif()
 endfunction()
 
-function(_add_libc_unittest target_name)
+function(_add_libc_test target_name)
   add_target_with_flags(
     ${target_name}
-    CREATE_TARGET create_libc_unittest
+    CREATE_TARGET create_libc_test
     ${ARGN}
   )
 endfunction()
 
-function(add_libc_exhaustive_testsuite suite_name)
-  add_custom_target(${suite_name})
-  add_dependencies(exhaustive-check-libc ${suite_name})
-endfunction()
-
-function(add_libc_long_running_testsuite suite_name)
-  add_custom_target(${suite_name})
-  add_dependencies(libc-long-running-tests ${suite_name})
-endfunction()
 
 # Rule to add a fuzzer test.
 # Usage
@@ -472,13 +857,7 @@ endfunction()
 #      DEPENDS <list of dependencies>
 #    )
 function(add_libc_fuzzer target_name)
-  cmake_parse_arguments(
-    "LIBC_FUZZER"
-    "NEED_MPFR" # Optional arguments
-    "" # Single value arguments
-    "SRCS;HDRS;DEPENDS;COMPILE_OPTIONS;LINK_LIBRARIES" # Multi-value arguments
-    ${ARGN}
-  )
+  llvm_libc_parse_arguments(LIBC_FUZZER ${ARGN})
   if(NOT LIBC_FUZZER_SRCS)
     message(FATAL_ERROR "'add_libc_fuzzer' target requires a SRCS list of .cpp "
       "files.")
@@ -546,39 +925,10 @@ function(add_libc_fuzzer target_name)
     PRIVATE
       ${LIBC_FUZZER_COMPILE_OPTIONS})
 
-endfunction()
-
-# Get libgcc_s to be used in hermetic and integration tests.
-if(NOT MSVC AND NOT LIBC_CC_SUPPORTS_NOSTDLIBPP)
-  execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=libgcc_s.so.1
-    OUTPUT_VARIABLE LIBGCC_S_LOCATION)
-  string(STRIP ${LIBGCC_S_LOCATION} LIBGCC_S_LOCATION)
-endif()
-
-# Hermetic and integration tests link with -nodefaultlibs, so the compiler
-# runtime builtins have to be linked explicitly. -print-libgcc-file-name
-# reports whichever library the driver would use (libgcc.a or
-# libclang_rt.builtins.a). The query uses the same flags as the test link so
-# that the driver selects the same multilib. GPU and UEFI targets do not link a
-# compiler runtime.
-if(LLVM_INCLUDE_TESTS AND LLVM_LIBC_FULL_BUILD AND NOT LIBC_TARGET_OS_IS_GPU AND
-  NOT LIBC_TARGET_OS_IS_UEFI AND
-  NOT LIBC_TEST_BUILTINS_LIBRARY AND NOT LIBC_TEST_BUILTINS_TARGET)
-  separate_arguments(libc_cxx_flags NATIVE_COMMAND "${CMAKE_CXX_FLAGS}")
-  execute_process(
-    COMMAND ${CMAKE_CXX_COMPILER} ${libc_cxx_flags}
-      ${LIBC_COMPILE_OPTIONS_DEFAULT} ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
-      -print-libgcc-file-name
-    OUTPUT_VARIABLE LIBC_TEST_BUILTINS_LIBRARY
-    OUTPUT_STRIP_TRAILING_WHITESPACE)
-  if(NOT EXISTS "${LIBC_TEST_BUILTINS_LIBRARY}")
-    message(FATAL_ERROR "Compiler runtime builtins library "
-      "'${LIBC_TEST_BUILTINS_LIBRARY}' not found. Set "
-      "LIBC_TEST_BUILTINS_LIBRARY or LIBC_TEST_BUILTINS_TARGET.")
-  endif()
-endif()
+endfunction()
+
 
-# DEPRECATED: Use add_hermetic_test instead.
+# DEPRECATED: Use add_libc_test instead.
 #
 # Rule to add an integration test. An integration test is like a unit test
 # but does not use the system libc. Not even the startup objects from the
@@ -607,13 +957,7 @@ function(add_integration_test test_name)
     message(STATUS "Skipping ${fq_target_name} as it is not available on ${LIBC_TARGET_OS}.")
     return()
   endif()
-  cmake_parse_arguments(
-    "INTEGRATION_TEST"
-    "" # No optional arguments
-    "SUITE" # Single value arguments
-    "SRCS;HDRS;DEPENDS;ARGS;ENV;COMPILE_OPTIONS;LOADER_ARGS" # Multi-value arguments
-    ${ARGN}
-  )
+  llvm_libc_parse_arguments(INTEGRATION_TEST ${ARGN})
 
   if(NOT INTEGRATION_TEST_SUITE)
     message(FATAL_ERROR "SUITE not specified for ${fq_target_name}")
@@ -802,420 +1146,84 @@ function(add_integration_test test_name)
   endif()
 endfunction()
 
-# Rule to add a hermetic program. A hermetic program is one whose executable is fully
-# statically linked and consists of pieces drawn only from LLVM's libc. Nothing,
-# including the startup objects, come from the system libc.
-#
-# For the GPU, these can be either tests or benchmarks, depending on the value
-# of the LINK_LIBRARIES arg.
-#
-# Usage:
-#   add_libc_hermetic(
-#     <target name>
-#     SUITE <the suite to which the test should belong>
-#     SRCS <src1.cpp> [src2.cpp ...]
-#     HDRS [hdr1.cpp ...]
-#     DEPENDS <list of entrypoint or other object targets>
-#     ARGS <list of command line arguments to be passed to the test>
-#     ENV <list of environment variables to set before running the test>
-#     COMPILE_OPTIONS <list of special compile options for the test>
-#     LINK_LIBRARIES <list of linking libraries for this target>
-#     LOADER_ARGS <list of special args to loaders (like the GPU loader)>
-#     STARTUP <startup object target used when internal startup is disabled>
-#   )
-function(add_libc_hermetic test_name)
-  get_fq_target_name(${test_name} fq_target_name)
-  get_fq_target_name(${test_name}.libc fq_libc_target_name)
-
-  cmake_parse_arguments(
-    "HERMETIC_TEST"
-    "IS_GPU_BENCHMARK;NO_RUN_POSTBUILD;C_TEST" # Optional arguments
-    "SUITE;CXX_STANDARD;STARTUP" # Single value arguments
-    "SRCS;HDRS;DEPENDS;ARGS;ENV;COMPILE_OPTIONS;LINK_LIBRARIES;FLAGS;LOADER_ARGS" # Multi-value arguments
-    ${ARGN}
-  )
+# A unified function to add a libc test.
+# FULL_BUILD_ONLY: skip test in overlay mode.
+# OVERLAY_ONLY: skip test in full build mode.
+# FP_TEST: add floating-point test helper dependencies.
+# NEED_MPFR / NEED_MPFR_F80 / NEED_MPFR_F128: link against MPFR; tests requiring MPFR run only in overlay mode.
+# NEED_MPC: link against MPC and MPFR; tests requiring MPC run only in overlay mode.
+function(add_libc_test test_name)
+  llvm_libc_parse_arguments(LIBC_TEST ${ARGN})
 
-  set(startup_target libc.startup.${LIBC_TARGET_OS}.crt1)
-  set(startup_target_dep "")
-  if(LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP)
-    if(HERMETIC_TEST_STARTUP)
-      message(FATAL_ERROR
-        "Hermetic test ${fq_target_name} provides an external STARTUP target "
-        "while internal startup is enabled.")
-    endif()
-    if(NOT TARGET ${startup_target})
-      if(LIBC_CMAKE_VERBOSE_LOGGING)
-        message(STATUS "Skipping ${fq_target_name} as ${startup_target} is not available on ${LIBC_TARGET_OS}.")
-      endif()
+  if(LIBC_TEST_NEED_MPC)
+    set(LIBC_TEST_NEED_MPFR TRUE)
+    if(NOT LIBC_TESTS_CAN_USE_MPC)
+      message(VERBOSE "Complex test ${test_name} will be skipped as MPC library is not available.")
       return()
     endif()
-    set(startup_target_dep ${startup_target})
-  elseif(HERMETIC_TEST_STARTUP)
-    get_fq_dep_name(startup_target_dep ${HERMETIC_TEST_STARTUP})
-    if(NOT TARGET ${startup_target_dep})
-      message(FATAL_ERROR
-        "Hermetic test ${fq_target_name} startup target ${startup_target_dep} does not exist.")
-    endif()
+    list(APPEND LIBC_TEST_DEPENDS libc.utils.MPCWrapper)
   endif()
 
-  if(NOT startup_target_dep AND NOT LIBC_TEST_LINK_OPTIONS_DEFAULT)
-    if(LIBC_CMAKE_VERBOSE_LOGGING)
-      message(STATUS "Skipping ${fq_target_name} as it has no startup provider.")
+  if(LIBC_TEST_NEED_MPFR_F80)
+    set(LIBC_TEST_NEED_MPFR TRUE)
+    if(NOT LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+      message(VERBOSE "Math test ${test_name} will be skipped as float80 support in MPFR is not available.")
+      return()
     endif()
-    return()
-  endif()
-
-  if(NOT HERMETIC_TEST_SUITE)
-    message(FATAL_ERROR "SUITE not specified for ${fq_target_name}")
-  endif()
-  if(NOT HERMETIC_TEST_SRCS)
-    message(FATAL_ERROR "The SRCS list for add_integration_test is missing.")
-  endif()
-
-  get_fq_deps_list(fq_deps_list ${HERMETIC_TEST_DEPENDS})
-  list(APPEND fq_deps_list
-    # Hermetic tests use the platform's startup object. So, their deps also
-    # have to be collected.
-    ${startup_target_dep}
-    # We always add the memory functions objects. This is because the
-    # compiler's codegen can emit calls to the C memory functions.
-    libc.src.__support.StringUtil.error_to_string
-    libc.src.string.memcmp
-    libc.src.string.memcpy
-    libc.src.string.memmove
-    libc.src.string.memset
-    libc.src.strings.bcmp
-    libc.src.strings.bzero
-    libc.test.UnitTest.ErrnoSetterMatcher
-    libc.test.UnitTest.LibcTest
-    libc.test.UnitTest.HermeticTestUtils
-  )
-  if(HERMETIC_TEST_C_TEST)
-    list(APPEND fq_deps_list libc.test.UnitTest.LibcCTest)
-  endif()
-  if(LIBC_TARGET_ARCHITECTURE_IS_AARCH64 AND NOT(LIBC_TARGET_OS_IS_BAREMETAL))
-    list(APPEND fq_deps_list libc.src.sys.auxv.getauxval)
-  endif()
-
-  # Syscalls used by death tests.
-  if(LIBC_TEST_SUBPROCESS_TESTS AND NOT HERMETIC_TEST_C_TEST)
-    list(APPEND fq_deps_list
-      libc.test.UnitTest.LibcDeathTestExecutors
-      libc.src.poll.poll
-      libc.src.signal.kill
-      libc.src.stdio.fflush
-      libc.src.stdio.stderr
-      libc.src.stdio.stdout
-      libc.src.stdlib.exit
-      libc.src.string.strsignal
-      libc.src.sys.wait.waitpid
-      libc.src.unistd.close
-      libc.src.unistd.fork
-      libc.src.unistd.pipe
-    )
-  endif()
-
-  if(NOT LLVM_LIBC_HERMETIC_TEST_USE_INTERNAL_STARTUP AND LIBC_TARGET_OS_IS_BAREMETAL)
-    # External baremetal startup objects still need libc's process startup and
-    # termination entrypoints, even when the test itself does not depend on them.
-    list(APPEND fq_deps_list
-      libc.src.stdlib.atexit
-      libc.src.stdlib.exit
-      libc.startup.baremetal.init
-    )
-  endif()
-
-  if(libc.src.compiler.__stack_chk_fail IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
-    # __stack_chk_fail should always be included if supported to allow building
-    # libc with the stack protector enabled.
-    list(APPEND fq_deps_list libc.src.compiler.__stack_chk_fail)
-  endif()
-
-  if(libc.src.time.clock IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
-    # We will link in the 'clock' implementation if it exists for test timing.
-    list(APPEND fq_deps_list libc.src.time.clock)
-  endif()
-
-  if(LIBC_ENABLE_COVERAGE)
-    set(coverage_deps
-      libc.src.stdio.fclose
-      libc.src.stdio.fdopen
-      libc.src.stdio.feof
-      libc.src.stdio.fflush
-      libc.src.stdio.fileno
-      libc.src.stdio.fopen
-      libc.src.stdio.fprintf
-      libc.src.stdio.fread
-      libc.src.stdio.fseek
-      libc.src.stdio.ftell
-      libc.src.stdio.fwrite
-      libc.src.stdio.snprintf
-      libc.src.stdio.stderr
-      libc.src.stdio.vfprintf
-      libc.src.stdio.vsnprintf
-      libc.src.fcntl.fcntl
-      libc.src.fcntl.open
-      libc.src.stdlib.getenv
-      libc.src.stdlib.setenv
-      libc.src.stdlib.strtol
-      libc.src.string.strchr
-      libc.src.string.strcmp
-      libc.src.string.strdup
-      libc.src.string.strerror
-      libc.src.string.strlen
-      libc.src.string.strncpy
-      libc.src.string.strrchr
-      libc.src.sys.mman.madvise
-      libc.src.sys.mman.mmap
-      libc.src.sys.mman.munmap
-      libc.src.sys.prctl.prctl
-      libc.src.sys.stat.mkdir
-      libc.src.sys.utsname.uname
-      libc.src.unistd.fork
-      libc.src.unistd.ftruncate
-      libc.src.unistd.getpagesize
-      libc.src.unistd.getpid
-    )
-    list(APPEND fq_deps_list ${coverage_deps})
   endif()
 
-  list(REMOVE_DUPLICATES fq_deps_list)
-
-  # TODO: Instead of gathering internal object files from entrypoints,
-  # collect the object files with public names of entrypoints.
-  get_object_files_for_test(
-    link_object_files skipped_entrypoints_list ${fq_deps_list})
-  if(skipped_entrypoints_list)
-    if(LIBC_CMAKE_VERBOSE_LOGGING)
-      set(msg "Skipping hermetic test ${fq_target_name} as it has missing deps: "
-        "${skipped_entrypoints_list}.")
-      message(STATUS ${msg})
+  # TODO: To be removed when we find a workaround to run MPFR tests for
+  # emulated type where compiler doesn't support the native float128
+  # TODO: Check mpfr_buildopt_float128_p() returns non-zero.
+  if(LIBC_TEST_NEED_MPFR_F128)
+    set(LIBC_TEST_NEED_MPFR TRUE)
+    if(NOT LIBC_TYPES_HAS_NATIVE_FLOAT128)
+      message(VERBOSE "Math test ${test_name} will be skipped as native float128 support in MPFR is not available.")
+      return()
     endif()
-    return()
-  endif()
-  list(REMOVE_DUPLICATES link_object_files)
-
-  if(LIBC_ENABLE_COVERAGE)
-    foreach(cov_dep IN LISTS coverage_deps)
-      get_target_property(is_alias ${cov_dep} "IS_ALIAS")
-      if(is_alias)
-        get_target_property(real_target ${cov_dep} "DEPS")
-      else()
-        set(real_target ${cov_dep})
-      endif()
-      string(REPLACE "${real_target}.__internal__" "${real_target}" link_object_files "${link_object_files}")
-    endforeach()
-  endif()
-
-  # Make a library of all deps
-  add_library(
-    ${fq_target_name}.__libc__
-    STATIC
-    EXCLUDE_FROM_ALL
-      ${link_object_files}
-  )
-  set_target_properties(${fq_target_name}.__libc__
-    PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-  set_target_properties(${fq_target_name}.__libc__
-    PROPERTIES ARCHIVE_OUTPUT_NAME ${fq_target_name}.libc)
-
-  if(HERMETIC_TEST_NO_RUN_POSTBUILD)
-    set(fq_build_target_name ${fq_target_name})
-  else()
-    set(fq_build_target_name ${fq_target_name}.__build__)
-  endif()
-
-  add_executable(
-    ${fq_build_target_name}
-    EXCLUDE_FROM_ALL
-    ${HERMETIC_TEST_SRCS}
-    ${HERMETIC_TEST_HDRS}
-  )
-
-  if(NOT HERMETIC_TEST_CXX_STANDARD)
-    set(HERMETIC_TEST_CXX_STANDARD ${CMAKE_CXX_STANDARD})
   endif()
-  set_target_properties(${fq_build_target_name}
-    PROPERTIES
-      RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-      CXX_STANDARD ${HERMETIC_TEST_CXX_STANDARD}
-  )
-
-  target_include_directories(${fq_build_target_name} SYSTEM PRIVATE ${LIBC_INCLUDE_DIR})
-  target_include_directories(${fq_build_target_name} PRIVATE ${LIBC_SOURCE_DIR})
-  _get_hermetic_test_compile_options(compile_options "${HERMETIC_TEST_C_TEST}"
-    "${HERMETIC_TEST_FLAGS}")
-  target_compile_options(${fq_build_target_name} PRIVATE
-    ${compile_options}
-    ${HERMETIC_TEST_COMPILE_OPTIONS})
-
-  set(compiler_runtime "")
 
-  if(LIBC_TARGET_ARCHITECTURE_IS_AMDGPU)
-    target_link_options(${fq_build_target_name} PRIVATE
-      ${LIBC_COMPILE_OPTIONS_DEFAULT} -Wno-multi-gpu
-      -mcpu=${LIBC_GPU_TARGET_ARCHITECTURE} -flto -nostdlib -static
-      "-Wl,-mllvm,-amdhsa-code-object-version=${LIBC_GPU_CODE_OBJECT_VERSION}")
-  elseif(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
-    target_link_options(${fq_build_target_name} PRIVATE
-      ${LIBC_COMPILE_OPTIONS_DEFAULT} -Wno-multi-gpu
-      "-Wl,--suppress-stack-size-warning"
-      "-Wl,-mllvm,-nvptx-emit-init-fini-kernel"
-      -march=${LIBC_GPU_TARGET_ARCHITECTURE} -nostdlib -static
-      "--cuda-path=${LIBC_CUDA_ROOT}")
-  elseif(LIBC_CC_SUPPORTS_NOSTDLIBPP)
-    set(link_options
-      -nostartfiles
-      -nodefaultlibs
-      -nostdlib++
-      -static
-      ${LIBC_LINK_OPTIONS_DEFAULT}
-      ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
-    )
-    if(LIBC_ENABLE_COVERAGE)
-      list(APPEND link_options
-        -noprofilelib
-        -u__llvm_profile_runtime
-      )
+  if(LIBC_TEST_NEED_MPFR)
+    if(LIBC_TEST_FULL_BUILD_ONLY)
+      message(FATAL_ERROR "Full-build test cannot require MPFR.")
     endif()
-    target_link_options(${fq_build_target_name} PRIVATE ${link_options})
-  else()
-    # Older version of gcc does not support `nostdlib++` flag.  We use
-    # `nostdlib` and link against libgcc_s, which cannot be linked statically.
-    set(link_options
-      -nostartfiles
-      -nodefaultlibs
-      -nostdlib
-      ${LIBC_LINK_OPTIONS_DEFAULT}
-      ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
-    )
-    if(LIBC_ENABLE_COVERAGE)
-      list(APPEND link_options
-        -noprofilelib
-        -u__llvm_profile_runtime
-      )
+    if(NOT LIBC_TESTS_CAN_USE_MPFR)
+      message(VERBOSE "Test ${test_name} will be skipped as MPFR library is not available.")
+      return()
     endif()
-    target_link_options(${fq_build_target_name} PRIVATE ${link_options})
-    list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
+    set(LIBC_TEST_OVERLAY_ONLY TRUE)
+    list(APPEND LIBC_TEST_DEPENDS libc.utils.MPFRWrapper)
   endif()
 
-  set(coverage_link_libs "")
-  if(LIBC_ENABLE_COVERAGE)
-    set(coverage_link_libs
-      "${LIBC_CLANG_PROFILE_LIB}"
-      ${fq_target_name}.__libc__
+  if(LIBC_TEST_FP_TEST)
+    list(APPEND LIBC_TEST_DEPENDS
+      libc.hdr.math_macros
+      libc.test.UnitTest.FPTestHelpers
+      libc.hdr.stdint_proxy
     )
   endif()
 
-  target_link_libraries(
-    ${fq_build_target_name}
-    PRIVATE
-      ${startup_target_dep}
-      ${HERMETIC_TEST_LINK_LIBRARIES}
-      ${fq_target_name}.__libc__
-      ${coverage_link_libs}
-      ${LIBC_TEST_BUILTINS_LIBRARY}
-      ${compiler_runtime}
-  )
-  if(LIBC_TEST_BUILTINS_TARGET)
-    target_link_libraries(${fq_build_target_name} PRIVATE $<TARGET_FILE:${LIBC_TEST_BUILTINS_TARGET}>)
-    add_dependencies(${fq_build_target_name} ${LIBC_TEST_BUILTINS_TARGET})
+  if(LLVM_LIBC_FULL_BUILD AND LIBC_TEST_OVERLAY_ONLY)
+    return()
   endif()
-  add_dependencies(${fq_build_target_name} ${fq_deps_list})
-
-  if(NOT HERMETIC_TEST_NO_RUN_POSTBUILD)
-    if(LIBC_TEST_CMD)
-      # In the form of "<command> binary=@BINARY@", e.g. "qemu-system-arm -loader$<COMMA>file=@BINARY@"
-      string(REPLACE "@BINARY@" "$<TARGET_FILE:${fq_build_target_name}>" test_cmd_parsed ${LIBC_TEST_CMD})
-      string(REPLACE " " ";" test_cmd "${test_cmd_parsed}")
-    else()
-      set(test_cmd ${HERMETIC_TEST_ENV}
-        $<$<BOOL:${LIBC_TARGET_ARCHITECTURE_IS_NVPTX}>:LIBOMPTARGET_STACK_SIZE=3072>
-        ${CMAKE_CROSSCOMPILING_EMULATOR} ${HERMETIC_TEST_LOADER_ARGS}
-        $<TARGET_FILE:${fq_build_target_name}> ${HERMETIC_TEST_ARGS})
-    endif()
-
-    set(_params_content "")
-    foreach(_arg IN LISTS HERMETIC_TEST_LOADER_ARGS)
-      string(APPEND _params_content "${_arg}\n")
-    endforeach()
-    string(APPEND _params_content "---\n")
-    foreach(_arg IN LISTS HERMETIC_TEST_ARGS)
-      string(APPEND _params_content "${_arg}\n")
-    endforeach()
-    string(APPEND _params_content "---\n")
-    foreach(_env_entry IN LISTS HERMETIC_TEST_ENV)
-      string(APPEND _params_content "${_env_entry}\n")
-    endforeach()
-    if(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
-      string(APPEND _params_content "LIBOMPTARGET_STACK_SIZE=3072\n")
-    endif()
-
-    if(HERMETIC_TEST_LOADER_ARGS OR HERMETIC_TEST_ARGS OR HERMETIC_TEST_ENV OR LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
-      file(GENERATE
-        OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${fq_build_target_name}.params"
-        CONTENT "${_params_content}"
-      )
-    endif()
-
-    add_custom_target(
-      ${fq_target_name}
-      DEPENDS ${fq_target_name}.__cmd__
-    )
-
-    add_custom_command(
-      OUTPUT ${fq_target_name}.__cmd__
-      COMMAND ${test_cmd}
-      COMMAND_EXPAND_LISTS
-      COMMENT "Running hermetic test ${fq_target_name}"
-      ${LIBC_HERMETIC_TEST_JOB_POOL}
-    )
-
-    set_source_files_properties(${fq_target_name}.__cmd__
-      PROPERTIES
-        SYMBOLIC "TRUE"
-    )
+  if(NOT LLVM_LIBC_FULL_BUILD AND LIBC_TEST_FULL_BUILD_ONLY)
+    return()
   endif()
 
-  add_dependencies(${HERMETIC_TEST_SUITE} ${fq_target_name})
-  if(NOT ${HERMETIC_TEST_IS_GPU_BENCHMARK})
-    # If it is a benchmark, it will already have been added to the
-    # gpu-benchmark target
-    add_dependencies(libc-hermetic-tests ${fq_target_name})
-    if(LIBC_HERMETIC_TEST_SUITE)
-      add_dependencies(${LIBC_HERMETIC_TEST_SUITE} ${fq_target_name})
-      # If a target named ${LIBC_HERMETIC_TEST_SUITE}-build exists, we add the
-      # test executable to it as a dependency. This allows building the
-      # test binaries for Lit without triggering their execution.
-      if(TARGET ${LIBC_HERMETIC_TEST_SUITE}-build)
-        add_dependencies(${LIBC_HERMETIC_TEST_SUITE}-build ${fq_build_target_name})
-      endif()
-    endif()
-    if(TARGET libc-hermetic-tests-build)
-      add_dependencies(libc-hermetic-tests-build ${fq_build_target_name})
-    endif()
-  endif()
+  forward_arguments(LIBC_TEST forwarded_args)
+  _add_libc_test(
+    ${test_name}
+    ${forwarded_args}
+    ${LIBC_TEST_UNPARSED_ARGUMENTS}
+  )
 endfunction()
 
-# A convenience function to add both a unit test as well as a hermetic test.
-function(add_libc_test test_name)
-  cmake_parse_arguments(
-    "LIBC_TEST"
-    "FULL_BUILD_ONLY;OVERLAY_BUILD_ONLY" # Optional arguments
-    "" # Single value arguments
-    "" # Multi-value arguments
+function(add_fp_unittest test_name)
+  add_libc_test(
+    ${test_name}
+    FP_TEST
     ${ARGN}
   )
-  if(LLVM_LIBC_FULL_BUILD)
-    if(NOT LIBC_TEST_OVERLAY_BUILD_ONLY)
-      add_libc_hermetic(${test_name} ${LIBC_TEST_UNPARSED_ARGUMENTS})
-    endif()
-  else()
-    # Overlay mode
-    if(NOT LIBC_TEST_FULL_BUILD_ONLY)
-      _add_libc_unittest(${test_name} ${LIBC_TEST_UNPARSED_ARGUMENTS})
-    endif()
-  endif()
 endfunction()
 
 # Tests all implementations that can run on the target CPU.
diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index 8e58ca5d14ffdf..344c7a9033a97a 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -170,19 +170,19 @@ export LLVM_PROFDATA="$PWD/build-clang/bin/llvm-profdata"
 export LLVM_COV="$PWD/build-clang/bin/llvm-cov"
 ```
 
-### 4. Build and Execute Hermetic Tests
+### 4. Build and Execute Tests
 
-Compiles and executes the full hermetic test suite:
+Compiles and executes the test suite:
 
 ```bash
-ninja -k 0 -C build-cov libc-hermetic-tests
+ninja -k 0 -C build-cov libc-tests
 ```
 
 :::{note}
 The `-k 0` flag ensures Ninja continues executing all remaining test targets
 even if an individual edge-case test encounters an error. To only compile test
 binaries without executing them, use
-`ninja -C build-cov libc-hermetic-tests-build`.
+`ninja -C build-cov libc-tests-build`.
 :::
 
 ### 5. Merge Profile Counters
@@ -295,13 +295,13 @@ export LLVM_PROFDATA="$PWD/build-clang/bin/llvm-profdata"
 export LLVM_COV="$PWD/build-clang/bin/llvm-cov"
 ```
 
-### 4. Build and Execute Hermetic Tests
+### 4. Build and Execute Tests
 
 Compiles and executes test executables in parallel with MC/DC instrumentation
 enabled:
 
 ```bash
-ninja -k 0 -C build-cov-mcdc libc-hermetic-tests
+ninja -k 0 -C build-cov-mcdc libc-tests
 ```
 
 ### 5. Merge Profile Counters
diff --git a/libc/docs/full_cross_build.md b/libc/docs/full_cross_build.md
index 3506609521b2a6..4783b203184eac 100644
--- a/libc/docs/full_cross_build.md
+++ b/libc/docs/full_cross_build.md
@@ -217,11 +217,11 @@ You can then build the libc using:
 ninja -C build libc
 ```
 
-To run the tests for the cross-compiled libc, you must use the hermetic test
-suite, which is entirely self-hosted.
+To run the tests for the cross-compiled libc, you can run `libc-tests`,
+which executes self-hosted tests in full-build mode:
 
 ```sh
-ninja -C build libc-hermetic-tests
+ninja -C build libc-tests
 ```
 
 :::{note}
diff --git a/libc/docs/gpu/testing.rst b/libc/docs/gpu/testing.rst
index c34467346b7987..2e5a87dfea23ac 100644
--- a/libc/docs/gpu/testing.rst
+++ b/libc/docs/gpu/testing.rst
@@ -108,11 +108,11 @@ The CMake target depends on how the library was built.
 
         $> ninja check-libc
 
-   #. Hermetic tests - You can run hermetic with tests the command:
+   #. Libc tests - You can run libc tests with the command:
 
       .. code-block:: sh
 
-        $> ninja libc-hermetic-tests
+        $> ninja libc-tests
 
    #. Integration tests - You can run integration tests by the command:
 
diff --git a/libc/test/CMakeLists.txt b/libc/test/CMakeLists.txt
index dd8db9a5166c0d..19fb6ea4a369ff 100644
--- a/libc/test/CMakeLists.txt
+++ b/libc/test/CMakeLists.txt
@@ -1,14 +1,11 @@
-add_custom_target(libc-unit-tests)
-add_custom_target(libc-hermetic-tests)
+add_custom_target(libc-tests)
 
 add_custom_target(exhaustive-check-libc)
 add_custom_target(libc-long-running-tests)
 
 # Build-only targets for lit (don't run tests, just build executables)
-add_custom_target(libc-unit-tests-build)
-add_custom_target(libc-hermetic-tests-build)
+add_custom_target(libc-tests-build)
 add_custom_target(libc-integration-tests-build)
-add_custom_target(libc_include_tests-build)
 
 # Configure the site config file for lit
 configure_lit_site_cfg(
@@ -37,8 +34,7 @@ if(TARGET check-hdrgen)
   add_dependencies(check-libc check-hdrgen)
 endif()
 
-add_dependencies(check-libc-build libc-unit-tests-build)
-add_dependencies(check-libc-build libc-hermetic-tests-build)
+add_dependencies(check-libc-build libc-tests-build)
 
 add_subdirectory(UnitTest)
 
diff --git a/libc/test/include/CMakeLists.txt b/libc/test/include/CMakeLists.txt
index b2fb07f9c3def0..a63e1638418467 100644
--- a/libc/test/include/CMakeLists.txt
+++ b/libc/test/include/CMakeLists.txt
@@ -1,5 +1,4 @@
-add_custom_target(libc_include_tests)
-add_dependencies(check-libc libc_include_tests-build)
+add_libc_testsuite(libc_include_tests)
 
 add_libc_test(
   alloca_test
diff --git a/libc/test/shared/CMakeLists.txt b/libc/test/shared/CMakeLists.txt
index 74eda7cdaf065e..6a4a0b68b2c926 100644
--- a/libc/test/shared/CMakeLists.txt
+++ b/libc/test/shared/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_custom_target(libc-shared-tests)
+add_libc_testsuite(libc-shared-tests)
 
 # Shared math tests include the entire math library headers into a single file
 # so it might take some time to compile the tests.  We do have dedicated
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index b4120fa5fe268b..b3ca7a0d366374 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -1,80 +1,3 @@
-function(add_fp_unittest name)
-  cmake_parse_arguments(
-    "MATH_UNITTEST"
-    "NEED_MPFR;NEED_MPFR_F80;NEED_MPFR_F128;NEED_MPC;FULL_BUILD_ONLY;OVERLAY_BUILD_ONLY" # Optional arguments
-    "" # Single value arguments
-    "LINK_LIBRARIES;DEPENDS" # Multi-value arguments
-    ${ARGN}
-  )
-
-  if(MATH_UNITTEST_NEED_MPC)
-    set(MATH_UNITTEST_NEED_MPFR TRUE)
-    if(NOT LIBC_TESTS_CAN_USE_MPC)
-      message(VERBOSE "Complex test ${name} will be skipped as MPC library is not available.")
-      return()
-    endif()
-    list(APPEND MATH_UNITTEST_LINK_LIBRARIES libcMPCWrapper)
-  endif()
-
-  if(MATH_UNITTEST_NEED_MPFR_F80)
-    set(MATH_UNITTEST_NEED_MPFR TRUE)
-    if(NOT LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
-      message(VERBOSE "Math test ${name} will be skipped as float80 support in MPFR is not available.")
-      return()
-    endif()
-  endif()
-
-  # TODO: To be removed when we find a workaround to run MPFR tests for
-  # emulated type where compiler doesn't support the native float128
-  # TODO: Check mpfr_buildopt_float128_p() returns non-zero.
-  if(MATH_UNITTEST_NEED_MPFR_F128)
-    set(MATH_UNITTEST_NEED_MPFR TRUE)
-    if(NOT LIBC_TYPES_HAS_NATIVE_FLOAT128)
-      message(VERBOSE "Math test ${name} will be skipped as native float128 support in MPFR is not available.")
-      return()
-    endif()
-  endif()
-
-  if(MATH_UNITTEST_NEED_MPFR)
-    if(NOT LIBC_TESTS_CAN_USE_MPFR)
-      message(VERBOSE "Math test ${name} will be skipped as MPFR library is not available.")
-      return()
-    endif()
-  endif()
-
-  if(MATH_UNITTEST_FULL_BUILD_ONLY)
-    set(test_type FULL_BUILD_ONLY)
-  elseif(MATH_UNITTEST_OVERLAY_BUILD_ONLY)
-    set(test_type OVERLAY_BUILD_ONLY)
-  endif()
-  if(MATH_UNITTEST_NEED_MPFR)
-    if(MATH_UNITTEST_FULL_BUILD_ONLY)
-      message(FATAL_ERROR "Full-build math test cannot require MPFR.")
-    endif()
-    set(test_type OVERLAY_BUILD_ONLY)
-    list(APPEND MATH_UNITTEST_LINK_LIBRARIES libcMPFRWrapper -lmpfr -lgmp)
-    if(NOT(LIBC_TARGET_OS_IS_DARWIN) AND NOT(LIBC_TARGET_OS_IS_FREEBSD))
-      # macOS/FreeBSD does not have libatomic.
-      list(APPEND MATH_UNITTEST_LINK_LIBRARIES -latomic)
-    endif()
-  endif()
-
-  set(deps libc.hdr.math_macros libc.test.UnitTest.FPTestHelpers)
-  if(MATH_UNITTEST_DEPENDS)
-    list(APPEND deps ${MATH_UNITTEST_DEPENDS})
-  endif()
-
-  add_libc_test(
-    ${name}
-    ${test_type}
-    LINK_LIBRARIES "${MATH_UNITTEST_LINK_LIBRARIES}"
-      "${MATH_UNITTEST_UNPARSED_ARGUMENTS}"
-    DEPENDS
-      libc.hdr.stdint_proxy
-      "${deps}"
-  )
-endfunction()
-
 add_subdirectory(__support)
 add_subdirectory(arpa)
 add_subdirectory(complex)
diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 765eec6b421780..6f97334657158a 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -308,8 +308,9 @@ if(LIBC_TARGET_OS_IS_BAREMETAL AND
       minimal_external_crt1_for_test.cpp
   )
 
-  add_libc_hermetic(
+  add_libc_test(
     external_startup_link_test
+    FULL_BUILD_ONLY
     NO_RUN_POSTBUILD
     STARTUP
       .minimal_external_crt1_for_test
diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt
index bb3e568c2d4c9f..0478477eead1d8 100644
--- a/libc/test/src/math/CMakeLists.txt
+++ b/libc/test/src/math/CMakeLists.txt
@@ -1,4 +1,4 @@
-add_custom_target(libc-math-unittests)
+add_libc_testsuite(libc-math-unittests)
 
 add_fp_unittest(
   cosf_test
@@ -3926,6 +3926,9 @@ add_fp_unittest(
 
 add_subdirectory(smoke)
 
+add_custom_target(libc-math-all-tests)
+add_dependencies(libc-math-all-tests libc-math-smoke-tests libc-math-unittests)
+
 if(NOT LLVM_LIBC_FULL_BUILD)
   add_subdirectory(exhaustive)
   add_subdirectory(performance_testing)
diff --git a/libc/test/src/math/smoke/CMakeLists.txt b/libc/test/src/math/smoke/CMakeLists.txt
index a1f85df34e4c7d..661027844a3711 100644
--- a/libc/test/src/math/smoke/CMakeLists.txt
+++ b/libc/test/src/math/smoke/CMakeLists.txt
@@ -1,5 +1,4 @@
-add_custom_target(libc-math-smoke-tests)
-add_dependencies(libc-math-unittests libc-math-smoke-tests)
+add_libc_testsuite(libc-math-smoke-tests)
 
 add_fp_unittest(
   cosf_test
diff --git a/libc/utils/MPCWrapper/CMakeLists.txt b/libc/utils/MPCWrapper/CMakeLists.txt
index d06ec2b3664a53..321e72bbcce547 100644
--- a/libc/utils/MPCWrapper/CMakeLists.txt
+++ b/libc/utils/MPCWrapper/CMakeLists.txt
@@ -1,15 +1,15 @@
 if(LIBC_TESTS_CAN_USE_MPC)
-  add_library(libcMPCWrapper STATIC
+  add_library(libc.utils.MPCWrapper STATIC
     MPCUtils.cpp
     MPCUtils.h
     mpc_inc.h
   )
   _get_common_test_compile_options(compile_options "" "")
   list(REMOVE_ITEM compile_options "-ffreestanding")
-  target_compile_options(libcMPCWrapper PRIVATE -O3 ${compile_options})
+  target_compile_options(libc.utils.MPCWrapper PRIVATE -O3 ${compile_options})
   add_dependencies(
-    libcMPCWrapper
-    libcMPCommon
+    libc.utils.MPCWrapper
+    libc.utils.MPCommon
     libc.hdr.stdint_proxy
     libc.src.__support.CPP.array
     libc.src.__support.CPP.string
@@ -21,15 +21,15 @@ if(LIBC_TESTS_CAN_USE_MPC)
     libc.test.UnitTest.LibcTest
   )
   if(EXISTS ${LLVM_LIBC_MPFR_INSTALL_PATH})
-    target_include_directories(libcMPCWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/include)
-    target_link_directories(libcMPCWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/lib)
+    target_include_directories(libc.utils.MPCWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/include)
+    target_link_directories(libc.utils.MPCWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/lib)
   endif()
   if(EXISTS ${LIBC_MPC_INSTALL_PATH})
-    target_include_directories(libcMPCWrapper PUBLIC ${LIBC_MPC_INSTALL_PATH}/include)
-    target_link_directories(libcMPCWrapper PUBLIC ${LIBC_MPC_INSTALL_PATH}/lib)
+    target_include_directories(libc.utils.MPCWrapper PUBLIC ${LIBC_MPC_INSTALL_PATH}/include)
+    target_link_directories(libc.utils.MPCWrapper PUBLIC ${LIBC_MPC_INSTALL_PATH}/lib)
   endif()
-  target_include_directories(libcMPCWrapper PUBLIC ${LIBC_SOURCE_DIR})
-  target_link_libraries(libcMPCWrapper PUBLIC libcMPCommon mpc)
+  target_include_directories(libc.utils.MPCWrapper PUBLIC ${LIBC_SOURCE_DIR})
+  target_link_libraries(libc.utils.MPCWrapper PUBLIC libc.utils.MPCommon mpc)
 elseif(NOT LIBC_TARGET_OS_IS_GPU AND NOT LLVM_LIBC_FULL_BUILD)
   message(WARNING "Math tests using MPC will be skipped.")
 endif()
diff --git a/libc/utils/MPFRWrapper/CMakeLists.txt b/libc/utils/MPFRWrapper/CMakeLists.txt
index 53ad5ef4aa48cd..f4bc87c822a002 100644
--- a/libc/utils/MPFRWrapper/CMakeLists.txt
+++ b/libc/utils/MPFRWrapper/CMakeLists.txt
@@ -1,5 +1,5 @@
 if(LIBC_TESTS_CAN_USE_MPFR OR LIBC_TESTS_CAN_USE_MPC)
-  add_library(libcMPCommon STATIC
+  add_library(libc.utils.MPCommon STATIC
     MPCommon.cpp
     MPCommon.h
     mpfr_inc.h
@@ -7,9 +7,9 @@ if(LIBC_TESTS_CAN_USE_MPFR OR LIBC_TESTS_CAN_USE_MPC)
   _get_common_test_compile_options(compile_options "" "")
   # mpfr/gmp headers do not work with -ffreestanding flag.
   list(REMOVE_ITEM compile_options "-ffreestanding")
-  target_compile_options(libcMPCommon PRIVATE -O3 ${compile_options})
+  target_compile_options(libc.utils.MPCommon PRIVATE -O3 ${compile_options})
   add_dependencies(
-    libcMPCommon
+    libc.utils.MPCommon
     libc.hdr.stdint_proxy
     libc.src.__support.CPP.string
     libc.src.__support.CPP.string_view
@@ -21,27 +21,30 @@ if(LIBC_TESTS_CAN_USE_MPFR OR LIBC_TESTS_CAN_USE_MPC)
     libc.test.UnitTest.FPTestHelpers
   )
   if(EXISTS ${LLVM_LIBC_MPFR_INSTALL_PATH})
-    target_include_directories(libcMPCommon PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/include)
-    target_link_directories(libcMPCommon PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/lib)
+    target_include_directories(libc.utils.MPCommon PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/include)
+    target_link_directories(libc.utils.MPCommon PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/lib)
+  endif()
+  target_include_directories(libc.utils.MPCommon PUBLIC ${LIBC_SOURCE_DIR})
+  target_link_libraries(libc.utils.MPCommon PUBLIC mpfr gmp)
+  if(NOT LIBC_TARGET_OS_IS_DARWIN AND NOT LIBC_TARGET_OS_IS_FREEBSD)
+    target_link_libraries(libc.utils.MPCommon PUBLIC -latomic)
   endif()
-  target_include_directories(libcMPCommon PUBLIC ${LIBC_SOURCE_DIR})
-  target_link_libraries(libcMPCommon PUBLIC mpfr gmp)
 elseif(NOT LIBC_TARGET_OS_IS_GPU AND NOT LLVM_LIBC_FULL_BUILD)
   message(WARNING "Math tests using MPFR will be skipped.")
 endif()
 
 if(LIBC_TESTS_CAN_USE_MPFR)
-  add_library(libcMPFRWrapper STATIC
+  add_library(libc.utils.MPFRWrapper STATIC
     MPFRUtils.cpp
     MPFRUtils.h
   )
   _get_common_test_compile_options(compile_options "" "")
   # mpfr/gmp headers do not work with -ffreestanding flag.
   list(REMOVE_ITEM compile_options "-ffreestanding")
-  target_compile_options(libcMPFRWrapper PRIVATE ${libc_opt_high_flag} ${compile_options})
+  target_compile_options(libc.utils.MPFRWrapper PRIVATE ${libc_opt_high_flag} ${compile_options})
   add_dependencies(
-    libcMPFRWrapper
-    libcMPCommon
+    libc.utils.MPFRWrapper
+    libc.utils.MPCommon
     libc.hdr.stdint_proxy
     libc.src.__support.CPP.array
     libc.src.__support.CPP.stringstream
@@ -52,11 +55,11 @@ if(LIBC_TESTS_CAN_USE_MPFR)
     libc.test.UnitTest.LibcTest
   )
   if(EXISTS ${LLVM_LIBC_MPFR_INSTALL_PATH})
-    target_include_directories(libcMPFRWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/include)
-    target_link_directories(libcMPFRWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/lib)
+    target_include_directories(libc.utils.MPFRWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/include)
+    target_link_directories(libc.utils.MPFRWrapper PUBLIC ${LLVM_LIBC_MPFR_INSTALL_PATH}/lib)
   endif()
-  target_include_directories(libcMPFRWrapper PUBLIC ${LIBC_SOURCE_DIR})
-  target_link_libraries(libcMPFRWrapper PUBLIC libcMPCommon)
+  target_include_directories(libc.utils.MPFRWrapper PUBLIC ${LIBC_SOURCE_DIR})
+  target_link_libraries(libc.utils.MPFRWrapper PUBLIC libc.utils.MPCommon)
 elseif(NOT LIBC_TARGET_OS_IS_GPU AND NOT LLVM_LIBC_FULL_BUILD)
   message(WARNING "Math tests using MPFR will be skipped.")
 endif()

>From 34bb92f1eded272b93f62d4e34dc5c4bb7439113 Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Mon, 19 Oct 2026 17:55:08 +0000
Subject: [PATCH 2/3] Do not pass TEST_COMPILE_OPTIONS to linker in overlay
 tests.

---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index e6736153256fb2..b02c45f69a4578 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -782,8 +782,7 @@ function(create_libc_test fq_target_name)
     _get_common_test_compile_options(compile_options "${TEST_C_TEST}"
       "${TEST_FLAGS}")
   endif()
-  list(APPEND compile_options ${TEST_COMPILE_OPTIONS})
-  target_compile_options(${fq_build_target_name} PRIVATE ${compile_options})
+  target_compile_options(${fq_build_target_name} PRIVATE ${compile_options} ${TEST_COMPILE_OPTIONS})
 
   if(NOT TEST_CXX_STANDARD)
     set(TEST_CXX_STANDARD ${CMAKE_CXX_STANDARD})

>From 6dcb48f4cff7136c4e2d4dbfda6fe94e1de27c2f Mon Sep 17 00:00:00 2001
From: Tue Ly <lntue.h at gmail.com>
Date: Tue, 20 Oct 2026 22:33:43 +0000
Subject: [PATCH 3/3] [libc][cmake] Move FP/MPFR/MPC handling into
 create_libc_test and simplify suite checks

---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 184 +++++++--------------
 1 file changed, 64 insertions(+), 120 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index b02c45f69a4578..bc3adc79644ec9 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -324,19 +324,10 @@ function(add_libc_testsuite suite_name)
     add_custom_target(${suite_name})
   endif()
 
-  set(created_build_target FALSE)
   if(NOT TARGET ${suite_name}-build)
     add_custom_target(${suite_name}-build)
-    set(created_build_target TRUE)
-  endif()
-
-  if(created_build_target)
-    if(TARGET libc-tests)
-      add_dependencies(libc-tests ${suite_name})
-    endif()
-    if(TARGET libc-tests-build)
-      add_dependencies(libc-tests-build ${suite_name}-build)
-    endif()
+    add_dependencies(libc-tests ${suite_name})
+    add_dependencies(libc-tests-build ${suite_name}-build)
   endif()
 endfunction()
 
@@ -398,7 +389,7 @@ endfunction()
 # Helper function for create_libc_test: collects fully qualified dependencies
 # for a test target, including base test framework libraries, hermetic runtime
 # entrypoints, subprocess support, and coverage dependencies.
-function(_get_libc_test_dependencies output_var user_depends startup_target_dep is_c_test)
+function(_get_libc_test_dependencies output_var user_depends startup_target_dep is_c_test is_fp_test)
   get_fq_deps_list(fq_deps_list ${user_depends})
   list(APPEND fq_deps_list
     libc.src.__support.StringUtil.error_to_string
@@ -410,6 +401,13 @@ function(_get_libc_test_dependencies output_var user_depends startup_target_dep
   else()
     list(APPEND fq_deps_list libc.test.UnitTest.LibcDeathTestExecutors)
   endif()
+  if(is_fp_test)
+    list(APPEND fq_deps_list
+      libc.hdr.math_macros
+      libc.hdr.stdint_proxy
+      libc.test.UnitTest.FPTestHelpers
+    )
+  endif()
 
   if(LLVM_LIBC_FULL_BUILD)
     list(APPEND fq_deps_list
@@ -464,30 +462,6 @@ function(_get_libc_test_dependencies output_var user_depends startup_target_dep
   set(${output_var} ${fq_deps_list} PARENT_SCOPE)
 endfunction()
 
-# Helper function for create_libc_test: inspects dependencies and extracts
-# standard CMake library targets (STATIC, SHARED, INTERFACE) that do not have
-# a custom TARGET_TYPE (e.g. libc.utils.MPFRWrapper, libc.utils.MPCWrapper).
-function(_get_libc_test_link_libraries output_var fq_deps_list)
-  set(link_libs "")
-  foreach(dep IN LISTS fq_deps_list)
-    if(TARGET ${dep})
-      get_target_property(aliased_target ${dep} "ALIASED_TARGET")
-      if(aliased_target)
-        set(dep ${aliased_target})
-      endif()
-      get_target_property(dep_type ${dep} "TARGET_TYPE")
-      get_target_property(cmake_type ${dep} TYPE)
-      if(NOT dep_type AND cmake_type MATCHES ".*_LIBRARY")
-        list(APPEND link_libs ${dep})
-      endif()
-    endif()
-  endforeach()
-  if(link_libs)
-    list(REMOVE_DUPLICATES link_libs)
-  endif()
-  set(${output_var} ${link_libs} PARENT_SCOPE)
-endfunction()
-
 # Helper function for create_libc_test: configures linking for full-build hermetic
 # tests by creating the static .__libc__ archive, setting hermetic/GPU link
 # options, and wiring link libraries and compiler runtime.
@@ -661,16 +635,10 @@ function(_register_libc_test_with_suite fq_target_name fq_build_target_name suit
   if(suite)
     add_libc_testsuite(${suite})
     add_dependencies(${suite} ${fq_target_name})
-    if(TARGET ${suite}-build)
-      add_dependencies(${suite}-build ${fq_build_target_name})
-    endif()
+    add_dependencies(${suite}-build ${fq_build_target_name})
   else()
-    if(TARGET libc-tests)
-      add_dependencies(libc-tests ${fq_target_name})
-    endif()
-    if(TARGET libc-tests-build)
-      add_dependencies(libc-tests-build ${fq_build_target_name})
-    endif()
+    add_dependencies(libc-tests ${fq_target_name})
+    add_dependencies(libc-tests-build ${fq_build_target_name})
   endif()
 endfunction()
 
@@ -682,7 +650,47 @@ function(create_libc_test fq_target_name)
 
   llvm_libc_parse_arguments(TEST ${ARGN})
 
-  # 1. Validate build mode and target platform
+  # 1. Validate build mode, target platform, and external library requirements
+  if(TEST_NEED_MPC)
+    set(TEST_NEED_MPFR TRUE)
+    if(NOT LIBC_TESTS_CAN_USE_MPC)
+      message(VERBOSE "Complex test ${fq_target_name} will be skipped as MPC library is not available.")
+      return()
+    endif()
+    list(APPEND TEST_LINK_LIBRARIES libc.utils.MPCWrapper)
+  endif()
+
+  if(TEST_NEED_MPFR_F80)
+    set(TEST_NEED_MPFR TRUE)
+    if(NOT LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
+      message(VERBOSE "Math test ${fq_target_name} will be skipped as float80 support in MPFR is not available.")
+      return()
+    endif()
+  endif()
+
+  # TODO: To be removed when we find a workaround to run MPFR tests for
+  # emulated type where compiler doesn't support the native float128
+  # TODO: Check mpfr_buildopt_float128_p() returns non-zero.
+  if(TEST_NEED_MPFR_F128)
+    set(TEST_NEED_MPFR TRUE)
+    if(NOT LIBC_TYPES_HAS_NATIVE_FLOAT128)
+      message(VERBOSE "Math test ${fq_target_name} will be skipped as native float128 support in MPFR is not available.")
+      return()
+    endif()
+  endif()
+
+  if(TEST_NEED_MPFR)
+    if(TEST_FULL_BUILD_ONLY)
+      message(FATAL_ERROR "Full-build test cannot require MPFR.")
+    endif()
+    if(NOT LIBC_TESTS_CAN_USE_MPFR)
+      message(VERBOSE "Test ${fq_target_name} will be skipped as MPFR library is not available.")
+      return()
+    endif()
+    set(TEST_OVERLAY_ONLY TRUE)
+    list(APPEND TEST_LINK_LIBRARIES libc.utils.MPFRWrapper)
+  endif()
+
   if(LLVM_LIBC_FULL_BUILD AND TEST_OVERLAY_ONLY)
     return()
   endif()
@@ -727,7 +735,13 @@ function(create_libc_test fq_target_name)
   endif()
 
   # 2. Resolve dependencies and link objects
-  _get_libc_test_dependencies(fq_deps_list "${TEST_DEPENDS}" "${startup_target_dep}" "${TEST_C_TEST}")
+  _get_libc_test_dependencies(
+    fq_deps_list
+    "${TEST_DEPENDS}"
+    "${startup_target_dep}"
+    "${TEST_C_TEST}"
+    "${TEST_FP_TEST}"
+  )
 
   if(SHOW_INTERMEDIATE_OBJECTS)
     message(STATUS "Adding test ${fq_target_name}")
@@ -749,10 +763,6 @@ function(create_libc_test fq_target_name)
     return()
   endif()
   list(REMOVE_DUPLICATES link_object_files)
-
-  # Collect standard library targets from dependencies that do not provide raw object files.
-  _get_libc_test_link_libraries(dep_link_libraries "${fq_deps_list}")
-  list(APPEND TEST_LINK_LIBRARIES ${dep_link_libraries})
   if(TEST_LINK_LIBRARIES)
     list(REMOVE_DUPLICATES TEST_LINK_LIBRARIES)
   endif()
@@ -838,14 +848,6 @@ function(create_libc_test fq_target_name)
   endif()
 endfunction()
 
-function(_add_libc_test target_name)
-  add_target_with_flags(
-    ${target_name}
-    CREATE_TARGET create_libc_test
-    ${ARGN}
-  )
-endfunction()
-
 
 # Rule to add a fuzzer test.
 # Usage
@@ -1152,68 +1154,10 @@ endfunction()
 # NEED_MPFR / NEED_MPFR_F80 / NEED_MPFR_F128: link against MPFR; tests requiring MPFR run only in overlay mode.
 # NEED_MPC: link against MPC and MPFR; tests requiring MPC run only in overlay mode.
 function(add_libc_test test_name)
-  llvm_libc_parse_arguments(LIBC_TEST ${ARGN})
-
-  if(LIBC_TEST_NEED_MPC)
-    set(LIBC_TEST_NEED_MPFR TRUE)
-    if(NOT LIBC_TESTS_CAN_USE_MPC)
-      message(VERBOSE "Complex test ${test_name} will be skipped as MPC library is not available.")
-      return()
-    endif()
-    list(APPEND LIBC_TEST_DEPENDS libc.utils.MPCWrapper)
-  endif()
-
-  if(LIBC_TEST_NEED_MPFR_F80)
-    set(LIBC_TEST_NEED_MPFR TRUE)
-    if(NOT LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80)
-      message(VERBOSE "Math test ${test_name} will be skipped as float80 support in MPFR is not available.")
-      return()
-    endif()
-  endif()
-
-  # TODO: To be removed when we find a workaround to run MPFR tests for
-  # emulated type where compiler doesn't support the native float128
-  # TODO: Check mpfr_buildopt_float128_p() returns non-zero.
-  if(LIBC_TEST_NEED_MPFR_F128)
-    set(LIBC_TEST_NEED_MPFR TRUE)
-    if(NOT LIBC_TYPES_HAS_NATIVE_FLOAT128)
-      message(VERBOSE "Math test ${test_name} will be skipped as native float128 support in MPFR is not available.")
-      return()
-    endif()
-  endif()
-
-  if(LIBC_TEST_NEED_MPFR)
-    if(LIBC_TEST_FULL_BUILD_ONLY)
-      message(FATAL_ERROR "Full-build test cannot require MPFR.")
-    endif()
-    if(NOT LIBC_TESTS_CAN_USE_MPFR)
-      message(VERBOSE "Test ${test_name} will be skipped as MPFR library is not available.")
-      return()
-    endif()
-    set(LIBC_TEST_OVERLAY_ONLY TRUE)
-    list(APPEND LIBC_TEST_DEPENDS libc.utils.MPFRWrapper)
-  endif()
-
-  if(LIBC_TEST_FP_TEST)
-    list(APPEND LIBC_TEST_DEPENDS
-      libc.hdr.math_macros
-      libc.test.UnitTest.FPTestHelpers
-      libc.hdr.stdint_proxy
-    )
-  endif()
-
-  if(LLVM_LIBC_FULL_BUILD AND LIBC_TEST_OVERLAY_ONLY)
-    return()
-  endif()
-  if(NOT LLVM_LIBC_FULL_BUILD AND LIBC_TEST_FULL_BUILD_ONLY)
-    return()
-  endif()
-
-  forward_arguments(LIBC_TEST forwarded_args)
-  _add_libc_test(
+  add_target_with_flags(
     ${test_name}
-    ${forwarded_args}
-    ${LIBC_TEST_UNPARSED_ARGUMENTS}
+    CREATE_TARGET create_libc_test
+    ${ARGN}
   )
 endfunction()
 



More information about the libc-commits mailing list