"""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: # Chips without a hardware boot section — the tinies and the # m48s — reset to address 0 like silicon; the boot-sectioned # megas re-vector to the loader base (BOOTRST). patch = not mcu.startswith("atmega") or mcu.startswith("atmega48") cmd.append(reset_hex if reset_hex is not None else ("0" if patch 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