[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