diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-06 19:28:39 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-06 19:28:39 -0400 |
| commit | 6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf (patch) | |
| tree | 3b3680902cd2223cbe60aaec0a0799cfc7b26be6 /debug/risc-v-gsp-rm-vm/tests/test_rehost.py | |
| parent | a5473c91b8869b017385582ea969783b33553ded (diff) | |
| download | open-iov-6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf.tar.gz open-iov-6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf.zip | |
risc-v-gsp-rm-debugger: land docs 45-53 + harness/sim tool set with tests
rm_capture (chunked decode), launch_audit, state_seed/state_decode,
valprobe, rehost, contracts+run_contracts, host_traffic, gmmu_walk,
msgq_attach, libos_port, log_decode, server_snapshot; doc index updates
in the tool overview, operations pages and the repo README.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_rehost.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/tests/test_rehost.py | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_rehost.py b/debug/risc-v-gsp-rm-vm/tests/test_rehost.py new file mode 100644 index 0000000..3c88355 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/tests/test_rehost.py @@ -0,0 +1,135 @@ +"""Tests for the surgical-rehost harness (harness/rehost.py, docs/46). + +Fixtures: the demo firmware (built by ``make demo``) is rehosted offline. The +real-image probes are skipped when the firmware is absent, and are bounded to +one short call each. No hardware, no module, no guest, no network. + +Run:: + + python3 -m unittest tests.test_rehost -v +""" +from __future__ import annotations + +import contextlib +import io +import json +import os +import sys +import unittest + +HERE = os.path.dirname(os.path.abspath(__file__)) +TOOL_ROOT = os.path.abspath(os.path.join(HERE, "..")) +if TOOL_ROOT not in sys.path: + sys.path.insert(0, TOOL_ROOT) + +from harness.rehost import ( # noqa: E402 + DEFAULT_IMAGE, + DEMO_ELF, + DEMO_FORMAT, + REHOST_FORMAT, + RehostError, + main, + parse_hex_bytes, + parse_mem_spec, + parse_snap_spec, + rehost, + run_demo, +) + + +def _run_main(argv: list[str]) -> tuple[int, str, str]: + out, err = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): + rc = main(argv) + return rc, out.getvalue(), err.getvalue() + + +class SpecTests(unittest.TestCase): + def test_parse_hex_bytes_and_mem_spec(self): + self.assertEqual(parse_hex_bytes("aa bb cc"), b"\xaa\xbb\xcc") + addr, data = parse_mem_spec("0x1000=0102") + self.assertEqual((addr, data), (0x1000, b"\x01\x02")) + + def test_bad_specs_are_clean_errors(self): + with self.assertRaises(RehostError): + parse_hex_bytes("abc") + with self.assertRaises(RehostError): + parse_mem_spec("0x1000") + with self.assertRaises(RehostError): + parse_snap_spec("0x1000") + + def test_missing_image_is_clean_error(self): + with self.assertRaises(RehostError): + rehost(image="/nonexistent/fw.bin", entry=0x1000) + + +@unittest.skipUnless(os.path.isfile(DEMO_ELF), "demo firmware not built") +class DemoRehostTests(unittest.TestCase): + def test_demo_rehost_is_deterministic_and_correct(self): + payload = run_demo() + self.assertEqual(payload["format"], DEMO_FORMAT) + self.assertTrue(payload["ok"]) + self.assertEqual(payload["first"]["status"], "return") + self.assertEqual(payload["first"]["retval"], payload["expected"]) + self.assertEqual(payload["first"]["retval"], payload["second"]["retval"]) + + def test_rehost_writes_report_and_session(self): + import tempfile + + with tempfile.TemporaryDirectory() as tmp: + report = rehost( + image=DEMO_ELF, + entry="rpc_sum", + args=(0x80012000, 4), + mem=((0x80012000, b"\x01\x02\x03\x04"),), + snap=((0x80012000, 4),), + budget=50_000, + timeout=20.0, + out=tmp, + record=True, + name="rehost-test", + ) + self.assertEqual(report["format"], REHOST_FORMAT) + self.assertEqual(report["status"], "return") + self.assertEqual(report["retval"], 10) + self.assertTrue(os.path.isfile(report["report_json"])) + self.assertEqual(len(report["snapshots"]), 1) + self.assertEqual(report["snapshots"][0]["changed_bytes"], 0) + self.assertTrue(report["snapshots"][0]["before_sha256"]) + self.assertIn("fidelity", " ".join(report["limits"]).lower()) + + def test_cli_demo_returns_zero(self): + rc, out, _ = _run_main(["demo"]) + self.assertEqual(rc, 0) + self.assertTrue(json.loads(out)["ok"]) + + def test_cli_missing_image_returns_two(self): + rc, _, err = _run_main(["run", "--image", "/nonexistent/fw.bin", "--entry", "0x1000"]) + self.assertEqual(rc, 2) + self.assertIn("error:", err) + + +class RealImageProbeTests(unittest.TestCase): + """Bounded probes on the shipped 570.144 image; skipped without it.""" + + @unittest.skipUnless(os.path.isfile(DEFAULT_IMAGE), "570.144 firmware absent") + def test_gate_probe_reproduces_invalid_state_without_state(self): + report = rehost( + image=DEFAULT_IMAGE, + profile_path=os.path.join(TOOL_ROOT, "sim", "profiles", "tu104-570.144.json"), + entry=0x5B01210, # per-buffer commit gate (from live analysis; numeric only) + args=(0,), + budget=50_000, + timeout=20.0, + record=False, + name="probe-gate-test", + ) + self.assertEqual(report["format"], REHOST_FORMAT) + self.assertIn(report["status"], ("return", "fault", "budget", "ecall")) + # With no engine-context state the gate family returns NV_ERR_INVALID_STATE. + self.assertEqual(report["retval"], 0x40) + self.assertIn("Not a boot", report["limits"][0]) + + +if __name__ == "__main__": + unittest.main() |
