[libc-commits] [libc] c776a52 - [libc] Implement lit-based test execution for Libc (#178746)
via libc-commits
libc-commits at lists.llvm.org
Fri Feb 13 10:39:59 PST 2026
Author: Jeff Bailey
Date: 2026-02-13T18:39:54Z
New Revision: c776a52fa263999d4b14b6d7ce734384ef1c090d
URL: https://github.com/llvm/llvm-project/commit/c776a52fa263999d4b14b6d7ce734384ef1c090d
DIFF: https://github.com/llvm/llvm-project/commit/c776a52fa263999d4b14b6d7ce734384ef1c090d.diff
LOG: [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)
Added:
libc/test/lit.cfg.py
libc/test/lit.site.cfg.py.in
libc/utils/libctest/__init__.py
libc/utils/libctest/format.py
Modified:
libc/cmake/modules/LLVMLibCTestRules.cmake
libc/test/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index ba64933708760..e59f5447c5a0f 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -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)
diff --git a/libc/test/CMakeLists.txt b/libc/test/CMakeLists.txt
index 011ad6aeb34b7..88663367feb04 100644
--- a/libc/test/CMakeLists.txt
+++ b/libc/test/CMakeLists.txt
@@ -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)
diff --git a/libc/test/lit.cfg.py b/libc/test/lit.cfg.py
new file mode 100644
index 0000000000000..9791a24f9a56e
--- /dev/null
+++ b/libc/test/lit.cfg.py
@@ -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."
+)
diff --git a/libc/test/lit.site.cfg.py.in b/libc/test/lit.site.cfg.py.in
new file mode 100644
index 0000000000000..87a5649e4ba6a
--- /dev/null
+++ b/libc/test/lit.site.cfg.py.in
@@ -0,0 +1,37 @@
+ at 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", "")]
+ )
+
diff --git a/libc/utils/libctest/__init__.py b/libc/utils/libctest/__init__.py
new file mode 100644
index 0000000000000..9472aa94e7f30
--- /dev/null
+++ b/libc/utils/libctest/__init__.py
@@ -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"]
diff --git a/libc/utils/libctest/format.py b/libc/utils/libctest/format.py
new file mode 100644
index 0000000000000..df7d402799762
--- /dev/null
+++ b/libc/utils/libctest/format.py
@@ -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
More information about the libc-commits
mailing list