Files
kaptan/firmware/scripts/test-build-workflow.py
T
Onur Özkan 62b18d5b2e build: replace argument dispatch and retain source-bound verification
Replace argument recovery with direct build targets. Run host, target,
board, coverage, Miri and selected Kani checks while retaining source
identities, logs and artifacts, including failed attempts.

Keep the PID proof diagnostic exception explicit and match its exact
identity alongside passing contract assertions and covers.

Validation: final-source verification matrix, assembled workflow tests
and documentation build.
2026-11-07 18:12:11 +03:00

322 lines
13 KiB
Python

#!/usr/bin/env python3
"""Exercise board Makefiles with fake Cargo, image creation, and DFU tools."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
FIRMWARE = Path(__file__).resolve().parents[1]
BOARDS = ("geprc-f722-v2", "kakute-f7-mini-v3")
TARGET = "thumbv7em-none-eabihf"
FAKE_TOOL = """#!/usr/bin/env python3
import json
import os
from pathlib import Path
import sys
tool = Path(sys.argv[0]).name
args = sys.argv[1:]
with open(os.environ["WORKFLOW_LOG"], "a") as log:
log.write(json.dumps({"tool": tool, "args": args}) + "\\n")
if os.environ.get("FAIL_TOOL") == tool:
sys.exit(1)
if tool == "cargo":
if args[0] != "build" or not all(
flag in args for flag in ("--release", "--locked", "--offline")
):
raise ValueError("expected locked offline release build")
directory = Path(args[args.index("--target-dir") + 1])
directory /= args[args.index("--target") + 1]
directory /= "release"
directory.mkdir(parents=True, exist_ok=True)
for index, arg in enumerate(args):
if arg == "--bin":
binary = args[index + 1]
(directory / binary).write_text("fresh " + binary)
elif tool == "create-dfu-image.sh":
if len(args) != 1:
raise ValueError("expected one ELF")
elf = Path(args[0])
if elf.read_text() != "fresh " + elf.name:
raise ValueError("stale ELF")
Path(str(elf) + ".bin").write_text(elf.read_text())
elif tool == "dfu-util":
image = Path(args[args.index("--download") + 1])
if image.read_text() != "fresh " + image.stem:
raise ValueError("stale image")
else:
raise AssertionError(tool)
"""
class BoardWorkflowTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory(prefix="kaptan-workflow-")
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
self.boards = self.root / "firmware" / "boards"
self.boards.mkdir(parents=True)
for relative in ("Makefile", "board.mk", *(f"{b}/Makefile" for b in BOARDS)):
destination = self.boards / relative
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(FIRMWARE / "boards" / relative, destination)
for relative in (
"bin/cargo",
"bin/dfu-util",
"firmware/scripts/create-dfu-image.sh",
):
tool = self.root / relative
tool.parent.mkdir(parents=True, exist_ok=True)
tool.write_text(FAKE_TOOL)
tool.chmod(0o755)
self.log = self.root / "events.jsonl"
self.environment = dict(os.environ)
for name in ("CARGO_TARGET_DIR", "MAKEFLAGS", "MFLAGS", "MAKEOVERRIDES"):
self.environment.pop(name, None)
self.environment.update(
PATH=f"{self.root / 'bin'}{os.pathsep}{os.environ['PATH']}",
WORKFLOW_LOG=str(self.log),
)
# A wrong packaging path must fail even when old build outputs exist.
self.seed_stale(self.root / "firmware" / "target")
def seed_stale(self, target_dir):
release = target_dir / TARGET / "release"
release.mkdir(parents=True, exist_ok=True)
for board in BOARDS:
for suffix in ("", ".bin"):
(release / f"kaptan-{board}{suffix}").write_text("stale")
def run_make(self, directory, goal, target_dir=None, fail="", variables=()):
self.log.unlink(missing_ok=True)
environment = dict(self.environment, FAIL_TOOL=fail)
if target_dir is not None:
environment["CARGO_TARGET_DIR"] = target_dir
result = subprocess.run(
[
"make",
"--no-print-directory",
"-j2",
"-C",
str(directory),
goal,
*variables,
],
env=environment,
capture_output=True,
text=True,
timeout=30,
)
events = (
[json.loads(line) for line in self.log.read_text().splitlines()]
if self.log.exists()
else []
)
return result, events
def test_selected_board_artifact_directory_and_identity(self):
for board in BOARDS:
directory = self.boards / board
for target_dir in (
None,
"local artifacts",
str(self.root / "external artifacts"),
):
with self.subTest(board=board, target_dir=target_dir):
result, events = self.run_make(
directory,
"flash",
target_dir,
variables=(
"BOARD=wrong",
"PACKAGE=wrong",
"EMBEDDED_TARGET=wrong",
),
)
self.assertEqual(
result.returncode, 0, result.stdout + result.stderr
)
self.assertEqual(
[event["tool"] for event in events],
["cargo", "create-dfu-image.sh", "dfu-util"],
)
args = events[0]["args"]
self.assertEqual(args[args.index("-p") + 1], f"kaptan-{board}")
self.assertEqual(args[args.index("--bin") + 1], f"kaptan-{board}")
self.assertEqual(args[args.index("--target") + 1], TARGET)
expected = directory / (target_dir or "../../target")
self.assertEqual(
(directory / args[args.index("--target-dir") + 1]).resolve(),
expected.resolve(),
)
elf = expected / TARGET / "release" / f"kaptan-{board}"
self.assertEqual(
(directory / events[1]["args"][0]).resolve(), elf.resolve()
)
self.assertEqual(
(directory / events[2]["args"][-1]).resolve(),
Path(str(elf) + ".bin").resolve(),
)
def test_all_boards_use_one_artifact_directory(self):
for target_dir in (
None,
"local artifacts",
str(self.root / "external artifacts"),
):
with self.subTest(target_dir=target_dir):
result, events = self.run_make(self.boards, "build", target_dir)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(
[event["tool"] for event in events],
["cargo", "create-dfu-image.sh", "create-dfu-image.sh"],
)
expected = self.boards / (target_dir or "../target")
args = events[0]["args"]
self.assertEqual(
(self.boards / args[args.index("--target-dir") + 1]).resolve(),
expected.resolve(),
)
self.assertEqual(
[
(self.boards / event["args"][0]).resolve()
for event in events[1:]
],
[
(expected / TARGET / "release" / f"kaptan-{b}").resolve()
for b in BOARDS
],
)
def test_failures_stop_packaging_and_flashing_even_with_stale_outputs(self):
target_dir = self.root / "external artifacts"
for directory, goal in (
(self.boards, "build"),
*((self.boards / board, "flash") for board in BOARDS),
):
for failed_tool in ("cargo", "create-dfu-image.sh"):
with self.subTest(directory=directory, failed_tool=failed_tool):
self.seed_stale(target_dir)
result, events = self.run_make(
directory, goal, str(target_dir), failed_tool
)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(
[event["tool"] for event in events],
["cargo"] if failed_tool == "cargo" else ["cargo", failed_tool],
)
for board in BOARDS:
image = target_dir / TARGET / "release" / f"kaptan-{board}.bin"
self.assertEqual(image.read_text(), "stale")
class PackagingValidationTests(unittest.TestCase):
def test_real_packager_validates_sram_and_bounds_before_objcopy(self):
fake = """#!/usr/bin/env python3
import os
from pathlib import Path
import sys
name = Path(sys.argv[0]).name
if name == "rustc":
print("host: test-host" if sys.argv[1] == "-vV" else os.environ["FAKE_SYSROOT"])
elif name == "llvm-readobj":
print(os.environ["ELF_METADATA"])
elif name == "llvm-objdump":
print("20000000: bx lr")
elif name == "llvm-objcopy":
Path(os.environ["OBJCOPY_LOG"]).touch()
Path(sys.argv[-1]).write_bytes(b"validated image")
else:
raise AssertionError(name)
"""
with tempfile.TemporaryDirectory(prefix="kaptan-package-") as temporary:
root = Path(temporary)
tools = root / "lib/rustlib/test-host/bin"
tools.mkdir(parents=True)
for path in (
root / "rustc",
*(tools / f"llvm-{tool}" for tool in ("readobj", "objdump", "objcopy")),
):
path.write_text(fake)
path.chmod(0o755)
elf, memory, log = (
root / "arbitrary-name.elf",
root / "memory.x",
root / "objcopy-called",
)
elf.touch()
image = elf.with_suffix(".bin")
memory.write_text(
"FLASH : ORIGIN = 0x08000000, LENGTH = 256\nRAM : ORIGIN = 0x20000000, LENGTH = 128\n"
)
for executable, size, accepted in (
(True, 16, True),
(False, 16, False),
(True, 129, False),
):
with self.subTest(executable=executable, size=size):
image.write_bytes(b"previous image")
log.unlink(missing_ok=True)
flags = [{"Name": "SHF_ALLOC"}]
if executable:
flags.append({"Name": "SHF_EXECINSTR"})
metadata = [
{
"Sections": [
{
"Section": {
"Name": {"Name": ".data"},
"Size": size,
"Address": 0x20000000,
"Flags": {"Flags": flags},
}
}
],
"ProgramHeaders": [
{
"ProgramHeader": {
"Type": {"Name": "PT_LOAD"},
"VirtualAddress": 0x20000000,
"PhysicalAddress": 0x08000000,
"FileSize": size,
"MemSize": size,
}
}
],
}
]
result = subprocess.run(
[
str(FIRMWARE / "scripts/create-dfu-image.sh"),
str(elf),
str(memory),
],
env=dict(
os.environ,
PATH=f"{root}{os.pathsep}{os.environ['PATH']}",
FAKE_SYSROOT=str(root),
ELF_METADATA=json.dumps(metadata),
OBJCOPY_LOG=str(log),
),
capture_output=True,
text=True,
timeout=10,
)
self.assertEqual(
result.returncode == 0, accepted, result.stdout + result.stderr
)
self.assertEqual(log.exists(), accepted)
self.assertEqual(
image.read_bytes(),
b"validated image" if accepted else b"previous image",
)
if __name__ == "__main__":
unittest.main()