summaryrefslogtreecommitdiff
path: root/test/tx/cmake/module_qemu/run_qemu_test.py
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-10-23 13:06:05 -0400
committerGitHub <[email protected]>2026-10-23 13:06:05 -0400
commit4c2179186c5348175bbce481cfafbc3d13f54a61 (patch)
treeece1b62db161d3aaf6b64dd67896beea5f2024d0 /test/tx/cmake/module_qemu/run_qemu_test.py
parent3a77bc385c6cac82c71e1c36d6817e0860d12526 (diff)
Added a QEMU module execution harness for the Cortex-M3 and M4 ports (#821)dev
No Cortex-M module port ran in CI. gcc_check compiles each port's module manager sources and links nothing that loads a module, and the host tests under test/tx/module_manager compile a few portable sources against stubs. A module's start-up, its kernel calls through the supervisor call, the MPU programming on context switches and the fault path that terminates a misbehaving module were exercised only on the Cortex-R52 FVP. test/tx/cmake/module_qemu is a stand-alone CMake project, like test/tx/cmake/riscv, that builds the kernel and Module Manager with a module port's headers, builds test modules position-independent with the port's module linker script, embeds their raw images in manager images, and registers each image with CTest. A Python runner starts it on QEMU's MPS2 board for the core, mps2-an385 for cortex_m3 and mps2-an386 for cortex_m4 built for its FPU with the hard-float ABI, and judges the result line the image prints over semihosting and the exit status it sets. Three images per port: a protected module loaded, started, exercising queues, semaphores, a worker thread and the timer, stopped and unloaded twice with its memory returned; the same module faulting on a read of kernel memory, reported to the fault notification with its module, thread and address and terminated, then stopped and unloaded; and the module built privileged and unprotected, whose identical read succeeds. The fault image is also the check that QEMU's MPU enforces at all. The new module_qemu workflow runs both ports on changes to common/, common_modules/, the two module ports and the harness, installing QEMU from the Ubuntu archive through the bounded apt helpers in scripts/tx_ci_common.sh. Both ports pass 3/3 on QEMU 8.2.2 with Arm GNU 14.3.rel1, using the workflow's commands, with zero compiler warnings; configure, build and run take 9 seconds for both. The AI disclosure check passes. The workflow itself has not run on GitHub yet. Assisted-by: Claude Code (Opus 5.5) <[email protected]>
Diffstat (limited to 'test/tx/cmake/module_qemu/run_qemu_test.py')
-rwxr-xr-xtest/tx/cmake/module_qemu/run_qemu_test.py105
1 files changed, 105 insertions, 0 deletions
diff --git a/test/tx/cmake/module_qemu/run_qemu_test.py b/test/tx/cmake/module_qemu/run_qemu_test.py
new file mode 100755
index 00000000..b2533c5d
--- /dev/null
+++ b/test/tx/cmake/module_qemu/run_qemu_test.py
@@ -0,0 +1,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())