summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_rehost.py
diff options
context:
space:
mode:
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.py135
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()