summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-06 19:28:39 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-06 19:28:39 -0400
commit6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf (patch)
tree3b3680902cd2223cbe60aaec0a0799cfc7b26be6 /debug/risc-v-gsp-rm-vm/tests/test_valprobe.py
parenta5473c91b8869b017385582ea969783b33553ded (diff)
downloadopen-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_valprobe.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/tests/test_valprobe.py200
1 files changed, 200 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py b/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py
new file mode 100644
index 0000000..cbf530a
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/tests/test_valprobe.py
@@ -0,0 +1,200 @@
+"""Tests for the validation-predicate probe (harness/valprobe.py, docs/47).
+
+The bundled FECS-promote matrix runs offline on the shipped 570.144 image
+(skipped when the image is absent). No hardware, no module, no guest, no
+network.
+
+Run::
+
+ python3 -m unittest tests.test_valprobe -v
+"""
+from __future__ import annotations
+
+import contextlib
+import io
+import json
+import os
+import struct
+import sys
+import tempfile
+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 sim.decode import K_JALR, K_LUI, K_OPI, decode32 # noqa: E402
+
+from harness.rehost import DEFAULT_IMAGE, _parse_u64 # noqa: E402
+from harness.valprobe import ( # noqa: E402
+ MATRIX_FORMAT,
+ PROBE_FORMAT,
+ PROMOTE_VALIDATION,
+ SPEC_FORMAT,
+ ValprobeError,
+ decode_memory_spec,
+ encode_return_stub,
+ fecs_spec,
+ main,
+ reg_index,
+ run_spec,
+)
+
+PROFILE = os.path.join(TOOL_ROOT, "sim", "profiles", "tu104-570.144.json")
+
+
+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 EncodingTests(unittest.TestCase):
+ def test_stub_decodes_as_lui_addi_ret(self):
+ code = encode_return_stub(0x2FFFF9800)
+ self.assertEqual(len(code), 12)
+ words = struct.unpack("<III", code)
+ lui = decode32(0, words[0])
+ addi = decode32(4, words[1])
+ ret = decode32(8, words[2])
+ self.assertEqual(lui[0], K_LUI)
+ self.assertEqual(lui[2], 10)
+ self.assertEqual(addi[0], K_OPI)
+ self.assertEqual(addi[2], 10)
+ self.assertEqual(ret[0], K_JALR)
+ self.assertEqual(ret[1], 0) # jalr zero, 0(ra)
+
+ def test_stub_value_roundtrip(self):
+ # The lui+addi pair must reconstruct every value the probe uses
+ # (scratch addresses below 2^31, offset by the 12-bit signed addi).
+ for value in (0, 0x800, 0xFFF, 0x1234, 0x10000000, 0x1FFFFFFF, 0x7FFF_F000):
+ code = encode_return_stub(value)
+ hi = struct.unpack("<I", code[0:4])[0] >> 12
+ lo = struct.unpack("<I", code[4:8])[0] >> 20
+ if lo >= 0x800:
+ lo -= 0x1000
+ self.assertEqual((hi << 12) + lo, value)
+
+ def test_memory_spec_widths(self):
+ self.assertEqual(decode_memory_spec({"addr": "0x10", "hex": "aabb"}),
+ (0x10, b"\xaa\xbb"))
+ self.assertEqual(decode_memory_spec({"addr": "0x10", "u64": 1}),
+ (0x10, b"\x01" + b"\x00" * 7))
+ self.assertEqual(decode_memory_spec({"addr": "0x10", "u32": 2}),
+ (0x10, b"\x02\x00\x00\x00"))
+ self.assertEqual(decode_memory_spec({"addr": "0x10", "u16": 3}),
+ (0x10, b"\x03\x00"))
+ self.assertEqual(decode_memory_spec({"addr": "0x10", "u8": 4}),
+ (0x10, b"\x04"))
+ with self.assertRaises(ValprobeError):
+ decode_memory_spec({"addr": "0x10"})
+
+ def test_register_aliases(self):
+ for name, index in (("s2", 18), ("x18", 18), ("a3", 13), ("t1", 6), ("fp", 8)):
+ self.assertEqual(reg_index(name), index)
+ with self.assertRaises(ValprobeError):
+ reg_index("r99")
+
+
+class SpecTests(unittest.TestCase):
+ def test_fecs_spec_shape(self):
+ spec = fecs_spec()
+ self.assertEqual(spec["format"], SPEC_FORMAT)
+ self.assertEqual(_parse_u64(spec["target"]), PROMOTE_VALIDATION)
+ self.assertGreaterEqual(len(spec["variants"]), 15)
+ for variant in spec["variants"]:
+ self.assertTrue(variant["label"])
+ self.assertIn(variant["expect"], set(spec["outcomes"].values()))
+ self.assertTrue(variant["stubs"])
+
+ def test_fecs_spec_layout_stays_inside_ram(self):
+ spec = fecs_spec()
+ profile_ram_mb = 12288
+ ram_hi = profile_ram_mb * 1024 * 1024
+ for variant in spec["variants"]:
+ for item in variant["memory"]:
+ self.assertLess(_parse_u64(item["addr"]) + 8, ram_hi)
+
+ def test_missing_spec_is_clean_error(self):
+ rc, _, err = _run_main(["run", "--spec", "/nonexistent/spec.json"])
+ self.assertEqual(rc, 2)
+ self.assertIn("error:", err)
+
+
+@unittest.skipUnless(os.path.isfile(DEFAULT_IMAGE), "570.144 firmware absent")
+class RealImageMatrixTests(unittest.TestCase):
+ def _reduced_spec(self) -> dict:
+ spec = fecs_spec()
+ keep = {
+ "FECS init vidmem, slot non-NULL",
+ "FECS init vidmem, slot NULL",
+ "FECS init sysmem attr 0x6, slot non-NULL",
+ "FECS no-init mapped, slot non-NULL",
+ "PM init",
+ }
+ spec["variants"] = [v for v in spec["variants"] if v["label"] in keep]
+ return spec
+
+ def test_matrix_reproduces_live_verdicts_deterministically(self):
+ spec = self._reduced_spec()
+ first = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test")
+ second = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test")
+ self.assertEqual(first["format"], MATRIX_FORMAT)
+ self.assertEqual(first["evaluated"], len(spec["variants"]))
+ self.assertEqual(first["failed"], 0)
+ verdicts_a = [(r["label"], r["outcome"]) for r in first["variants"]]
+ verdicts_b = [(r["label"], r["outcome"]) for r in second["variants"]]
+ self.assertEqual(verdicts_a, verdicts_b)
+ by_label = dict(verdicts_a)
+ self.assertEqual(by_label["FECS init vidmem, slot non-NULL"],
+ "refuse-fecs-slot-exists-0x1f")
+ self.assertEqual(by_label["FECS init vidmem, slot NULL"], "accept-commit-path")
+ self.assertEqual(by_label["FECS no-init mapped, slot non-NULL"], "accept-skip-path")
+
+ def test_probe_reports_firmware_slot_index_and_stub(self):
+ spec = self._reduced_spec()
+ spec["variants"] = [v for v in spec["variants"]
+ if v["label"] == "FECS init vidmem, slot non-NULL"]
+ matrix = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test")
+ row = matrix["variants"][0]
+ self.assertEqual(row["format"], PROBE_FORMAT)
+ self.assertEqual(row["status"], "outcome")
+ self.assertEqual(row["instructions"], row["instructions"])
+ self.assertTrue(row["stubs"])
+ slot_index = [r for r in row["reads"] if r.get("label") == "firmware-mapped-slot-index"]
+ self.assertEqual(slot_index[0]["u32"], 6) # firmware maps FECS -> slot 6
+
+ def test_boot_to_b3_then_probe(self):
+ spec = self._reduced_spec()
+ spec["variants"] = [v for v in spec["variants"]
+ if v["label"] == "FECS init vidmem, slot non-NULL"]
+ spec["boot_to"] = "B3"
+ matrix = run_spec(spec=spec, profile_path=PROFILE, record=False, name="valprobe-test")
+ row = matrix["variants"][0]
+ self.assertIsNotNone(row["boot"])
+ self.assertEqual(row["boot"]["rung"], "B3")
+ self.assertTrue(row["boot"]["reached"])
+ self.assertEqual(row["outcome"], "refuse-fecs-slot-exists-0x1f")
+
+ def test_matrix_writes_report_json(self):
+ spec = self._reduced_spec()
+ spec["variants"] = spec["variants"][:2]
+ with tempfile.TemporaryDirectory() as tmp:
+ matrix = run_spec(spec=spec, profile_path=PROFILE, out=tmp,
+ record=False, name="valprobe-test")
+ self.assertTrue(os.path.isfile(os.path.join(tmp, "valprobe-matrix.json")))
+ with open(matrix["report_json"], "r", encoding="utf-8") as fh:
+ doc = json.load(fh)
+ self.assertEqual(doc["format"], MATRIX_FORMAT)
+ self.assertEqual(len(doc["variants"]), 2)
+
+ def test_cli_sweep_returns_zero(self):
+ rc, out, _ = _run_main(["sweep", "--no-record"])
+ self.assertEqual(rc, 0)
+ self.assertIn("PASS", out)
+
+
+if __name__ == "__main__":
+ unittest.main()