[libc] Implement lit-based test execution for Libc (#178746)

This provides optional lit-based test execution for the LLVM Libc tests,
alongside the existing CMake-based test execution.

Usage:
  ninja -C build check-libc-lit
  cd build && bin/llvm-lit libc/test/src/

Partially addresses
[#118694](https://github.com/llvm/llvm-project/issues/118694). A future
PR once this lands will flip the default (per suggestion in the RFC)
This commit is contained in:
Jeff Bailey
2026-02-13 18:39:54 +00:00
committed by GitHub
parent 4d518e6c7a
commit c776a52fa2
6 changed files with 214 additions and 0 deletions

View File

@@ -344,6 +344,10 @@ function(create_libc_unittest fq_target_name)
)
endif()
add_dependencies(libc-unit-tests ${fq_target_name})
# Also add dependency to build-only target for lit
if(TARGET libc-unit-tests-build)
add_dependencies(libc-unit-tests-build ${fq_build_target_name})
endif()
endfunction(create_libc_unittest)
function(add_libc_unittest target_name)
@@ -883,6 +887,10 @@ function(add_libc_hermetic test_name)
# If it is a benchmark, it will already have been added to the
# gpu-benchmark target
add_dependencies(libc-hermetic-tests ${fq_target_name})
# Also add dependency to build-only target for lit
if(TARGET libc-hermetic-tests-build)
add_dependencies(libc-hermetic-tests-build ${fq_build_target_name})
endif()
endif()
endfunction(add_libc_hermetic)

View File

@@ -6,6 +6,31 @@ add_dependencies(check-libc libc-unit-tests libc-hermetic-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)
# Configure the site config file for lit
configure_lit_site_cfg(
${LIBC_SOURCE_DIR}/test/lit.site.cfg.py.in
${LIBC_BUILD_DIR}/test/lit.site.cfg.py
MAIN_CONFIG
${LIBC_SOURCE_DIR}/test/lit.cfg.py
PATHS
"LLVM_SOURCE_DIR"
"LLVM_BINARY_DIR"
"LLVM_TOOLS_DIR"
"LLVM_LIBS_DIR"
"LIBC_SOURCE_DIR"
"LIBC_BUILD_DIR"
)
add_lit_testsuite(check-libc-lit
"Running libc tests via lit"
${LIBC_BUILD_DIR}/test
DEPENDS libc-unit-tests-build libc-hermetic-tests-build
)
add_subdirectory(UnitTest)
if(LIBC_TARGET_OS_IS_GPU)

17
libc/test/lit.cfg.py Normal file
View File

@@ -0,0 +1,17 @@
# -*- Python -*-
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# All the Lit configuration is handled in the site config -- this file is only
# left as a canary to catch invocations of Lit that do not go through llvm-lit.
#
# Invocations that go through llvm-lit will automatically use the right Lit
# site configuration inside the build directory.
lit_config.fatal(
"You seem to be running Lit directly -- you should be running Lit through "
"<build>/bin/llvm-lit, which will ensure that the right Lit configuration "
"file is used."
)

View File

@@ -0,0 +1,37 @@
@LIT_SITE_CFG_IN_HEADER@
import os
import site
# Configuration values from CMake
config.llvm_tools_dir = lit_config.substitute(path(r"@LLVM_TOOLS_DIR@"))
config.libc_src_root = path(r"@LIBC_SOURCE_DIR@")
config.libc_obj_root = path(r"@LIBC_BUILD_DIR@")
config.libc_test_cmd = "@LIBC_TEST_CMD@"
# Add libc's utils directory to the path so we can import the test format.
site.addsitedir(os.path.join(config.libc_src_root, "utils"))
import libctest
# name: The name of this test suite.
config.name = "libc"
# testFormat: Use libc's custom test format that discovers pre-built
# test executables (Libc*Tests) in the build directory.
config.test_format = libctest.LibcTest()
# excludes: A list of directories to exclude from the testsuite.
config.excludes = ["Inputs", "CMakeLists.txt", "README.txt", "LICENSE.txt", "UnitTest"]
# test_source_root: The root path where tests are located.
# test_exec_root: The root path where test executables are built.
# Set both to the build directory so ExecutableTest finds executables correctly.
config.test_exec_root = os.path.join(config.libc_obj_root, "test")
config.test_source_root = config.test_exec_root
# Add tool directories to PATH (in case we add FileCheck tests later).
if hasattr(config, "llvm_tools_dir") and config.llvm_tools_dir:
config.environment["PATH"] = os.path.pathsep.join(
[config.llvm_tools_dir, config.environment.get("PATH", "")]
)

View File

@@ -0,0 +1,21 @@
# ===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===----------------------------------------------------------------------===##
"""
Lit test format for LLVM libc unit tests.
This format extends lit.formats.ExecutableTest to discover pre-built test
executables in the build directory. Test executables are expected to follow
the naming pattern used by add_libc_test():
libc.test.src.<category>.<test_name>.__unit__.__build__
libc.test.src.<category>.<test_name>.__hermetic__.__build__
"""
from .format import LibcTest
__all__ = ["LibcTest"]

View File

@@ -0,0 +1,106 @@
# ===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===----------------------------------------------------------------------===##
"""
Lit test format for LLVM libc tests.
This format discovers pre-built test executables in the build directory
and runs them. It extends lit's ExecutableTest format.
The lit config sets test_source_root == test_exec_root (both to the build
directory), following the pattern used by llvm/test/Unit/lit.cfg.py.
Test executables are discovered by looking for files matching:
libc.test.src.<category>.<test_name>.__unit__.__build__
libc.test.src.<category>.<test_name>.__hermetic__.__build__
These are created by the add_libc_test() infrastructure.
"""
import os
import shlex
import lit.formats
import lit.Test
import lit.util
class LibcTest(lit.formats.ExecutableTest):
"""
Test format for libc unit tests.
Extends ExecutableTest to discover tests from the build directory
rather than the source directory. Test executables are named like:
libc.test.src.ctype.isalnum_test.__unit__.__build__
and return 0 on success.
"""
def getTestsInDirectory(self, testSuite, path_in_suite, litConfig, localConfig):
"""
Discover test executables in the build directory.
Since test_source_root == test_exec_root (both point to build dir),
we use getSourcePath() to find test executables.
"""
source_path = testSuite.getSourcePath(path_in_suite)
# Look for test executables in the build directory
if not os.path.isdir(source_path):
return
# Sort for deterministic test discovery/output ordering.
for filename in sorted(os.listdir(source_path)):
filepath = os.path.join(source_path, filename)
# Match our test executable pattern
if self._isTestExecutable(filename, filepath):
# Create a test with the executable name
yield lit.Test.Test(testSuite, path_in_suite + (filename,), localConfig)
def _isTestExecutable(self, filename, filepath):
"""Check if a file is a test executable we should run."""
# Pattern: libc.test.src.*.__unit__.__build__ or .__hermetic__.__build__
if not filename.startswith("libc.test."):
return False
if not (
filename.endswith(".__unit__.__build__")
or filename.endswith(".__hermetic__.__build__")
):
return False
# Must be executable
if not os.path.isfile(filepath):
return False
if not os.access(filepath, os.X_OK):
return False
return True
def execute(self, test, litConfig):
"""
Execute a test by running the test executable.
Runs from the executable's directory so relative paths (like
testdata/test.txt) work correctly.
"""
test_path = test.getSourcePath()
exec_dir = os.path.dirname(test_path)
test_cmd_template = getattr(test.config, "libc_test_cmd", "")
if test_cmd_template:
test_cmd = test_cmd_template.replace("@BINARY@", test_path)
cmd_args = shlex.split(test_cmd)
if not cmd_args:
cmd_args = [test_path]
out, err, exit_code = lit.util.executeCommand(cmd_args, cwd=exec_dir)
else:
out, err, exit_code = lit.util.executeCommand([test_path], cwd=exec_dir)
if not exit_code:
return lit.Test.PASS, ""
return lit.Test.FAIL, out + err