[libc-commits] [libc] [llvm] [libc][uefi] add testing (PR #147235)
Tristan Ross via libc-commits
libc-commits at lists.llvm.org
Sun Jul 6 22:44:46 PDT 2025
https://github.com/RossComputerGuy updated https://github.com/llvm/llvm-project/pull/147235
>From bdcd574e6bdf50d2031277a16e28fb2ecab9ef7d Mon Sep 17 00:00:00 2001
From: Tristan Ross <tristan.ross at midstall.com>
Date: Sun, 6 Jul 2025 21:53:35 -0700
Subject: [PATCH 1/5] [libc][uefi] add testing
---
.github/workflows/libc-fullbuild-tests.yml | 2 -
libc/CMakeLists.txt | 7 +
libc/cmake/modules/LLVMLibCTestRules.cmake | 6 +-
libc/config/uefi/entrypoints.txt | 1 +
libc/src/__support/OSUtil/uefi/exit.cpp | 4 +-
libc/test/scripts/uefi_runner.py | 194 +++++++++++++++++++++
6 files changed, 209 insertions(+), 5 deletions(-)
create mode 100755 libc/test/scripts/uefi_runner.py
diff --git a/.github/workflows/libc-fullbuild-tests.yml b/.github/workflows/libc-fullbuild-tests.yml
index 24d75f58d45e0..947980ac40107 100644
--- a/.github/workflows/libc-fullbuild-tests.yml
+++ b/.github/workflows/libc-fullbuild-tests.yml
@@ -112,8 +112,6 @@ jobs:
--target install
- name: Test
- # Skip UEFI tests until we have testing set up.
- if: ${{ ! endsWith(matrix.target, '-uefi-llvm') }}
run: >
cmake
--build ${{ steps.strings.outputs.build-output-dir }}
diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index 507b3aa88babf..4ccbbe82a7f5d 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -260,6 +260,13 @@ endif()
if(LIBC_TARGET_OS_IS_GPU)
include(prepare_libc_gpu_build)
+endif()
+
+if(LIBC_TARGET_OS_IS_UEFI)
+ set(uefi_test_exe "${LIBC_SOURCE_DIR}/test/scripts/uefi_runner.py")
+endif()
+
+if(LIBC_TARGET_OS_IS_GPU OR LIBC_TARGET_OS_IS_UEFI)
set(LIBC_ENABLE_UNITTESTS OFF)
endif()
diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 1cd09816e223f..0088b438fbfa4 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -612,6 +612,8 @@ function(add_integration_test test_name)
set(test_cmd
${INTEGRATION_TEST_ENV}
$<$<BOOL:${LIBC_TARGET_OS_IS_GPU}>:${gpu_loader_exe}>
+ $<$<BOOL:${LIBC_TARGET_OS_IS_UEFI}>:${uefi_test_exe}>
+ $<$<BOOL:${LIBC_TARGET_OS_IS_UEFI}>:${LIBC_TARGET_TRIPLE}>
${CMAKE_CROSSCOMPILING_EMULATOR}
${INTEGRATION_TEST_LOADER_ARGS}
$<TARGET_FILE:${fq_build_target_name}> ${INTEGRATION_TEST_ARGS})
@@ -810,7 +812,9 @@ function(add_libc_hermetic test_name)
if(NOT HERMETIC_TEST_NO_RUN_POSTBUILD)
set(test_cmd ${HERMETIC_TEST_ENV}
- $<$<BOOL:${LIBC_TARGET_OS_IS_GPU}>:${gpu_loader_exe}> ${CMAKE_CROSSCOMPILING_EMULATOR} ${HERMETIC_TEST_LOADER_ARGS}
+ $<$<BOOL:${LIBC_TARGET_OS_IS_GPU}>:${gpu_loader_exe}>
+ $<$<BOOL:${LIBC_TARGET_OS_IS_UEFI}>:${uefi_test_exe}>
+ ${CMAKE_CROSSCOMPILING_EMULATOR} ${HERMETIC_TEST_LOADER_ARGS}
$<TARGET_FILE:${fq_build_target_name}> ${HERMETIC_TEST_ARGS})
add_custom_target(
${fq_target_name}
diff --git a/libc/config/uefi/entrypoints.txt b/libc/config/uefi/entrypoints.txt
index 2e11c534a4f3b..cf3b1a5d1aaa7 100644
--- a/libc/config/uefi/entrypoints.txt
+++ b/libc/config/uefi/entrypoints.txt
@@ -1,6 +1,7 @@
set(TARGET_LIBC_ENTRYPOINTS
# errno.h entrypoints
libc.src.errno.errno
+ libc.src.string.strstr
)
set(TARGET_LIBM_ENTRYPOINTS)
diff --git a/libc/src/__support/OSUtil/uefi/exit.cpp b/libc/src/__support/OSUtil/uefi/exit.cpp
index e734983cd125b..20214a09852af 100644
--- a/libc/src/__support/OSUtil/uefi/exit.cpp
+++ b/libc/src/__support/OSUtil/uefi/exit.cpp
@@ -7,7 +7,7 @@
//===-----------------------------------------------------------------===//
#include "src/__support/OSUtil/exit.h"
-#include "config/uefi.h"
+#include "config/app.h"
#include "include/llvm-libc-types/EFI_SYSTEM_TABLE.h"
#include "src/__support/macros/config.h"
@@ -15,7 +15,7 @@ namespace LIBC_NAMESPACE_DECL {
namespace internal {
[[noreturn]] void exit(int status) {
- app.system_table->BootServices->Exit(__llvm_libc_efi_image_handle, status, 0,
+ app.system_table->BootServices->Exit(app.image_handle, status, 0,
nullptr);
__builtin_unreachable();
}
diff --git a/libc/test/scripts/uefi_runner.py b/libc/test/scripts/uefi_runner.py
new file mode 100755
index 0000000000000..fa8bee4ad63bd
--- /dev/null
+++ b/libc/test/scripts/uefi_runner.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+#
+# ===- UEFI runner for binaries ------------------------------*- 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
+#
+# ==-------------------------------------------------------------------------==#
+
+import argparse
+import os
+import platform
+import re
+import shutil
+import subprocess
+import tempfile
+
+
+class Target:
+ def __init__(self, triple: str):
+ self.triple = triple.split("-")
+ assert len(self.triple) == 2 or len(self.triple) == 3
+
+ def arch(self):
+ return self.triple[0]
+
+ def isNativeArch(self):
+ return self.arch() == Target.defaultArch()
+
+ def vendor(self):
+ if len(self.triple) == 2:
+ return "unknown"
+
+ return self.triple[1]
+
+ def os(self):
+ if len(self.triple) == 2:
+ return self.triple[1]
+
+ return self.triple[2]
+
+ def abi(self):
+ if len(self.triple) < 4:
+ return "llvm"
+
+ return self.triple[3]
+
+ def qemuBinary(self):
+ return f"qemu-system-{self.arch()}"
+
+ def qemuArgs(self):
+ if self.arch() == "aarch64":
+ args = ["-machine", "virt"]
+
+ if self.isNativeArch():
+ args.pop()
+ args.append("virt,gic-version=max,accel=kvm:tcg")
+ args.append("-cpu")
+ args.append("max")
+
+ return args
+
+ if self.arch() == "x86_64" and self.isNativeArch():
+ return [
+ "-machine",
+ "accel=kvm:tcg",
+ "-cpu",
+ "max",
+ ]
+ return []
+
+ def ovmfPath(self):
+ if self.arch() == "aarch64":
+ return "AAVMF_CODE.fd"
+
+ if self.arch() == "x86_64":
+ return "OVMF_CODE.fd"
+
+ raise Exception(f"{self.arch()} is not a valid architecture")
+
+ def efiArch(self):
+ if self.arch() == "aarch64":
+ return "AA64"
+
+ if self.arch() == "x86_64":
+ return "X64"
+
+ raise Exception(f"{self.arch()} is not a valid architecture")
+
+ def efiFileName(self):
+ return f"BOOT{self.efiArch()}.EFI"
+
+ def __str__(self):
+ return f"{self.arch()}-{self.vendor()}-{self.os()}-{self.abi()}"
+
+ def default():
+ return Target(f"{Target.defaultArch()}-unknown-{Target.defaultOs()}")
+
+ def defaultArch():
+ return platform.machine()
+
+ def defaultOs():
+ return platform.system().lower()
+
+
+def main():
+ parser = argparse.ArgumentParser(description="UEFI runner for binaries")
+ parser.add_argument("binary_file", help="Path to the UEFI binary to execute")
+ parser.add_argument(
+ "--target",
+ help="Triplet which specifies what the target is",
+ )
+ parser.add_argument(
+ "--ovmf-path",
+ help="Path to the directory where OVMF is located",
+ )
+ args = parser.parse_args()
+ target = Target.default() if args.target is None else Target(args.target)
+
+ ovmfFile = os.path.join(
+ args.ovmf_path
+ or os.getenv("OVMF_PATH")
+ or f"/usr/share/edk2/{target.efiArch().lower()}",
+ target.ovmfPath(),
+ )
+
+ qemuArgs = [target.qemuBinary()]
+ qemuArgs.extend(target.qemuArgs())
+
+ qemuArgs.append("-drive")
+ qemuArgs.append(f"if=pflash,format=raw,unit=0,readonly=on,file={ovmfFile}")
+
+ qemuArgs.append("-nographic")
+ qemuArgs.append("-serial")
+ qemuArgs.append("stdio")
+
+ qemuArgs.append("-monitor")
+ qemuArgs.append("none")
+
+ with tempfile.TemporaryDirectory() as tempdir:
+ qemuArgs.append("-drive")
+ qemuArgs.append(f"file=fat:rw:{tempdir},format=raw,media=disk")
+
+ os.mkdir(os.path.join(tempdir, "EFI"))
+ os.mkdir(os.path.join(tempdir, "EFI", "BOOT"))
+
+ shutil.copyfile(
+ args.binary_file, os.path.join(tempdir, "EFI", "BOOT", target.efiFileName())
+ )
+
+ proc = subprocess.Popen(
+ qemuArgs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
+ )
+
+ num_tests = 0
+ num_suites = 0
+
+ while True:
+ line = proc.stdout.readline()
+ if not line:
+ break
+
+ line = line.rstrip()
+
+ if num_tests > 0:
+ print(line)
+
+ x = re.search(r"Running ([0-9]+) tests? from ([0-9]+) tests? suite\.", line)
+ if not x is None:
+ num_tests = int(x.group(1))
+ num_suites = int(x.group(2))
+ continue
+
+ x = re.search(
+ r"Ran ([0-9]+) tests?\. PASS: ([0-9]+) FAIL: ([0-9]+)", line
+ )
+
+ if not x is None:
+ proc.kill()
+ ran_tests = int(x.group(1))
+ passed_tests = int(x.group(2))
+ failed_tests = int(x.group(3))
+
+ assert passed_tests + failed_tests == ran_tests
+ assert ran_tests == num_tests
+
+ if failed_tests > 0:
+ raise Exception("A test failed")
+ break
+
+
+if __name__ == "__main__":
+ main()
>From c0d391a4a29a1e884934cb6fe933a5935a89e471 Mon Sep 17 00:00:00 2001
From: Tristan Ross <tristan.ross at midstall.com>
Date: Sun, 6 Jul 2025 21:58:04 -0700
Subject: [PATCH 2/5] fix formatting
---
libc/src/__support/OSUtil/uefi/exit.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/libc/src/__support/OSUtil/uefi/exit.cpp b/libc/src/__support/OSUtil/uefi/exit.cpp
index 20214a09852af..61d71dee23ec3 100644
--- a/libc/src/__support/OSUtil/uefi/exit.cpp
+++ b/libc/src/__support/OSUtil/uefi/exit.cpp
@@ -15,8 +15,7 @@ namespace LIBC_NAMESPACE_DECL {
namespace internal {
[[noreturn]] void exit(int status) {
- app.system_table->BootServices->Exit(app.image_handle, status, 0,
- nullptr);
+ app.system_table->BootServices->Exit(app.image_handle, status, 0, nullptr);
__builtin_unreachable();
}
>From 4f7e71e585d4b6b5ffb4a68ad149425c3e8e12ee Mon Sep 17 00:00:00 2001
From: Tristan Ross <tristan.ross at midstall.com>
Date: Sun, 6 Jul 2025 21:59:18 -0700
Subject: [PATCH 3/5] [libc][uefi] remove entrypoint for strstr
---
libc/config/uefi/entrypoints.txt | 1 -
1 file changed, 1 deletion(-)
diff --git a/libc/config/uefi/entrypoints.txt b/libc/config/uefi/entrypoints.txt
index cf3b1a5d1aaa7..2e11c534a4f3b 100644
--- a/libc/config/uefi/entrypoints.txt
+++ b/libc/config/uefi/entrypoints.txt
@@ -1,7 +1,6 @@
set(TARGET_LIBC_ENTRYPOINTS
# errno.h entrypoints
libc.src.errno.errno
- libc.src.string.strstr
)
set(TARGET_LIBM_ENTRYPOINTS)
>From a667129d894620eaecf9c343218de8f07da42710 Mon Sep 17 00:00:00 2001
From: Tristan Ross <tristan.ross at midstall.com>
Date: Sun, 6 Jul 2025 22:18:58 -0700
Subject: [PATCH 4/5] [libc][uefi] get uefi to use headers correctly
---
libc/config/uefi/headers.txt | 3 +++
libc/test/UnitTest/CMakeLists.txt | 9 ++++++---
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/libc/config/uefi/headers.txt b/libc/config/uefi/headers.txt
index a8e7b5bc5e37b..9ece42fc49d14 100644
--- a/libc/config/uefi/headers.txt
+++ b/libc/config/uefi/headers.txt
@@ -1,4 +1,7 @@
set(TARGET_PUBLIC_HEADERS
+ libc.include.assert
libc.include.errno
+ libc.include.inttypes
+ libc.include.time
libc.include.uefi
)
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index c32809da577d4..f2ee172ea9d78 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -26,9 +26,12 @@ function(add_unittest_framework_library name)
endforeach()
if(LLVM_LIBC_FULL_BUILD)
- # TODO: Build test framework with LIBC_FULL_BUILD in full build mode after
- # making LibcFPExceptionHelpers and LibcDeathTestExecutors hermetic.
- set(LLVM_LIBC_FULL_BUILD "")
+ # Ensure UEFI is built with full build since it doesn't support overlay.
+ if(NOT LIBC_TARGET_OS_IS_UEFI)
+ # TODO: Build test framework with LIBC_FULL_BUILD in full build mode after
+ # making LibcFPExceptionHelpers and LibcDeathTestExecutors hermetic.
+ set(LLVM_LIBC_FULL_BUILD "")
+ endif()
_get_common_test_compile_options(compile_options "" "")
target_compile_options(${name}.unit PRIVATE ${compile_options})
set(LLVM_LIBC_FULL_BUILD ON)
>From 38e9399908fabe7d855258a7d09a209cd8a3acae Mon Sep 17 00:00:00 2001
From: Tristan Ross <tristan.ross at midstall.com>
Date: Sun, 6 Jul 2025 22:44:24 -0700
Subject: [PATCH 5/5] [libc][uefi] make tests in uefi use libc headers, disable
clock in uefi
---
libc/test/UnitTest/CMakeLists.txt | 8 +++++++-
libc/test/UnitTest/LibcTest.cpp | 4 ++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index f2ee172ea9d78..bfc3a73b18de1 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -20,7 +20,8 @@ function(add_unittest_framework_library name)
${TEST_LIB_HDRS}
)
target_include_directories(${lib} PRIVATE ${LIBC_SOURCE_DIR})
- if(TARGET libc.src.time.clock)
+ # Somehow "TARGET libc.src.time.clock" is TRUE on UEFI despite not being defined.
+ if(TARGET libc.src.time.clock AND NOT LIBC_TARGET_OS_IS_UEFI)
target_compile_definitions(${lib} PRIVATE TARGET_SUPPORTS_CLOCK)
endif()
endforeach()
@@ -44,6 +45,11 @@ function(add_unittest_framework_library name)
target_include_directories(${name}.hermetic PRIVATE ${LIBC_INCLUDE_DIR})
target_compile_options(${name}.hermetic PRIVATE ${compile_options} -nostdinc++)
+ # All tests in UEFI need to use the libc.
+ if(LIBC_TARGET_OS_IS_UEFI)
+ target_include_directories(${name}.unit PRIVATE ${LIBC_INCLUDE_DIR})
+ endif()
+
if(TEST_LIB_DEPENDS)
foreach(dep IN ITEMS ${TEST_LIB_DEPENDS})
if(TARGET ${dep}.unit)
diff --git a/libc/test/UnitTest/LibcTest.cpp b/libc/test/UnitTest/LibcTest.cpp
index fec45982f3e63..e9362716695d6 100644
--- a/libc/test/UnitTest/LibcTest.cpp
+++ b/libc/test/UnitTest/LibcTest.cpp
@@ -158,13 +158,17 @@ int Test::runTests(const TestOptions &Options) {
}
tlog << green << "[ RUN ] " << reset << TestName << '\n';
+#ifdef TARGET_SUPPORTS_CLOCK
[[maybe_unused]] const uint64_t start_time = static_cast<uint64_t>(clock());
+#endif
RunContext Ctx;
T->SetUp();
T->setContext(&Ctx);
T->Run();
T->TearDown();
+#ifdef TARGET_SUPPORTS_CLOCK
[[maybe_unused]] const uint64_t end_time = static_cast<uint64_t>(clock());
+#endif
switch (Ctx.status()) {
case RunContext::RunResult::Fail:
tlog << red << "[ FAILED ] " << reset << TestName << '\n';
More information about the libc-commits
mailing list