1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
#!/usr/bin/env python3
#
# Copyright (c) 2026 Eclipse ThreadX contributors
#
# This program and the accompanying materials are made available under the
# terms of the MIT License which is available at
# https://opensource.org/licenses/MIT.
#
# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5).
# The AI-generated portions may be considered public domain (CC0-1.0)
# and not subject to the project's licence. The human contributor has
# reviewed and verified that the code is correct.
#
# SPDX-License-Identifier: MIT and CC0-1.0
#
"""Run a module harness image on QEMU and assert its result.
Modelled on the Cortex-R52 FVP runner. Every image reports through Arm
semihosting and ends the simulation itself with SYS_EXIT, so the runner only
launches QEMU, captures what the image printed, and judges it. Both the
result line and QEMU's exit status must say the image passed.
A missing result line is a failure, never a pass: a hang, or a crash before
the first check, must not be able to look like success.
"""
import argparse
import subprocess
import sys
PASS_MARK = "ALL CHECKS PASSED"
FAIL_MARK = "RESULT: FAILED"
FAULT_MARK = "[FAULT]"
def as_text(stream):
"""Normalise a captured stream to str; a timeout hands back bytes."""
if stream is None:
return ""
if isinstance(stream, bytes):
return stream.decode(errors="replace")
return stream
def run(qemu, machine, elf, timeout):
command = [
qemu,
"-M", machine,
"-nographic",
"-monitor", "none",
"-serial", "none",
"-semihosting-config", "enable=on,target=native",
"-kernel", elf,
]
print("Running:", " ".join(command), flush=True)
try:
completed = subprocess.run(
command, capture_output=True, text=True, timeout=timeout
)
except subprocess.TimeoutExpired as expired:
print(as_text(expired.stdout) + as_text(expired.stderr))
print(
f"FAIL: QEMU did not terminate within {timeout}s. Every image is "
"expected to exit through semihosting SYS_EXIT, so this is a hang.",
file=sys.stderr,
)
return 1
output = as_text(completed.stdout) + as_text(completed.stderr)
print(output)
if FAULT_MARK in output:
print("FAIL: the image reported an unexpected exception.", file=sys.stderr)
return 1
if FAIL_MARK in output:
print("FAIL: the image reported failing checks.", file=sys.stderr)
return 1
if PASS_MARK not in output:
print("FAIL: the image printed no result line.", file=sys.stderr)
return 1
if completed.returncode != 0:
print(
f"FAIL: the image reported success but QEMU exited with "
f"{completed.returncode}.",
file=sys.stderr,
)
return 1
return 0
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--qemu", required=True, help="qemu-system-arm executable")
parser.add_argument("--machine", required=True, help="QEMU machine, e.g. mps2-an385")
parser.add_argument("--elf", required=True, help="image to run")
parser.add_argument("--timeout", type=int, default=60, help="seconds before a hang is declared")
args = parser.parse_args()
return run(args.qemu, args.machine, args.elf, args.timeout)
if __name__ == "__main__":
sys.exit(main())
|