[libc-commits] [libc] [llvm] [libc][uefi] add testing (PR #147235)

Tristan Ross via libc-commits libc-commits at lists.llvm.org
Sun Jul 6 21:54:44 PDT 2025


https://github.com/RossComputerGuy created https://github.com/llvm/llvm-project/pull/147235

Add testing to UEFI.

>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] [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()



More information about the libc-commits mailing list