[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:
@@ -344,6 +344,10 @@ function(create_libc_unittest fq_target_name)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
add_dependencies(libc-unit-tests ${fq_target_name})
|
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)
|
endfunction(create_libc_unittest)
|
||||||
|
|
||||||
function(add_libc_unittest target_name)
|
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
|
# If it is a benchmark, it will already have been added to the
|
||||||
# gpu-benchmark target
|
# gpu-benchmark target
|
||||||
add_dependencies(libc-hermetic-tests ${fq_target_name})
|
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()
|
endif()
|
||||||
endfunction(add_libc_hermetic)
|
endfunction(add_libc_hermetic)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,31 @@ add_dependencies(check-libc libc-unit-tests libc-hermetic-tests)
|
|||||||
add_custom_target(exhaustive-check-libc)
|
add_custom_target(exhaustive-check-libc)
|
||||||
add_custom_target(libc-long-running-tests)
|
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)
|
add_subdirectory(UnitTest)
|
||||||
|
|
||||||
if(LIBC_TARGET_OS_IS_GPU)
|
if(LIBC_TARGET_OS_IS_GPU)
|
||||||
|
|||||||
17
libc/test/lit.cfg.py
Normal file
17
libc/test/lit.cfg.py
Normal 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."
|
||||||
|
)
|
||||||
37
libc/test/lit.site.cfg.py.in
Normal file
37
libc/test/lit.site.cfg.py.in
Normal 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", "")]
|
||||||
|
)
|
||||||
|
|
||||||
21
libc/utils/libctest/__init__.py
Normal file
21
libc/utils/libctest/__init__.py
Normal 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"]
|
||||||
106
libc/utils/libctest/format.py
Normal file
106
libc/utils/libctest/format.py
Normal 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
|
||||||
Reference in New Issue
Block a user