Skip to content

[libc][uefi] add testing #147235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/libc-fullbuild-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
7 changes: 7 additions & 0 deletions libc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
15 changes: 14 additions & 1 deletion libc/cmake/modules/LLVMLibCTestRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,15 @@ function(create_libc_unittest fq_target_name)
endif()
endforeach()

if(LIBC_TARGET_OS_IS_UEFI)
# Linker does not recognize to link libc and crt1
list(APPEND link_libraries libc.startup.uefi.crt1 ${LIBC_BUILD_DIR}/lib/libc.a)

# Needed to make symbols actually link
target_link_options(${fq_build_target_name} PRIVATE
${LIBC_COMPILE_OPTIONS_DEFAULT} "-Wl,/lldmingw")
endif()

set_target_properties(${fq_build_target_name}
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

Expand Down Expand Up @@ -612,6 +621,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})
Expand Down Expand Up @@ -810,7 +821,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}
Expand Down
2 changes: 1 addition & 1 deletion libc/config/uefi/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct AppProperties {
EFI_HANDLE image_handle;
};

[[gnu::weak]] extern AppProperties app;
extern AppProperties app;

} // namespace LIBC_NAMESPACE_DECL

Expand Down
5 changes: 5 additions & 0 deletions libc/config/uefi/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
"LIBC_CONF_ERRNO_MODE": {
"value": "LIBC_ERRNO_MODE_SHARED"
}
},
"printf": {
"LIBC_CONF_PRINTF_DISABLE_FLOAT": {
"value": true
}
}
}
4 changes: 4 additions & 0 deletions libc/config/uefi/entrypoints.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
set(TARGET_LIBC_ENTRYPOINTS
# errno.h entrypoints
libc.src.errno.errno

# string.h entrypoints
libc.src.string.memcpy
libc.src.string.memset
)

set(TARGET_LIBM_ENTRYPOINTS)
Expand Down
3 changes: 3 additions & 0 deletions libc/config/uefi/headers.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
set(TARGET_PUBLIC_HEADERS
libc.include.assert
libc.include.errno
libc.include.inttypes
libc.include.time
libc.include.uefi
)
5 changes: 2 additions & 3 deletions libc/src/__support/OSUtil/uefi/exit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@
//===-----------------------------------------------------------------===//

#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"

namespace LIBC_NAMESPACE_DECL {
namespace internal {

[[noreturn]] void exit(int status) {
app.system_table->BootServices->Exit(__llvm_libc_efi_image_handle, status, 0,
nullptr);
app.system_table->BootServices->Exit(app.image_handle, status, 0, nullptr);
__builtin_unreachable();
}

Expand Down
2 changes: 1 addition & 1 deletion libc/src/__support/macros/attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#define LIBC_INLINE_ASM __asm__ __volatile__
#define LIBC_UNUSED __attribute__((unused))

#ifdef LIBC_TARGET_ARCH_IS_GPU
#if defined(LIBC_TARGET_ARCH_IS_GPU) || defined(__UEFI__)
#define LIBC_THREAD_LOCAL
#else
#define LIBC_THREAD_LOCAL thread_local
Expand Down
17 changes: 13 additions & 4 deletions libc/test/UnitTest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ 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()

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)
Expand All @@ -41,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)
Expand Down
4 changes: 4 additions & 0 deletions libc/test/UnitTest/LibcTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
194 changes: 194 additions & 0 deletions libc/test/scripts/uefi_runner.py
Original file line number Diff line number Diff line change
@@ -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()
Loading