[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