pureboot: PI lint, tightened gates, and the self-update test suite
check_pi.py asserts the two link-time facts position independence rests on (no absolute jmp/call; the info block within the image's first 256 bytes); the size gates drop to 510 on the tinies for the trampoline word. New per-chip tests beside the reworked protocol test: the planner units (programming orders and their recovery properties, the surgery, staging composition, boot-fuse decode, and the update preflight's error/warning matrix over synthetic fuse bytes), the relocated-copy sweep (the identical image installed one slot lower serves the full command set — the PI acceptance test, and the one that caught the temporary-buffer trap), and the self-update end-to-end: --update-loader to a re-timed build (pureboot9, byte-different by PUREBOOT_TIMEOUT alone), then every power-fail phase killed mid-write, restarted from the runner's flash dump, and completed by a re-run with the application intact throughout. The mega rounds run the BOOTRST-unprogrammed profile: the fixture application's 'L' jump is the application-owned loader entry that profile relies on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
61
test/pbsim.py
Normal file
61
test/pbsim.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Shared simavr harness for the pureboot tests: spawn the device runner,
|
||||
hand out its pty, restart it from a flash dump (the power-fail path), and
|
||||
keep its chatter out of undrained pipes."""
|
||||
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
|
||||
class Device:
|
||||
def __init__(self, binary, elf, mcu, hz, base_hex, page, baud, dump, reset_hex=None, resume=None):
|
||||
cmd = [binary, elf, mcu, hz, base_hex, str(page), str(baud), dump]
|
||||
if reset_hex is not None or resume is not None:
|
||||
cmd.append(reset_hex if reset_hex is not None else ("0" if mcu != "atmega328p" else base_hex))
|
||||
if resume is not None:
|
||||
cmd.append(resume)
|
||||
self.log = open(dump + ".log", "a")
|
||||
self.proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=self.log, text=True)
|
||||
self.dump = dump
|
||||
self.pty = None
|
||||
for _ in range(50):
|
||||
line = self.proc.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
if line.startswith("PB_PTY"):
|
||||
self.pty = line.split()[1]
|
||||
break
|
||||
if not self.pty:
|
||||
self.stop()
|
||||
raise RuntimeError("device did not report a pty")
|
||||
|
||||
def reset(self):
|
||||
"""The external reset line: SIGUSR1 re-enters at the reset vector."""
|
||||
self.proc.send_signal(signal.SIGUSR1)
|
||||
|
||||
def power_fail(self):
|
||||
"""SIGTERM: the runner dumps its flash and exits — the image a
|
||||
restart resumes from."""
|
||||
self.stop()
|
||||
return self.dump
|
||||
|
||||
def stop(self):
|
||||
self.proc.terminate()
|
||||
try:
|
||||
self.proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.proc.kill()
|
||||
self.log.close()
|
||||
|
||||
|
||||
def run_tool(tool, pty, baud, *args, timeout=180):
|
||||
result = subprocess.run(
|
||||
[os.environ.get("PYTHON", "python3"), tool, "--port", pty, "--baud", str(baud), "--wait", "25", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
print(result.stdout, end="")
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"tool exited {result.returncode}: {result.stderr.strip()}")
|
||||
return result.stdout
|
||||
Reference in New Issue
Block a user