Skip to content

Commit 778c66c

Browse files
[libc] add python test runner for uefi
1 parent d7cbeea commit 778c66c

File tree

2 files changed

+197
-1
lines changed

2 files changed

+197
-1
lines changed

libc/cmake/modules/LLVMLibCTestRules.cmake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ function(add_integration_test test_name)
605605
${INTEGRATION_TEST_ENV}
606606
$<$<BOOL:${LIBC_TARGET_OS_IS_GPU}>:${gpu_loader_exe}>
607607
${CMAKE_CROSSCOMPILING_EMULATOR}
608+
$<$<BOOL:${LIBC_TARGET_OS_IS_UEFI}>:${LIBC_TARGET_TRIPLE}>
608609
${INTEGRATION_TEST_LOADER_ARGS}
609610
$<TARGET_FILE:${fq_build_target_name}> ${INTEGRATION_TEST_ARGS})
610611
add_custom_target(
@@ -799,7 +800,8 @@ function(add_libc_hermetic test_name)
799800
endif()
800801

801802
set(test_cmd ${HERMETIC_TEST_ENV}
802-
$<$<BOOL:${LIBC_TARGET_OS_IS_GPU}>:${gpu_loader_exe}> ${CMAKE_CROSSCOMPILING_EMULATOR} ${HERMETIC_TEST_LOADER_ARGS}
803+
$<$<BOOL:${LIBC_TARGET_OS_IS_GPU}>:${gpu_loader_exe}> ${CMAKE_CROSSCOMPILING_EMULATOR}
804+
$<$<BOOL:${LIBC_TARGET_OS_IS_UEFI}>:${LIBC_TARGET_TRIPLE}> ${HERMETIC_TEST_LOADER_ARGS}
803805
$<TARGET_FILE:${fq_build_target_name}> ${HERMETIC_TEST_ARGS})
804806
add_custom_target(
805807
${fq_target_name}

libc/test/scripts/uefi_runner.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env python3
2+
#
3+
# ===- UEFI runner for binaries ------------------------------*- python -*--==#
4+
#
5+
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6+
# See https://llvm.org/LICENSE.txt for license information.
7+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8+
#
9+
# ==-------------------------------------------------------------------------==#
10+
11+
import argparse
12+
import os
13+
import platform
14+
import re
15+
import shutil
16+
import subprocess
17+
import tempfile
18+
19+
20+
class Target:
21+
def __init__(self, triple: str):
22+
self.triple = triple.split("-")
23+
assert len(self.triple) == 2 or len(self.triple) == 3
24+
25+
def arch(self):
26+
return self.triple[0]
27+
28+
def isNativeArch(self):
29+
return self.arch() == Target.defaultArch()
30+
31+
def vendor(self):
32+
if len(self.triple) == 2:
33+
return "unknown"
34+
35+
return self.triple[1]
36+
37+
def os(self):
38+
if len(self.triple) == 2:
39+
return self.triple[1]
40+
41+
return self.triple[2]
42+
43+
def abi(self):
44+
if len(self.triple) < 4:
45+
return "llvm"
46+
47+
return self.triple[3]
48+
49+
def qemuBinary(self):
50+
return f"qemu-system-{self.arch()}"
51+
52+
def qemuArgs(self):
53+
if self.arch() == "aarch64":
54+
args = ["-machine", "virt"]
55+
56+
if self.isNativeArch():
57+
args.pop()
58+
args.append("virt,gic-version=max,accel=kvm:tcg")
59+
args.append("-cpu")
60+
args.append("max")
61+
62+
return args
63+
64+
if self.arch() == "x86_64" and self.isNativeArch():
65+
return [
66+
"-machine",
67+
"accel=kvm:tcg",
68+
"-cpu",
69+
"max",
70+
]
71+
return []
72+
73+
def ovmfPath(self):
74+
if self.arch() == "aarch64":
75+
return "AAVMF_CODE.fd"
76+
77+
if self.arch() == "x86_64":
78+
return "OVMF_CODE.fd"
79+
80+
raise Exception(f"{self.arch()} is not a valid architecture")
81+
82+
def efiArch(self):
83+
if self.arch() == "aarch64":
84+
return "AA64"
85+
86+
if self.arch() == "x86_64":
87+
return "X64"
88+
89+
raise Exception(f"{self.arch()} is not a valid architecture")
90+
91+
def efiFileName(self):
92+
return f"BOOT{self.efiArch()}.EFI"
93+
94+
def __str__(self):
95+
return f"{self.arch()}-{self.vendor()}-{self.os()}-{self.abi()}"
96+
97+
def default():
98+
return Target(f"{Target.defaultArch()}-unknown-{Target.defaultOs()}")
99+
100+
def defaultArch():
101+
return platform.machine()
102+
103+
def defaultOs():
104+
return platform.system().lower()
105+
106+
107+
def main():
108+
parser = argparse.ArgumentParser(description="UEFI runner for binaries")
109+
parser.add_argument("binary_file", help="Path to the UEFI binary to execute")
110+
parser.add_argument(
111+
"--target",
112+
help="Triplet which specifies what the target is",
113+
)
114+
parser.add_argument(
115+
"--ovmf-path",
116+
help="Path to the directory where OVMF is located",
117+
)
118+
args = parser.parse_args()
119+
target = Target.default() if args.target is None else Target(args.target)
120+
121+
ovmfFile = os.path.join(
122+
args.ovmf_path
123+
or os.getenv("OVMF_PATH")
124+
or f"/usr/share/edk2/{target.efiArch().lower()}",
125+
target.ovmfPath(),
126+
)
127+
128+
qemuArgs = [target.qemuBinary()]
129+
qemuArgs.extend(target.qemuArgs())
130+
131+
qemuArgs.append("-drive")
132+
qemuArgs.append(f"if=pflash,format=raw,unit=0,readonly=on,file={ovmfFile}")
133+
134+
qemuArgs.append("-nographic")
135+
qemuArgs.append("-serial")
136+
qemuArgs.append("stdio")
137+
138+
qemuArgs.append("-monitor")
139+
qemuArgs.append("none")
140+
141+
with tempfile.TemporaryDirectory() as tempdir:
142+
qemuArgs.append("-drive")
143+
qemuArgs.append(f"file=fat:rw:{tempdir},format=raw,media=disk")
144+
145+
os.mkdir(os.path.join(tempdir, "EFI"))
146+
os.mkdir(os.path.join(tempdir, "EFI", "BOOT"))
147+
148+
shutil.copyfile(
149+
args.binary_file, os.path.join(tempdir, "EFI", "BOOT", target.efiFileName())
150+
)
151+
152+
proc = subprocess.Popen(
153+
qemuArgs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
154+
)
155+
156+
num_tests = 0
157+
num_suites = 0
158+
159+
while True:
160+
line = proc.stdout.readline()
161+
if not line:
162+
break
163+
164+
line = line.rstrip()
165+
166+
if num_tests > 0:
167+
print(line)
168+
169+
x = re.search(r"Running ([0-9]+) tests? from ([0-9]+) tests? suite\.", line)
170+
if not x is None:
171+
num_tests = int(x.group(1))
172+
num_suites = int(x.group(2))
173+
continue
174+
175+
x = re.search(
176+
r"Ran ([0-9]+) tests?\. PASS: ([0-9]+) FAIL: ([0-9]+)", line
177+
)
178+
179+
if not x is None:
180+
proc.kill()
181+
ran_tests = int(x.group(1))
182+
passed_tests = int(x.group(2))
183+
failed_tests = int(x.group(3))
184+
185+
assert passed_tests + failed_tests == ran_tests
186+
assert ran_tests == num_tests
187+
188+
if failed_tests > 0:
189+
raise Exception("A test failed")
190+
break
191+
192+
193+
if __name__ == "__main__":
194+
main()

0 commit comments

Comments
 (0)