summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py534
1 files changed, 534 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py b/debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py
new file mode 100644
index 0000000..2460f1f
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py
@@ -0,0 +1,534 @@
+"""Golden-vector tests for the clean-room TU104 GMMU PTE/PDE decoder.
+
+Every vector is constructed field-by-field from the bit positions documented
+in ``sim/gmmu_pte.py`` (see ``docs/43-GMMU-PTE-DECODER.md``), then decoded and asserted field-by-field.
+The tests also pin the explicit failure behaviour: reserved bits are
+reported, unsupported encodings/levels error instead of guessing, empty and
+truncated blobs are handled deterministically, and decode output is
+repeatable.
+"""
+import contextlib
+import io
+import json
+import os
+import sys
+import tempfile
+import unittest
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from sim.gmmu_pte import ( # noqa: E402
+ APERTURE_NAMES_PDE,
+ APERTURE_NAMES_PTE,
+ E_BAD_VALUE,
+ E_TRUNCATED,
+ E_UNSUPPORTED,
+ GmmuDecodeError,
+ KIND_VER2,
+ KIND_VER2_DUAL,
+ LEVELS,
+ PTE_KIND_NAMES,
+ SUPPORTED_LEVELS,
+ decode_dual_pde,
+ decode_entry,
+ decode_pde,
+ decode_pte,
+ decode_table,
+ main,
+)
+
+
+def make_pte(*, valid=1, aperture=2, volatile=1, encrypted=0, privilege=0,
+ read_only=0, atomic_disable=1, address=0, comptagline=0,
+ peer_id=0, kind=0x06):
+ value = 0
+ value |= (valid & 1) << 0
+ value |= (aperture & 3) << 1
+ value |= (volatile & 1) << 3
+ value |= (encrypted & 1) << 4
+ value |= (privilege & 1) << 5
+ value |= (read_only & 1) << 6
+ value |= (atomic_disable & 1) << 7
+ if aperture in (2, 3):
+ value |= ((address >> 12) & ((1 << 46) - 1)) << 8
+ else:
+ value |= ((address >> 12) & ((1 << 25) - 1)) << 8
+ value |= (peer_id & 7) << 33
+ value |= (comptagline & ((1 << 20) - 1)) << 36
+ value |= (kind & 0xFF) << 56
+ return value
+
+
+def make_pde(*, aperture=1, volatile=1, no_ats=0, address=0, peer_id=0):
+ value = 0
+ value |= (aperture & 3) << 1
+ value |= (volatile & 1) << 3
+ value |= (no_ats & 1) << 5
+ value |= ((address >> 12) & ((1 << 46) - 1)) << 8
+ value |= (peer_id & 7) << 33
+ return value
+
+
+def make_dual(*, aperture_big=1, volatile_big=1, aperture_small=1,
+ volatile_small=1, address_big=0, address_small=0,
+ peer_big=0, peer_small=0, no_ats=0):
+ value = 0
+ value |= (aperture_big & 3) << 1
+ value |= (volatile_big & 1) << 3
+ value |= ((address_big >> 8) & ((1 << 50) - 1)) << 4
+ value |= (peer_big & 7) << 33
+ value |= (aperture_small & 3) << 65
+ value |= (volatile_small & 1) << 67
+ value |= ((address_small >> 12) & ((1 << 46) - 1)) << 72
+ value |= (peer_small & 7) << 97
+ value |= (no_ats & 1) << 5
+ return value
+
+
+class GoldenVectorTests(unittest.TestCase):
+ """Field-by-field constructed vectors must round-trip exactly."""
+
+ def test_pte_system_coherent_round_trip(self):
+ pa = 0x123456789000
+ value = make_pte(valid=1, aperture=2, volatile=1, encrypted=0,
+ privilege=1, read_only=1, atomic_disable=0,
+ address=pa, kind=0x06)
+ d = decode_pte(value)
+ self.assertEqual(d["role"], "pte")
+ self.assertTrue(d["valid"])
+ self.assertEqual(d["aperture"], 2)
+ self.assertEqual(d["aperture_name"], "system_coherent_memory")
+ self.assertTrue(d["volatile"])
+ self.assertFalse(d["encrypted"])
+ self.assertTrue(d["privilege"])
+ self.assertTrue(d["read_only"])
+ self.assertFalse(d["writable"])
+ self.assertFalse(d["atomic_disable"])
+ self.assertEqual(d["address"], pa)
+ self.assertTrue(d["address_complete"])
+ self.assertEqual(d["kind"], 0x06)
+ self.assertEqual(d["kind_name"], "GENERIC_MEMORY")
+ self.assertTrue(d["kind_known"])
+
+ def test_pte_system_non_coherent_round_trip(self):
+ pa = 0x2AAAAA0000
+ value = make_pte(aperture=3, address=pa, read_only=0,
+ atomic_disable=1, kind=0x00)
+ d = decode_pte(value)
+ self.assertEqual(d["aperture_name"], "system_non_coherent_memory")
+ self.assertEqual(d["address"], pa)
+ self.assertTrue(d["writable"])
+ self.assertTrue(d["atomic_disable"])
+ self.assertEqual(d["kind_name"], "PITCH")
+
+ def test_pte_video_aperture_round_trip(self):
+ pa_low = 0x0ABCDEF000
+ value = make_pte(aperture=0, address=pa_low, comptagline=0x12345,
+ kind=0x06)
+ d = decode_pte(value)
+ self.assertEqual(d["aperture_name"], "video_memory")
+ self.assertEqual(d["address"], pa_low)
+ self.assertFalse(d["address_complete"])
+ self.assertEqual(d["comptagline"], 0x12345)
+ self.assertEqual(d["peer_id"], 0)
+
+ def test_pte_peer_aperture_round_trip(self):
+ pa_low = 0x0100000000
+ value = make_pte(aperture=1, address=pa_low, peer_id=5, kind=0x06)
+ d = decode_pte(value)
+ self.assertEqual(d["aperture_name"], "peer_memory")
+ self.assertEqual(d["address"], pa_low)
+ self.assertFalse(d["address_complete"])
+ self.assertEqual(d["peer_id"], 5)
+
+ def test_pte_aperture_names_are_the_proven_four(self):
+ self.assertEqual(
+ APERTURE_NAMES_PTE,
+ {0: "video_memory", 1: "peer_memory",
+ 2: "system_coherent_memory", 3: "system_non_coherent_memory"})
+ for aperture in range(4):
+ d = decode_pte(make_pte(aperture=aperture))
+ self.assertEqual(d["aperture_name"], APERTURE_NAMES_PTE[aperture])
+
+ def test_pde_video_pointer_round_trip(self):
+ pa = 0x00000123456000
+ value = make_pde(aperture=1, volatile=1, no_ats=0, address=pa)
+ d = decode_pde(value)
+ self.assertEqual(d["role"], "pde")
+ self.assertTrue(d["is_pde_pointer"])
+ self.assertFalse(d["is_leaf"])
+ self.assertEqual(d["aperture_name"], "video_memory")
+ self.assertTrue(d["volatile"])
+ self.assertFalse(d["no_ats"])
+ self.assertEqual(d["address"], pa)
+ self.assertFalse(d["address_complete"])
+
+ def test_pde_system_pointer_round_trip(self):
+ pa = 0x1FFFF0000000
+ value = make_pde(aperture=2, volatile=0, no_ats=1, address=pa)
+ d = decode_pde(value)
+ self.assertEqual(d["aperture_name"], "system_coherent_memory")
+ self.assertFalse(d["volatile"])
+ self.assertTrue(d["no_ats"])
+ self.assertEqual(d["address"], pa)
+ self.assertTrue(d["address_complete"])
+
+ def test_pde_invalid_aperture_has_no_address(self):
+ d = decode_pde(make_pde(aperture=0, volatile=0))
+ self.assertEqual(d["aperture_name"], "invalid")
+ self.assertIsNone(d["address"])
+ self.assertFalse(d["address_complete"])
+
+ def test_pde_leaf_bit_is_rejected(self):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_pde(1)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+
+ def test_dual_pde_halves_round_trip(self):
+ addr_big = 0x0000001234560000
+ addr_small = 0x0000000FEDCBA000
+ value = make_dual(aperture_big=1, volatile_big=1, aperture_small=1,
+ volatile_small=1, address_big=addr_big,
+ address_small=addr_small)
+ d = decode_dual_pde(value)
+ self.assertEqual(d["role"], "dual_pde")
+ self.assertTrue(d["is_pde_pointer"])
+ self.assertFalse(d["is_leaf"])
+ self.assertFalse(d["no_ats"])
+ self.assertEqual(d["big"]["aperture_name"], "video_memory")
+ self.assertTrue(d["big"]["volatile"])
+ self.assertEqual(d["big"]["address"], addr_big)
+ self.assertFalse(d["big"]["address_complete"])
+ self.assertEqual(d["small"]["aperture_name"], "video_memory")
+ self.assertTrue(d["small"]["volatile"])
+ self.assertEqual(d["small"]["address"], addr_small)
+ self.assertFalse(d["small"]["address_complete"])
+
+ def test_dual_pde_no_ats_bit_does_not_perturb_big_address(self):
+ # The format names bit 5 NO_ATS inside the BIG address field; valid
+ # depth-3 allocations are 4 KB aligned, so address bit 9 is zero and
+ # the flag must decode without changing the reconstructed address.
+ addr_big = 0x0000001234560000
+ d = decode_dual_pde(make_dual(address_big=addr_big, no_ats=1))
+ self.assertTrue(d["no_ats"])
+ self.assertEqual(d["big"]["address"], addr_big)
+ self.assertEqual(d["big"]["address_vid"] & (1 << 1), 1 << 1)
+
+ def test_dual_pde_system_halves_are_complete(self):
+ value = make_dual(aperture_big=2, aperture_small=3,
+ address_big=0x1000000000,
+ address_small=0x2000000000)
+ d = decode_dual_pde(value)
+ self.assertEqual(d["big"]["aperture_name"], "system_coherent_memory")
+ self.assertTrue(d["big"]["address_complete"])
+ self.assertEqual(d["big"]["address"], 0x1000000000)
+ self.assertEqual(d["small"]["aperture_name"],
+ "system_non_coherent_memory")
+ self.assertTrue(d["small"]["address_complete"])
+ self.assertEqual(d["small"]["address"], 0x2000000000)
+
+ def test_dual_pde_peer_ids_round_trip(self):
+ value = make_dual(aperture_big=1, aperture_small=1, peer_big=3,
+ peer_small=6)
+ d = decode_dual_pde(value)
+ self.assertEqual(d["big"]["peer_id"], 3)
+ self.assertEqual(d["small"]["peer_id"], 6)
+
+ def test_pde_aperture_names_are_the_proven_four(self):
+ self.assertEqual(
+ APERTURE_NAMES_PDE,
+ {0: "invalid", 1: "video_memory",
+ 2: "system_coherent_memory", 3: "system_non_coherent_memory"})
+ for aperture in range(4):
+ d = decode_pde(make_pde(aperture=aperture))
+ self.assertEqual(d["aperture_name"], APERTURE_NAMES_PDE[aperture])
+
+ def test_known_kind_names_spot_values(self):
+ self.assertEqual(PTE_KIND_NAMES[0x06], "GENERIC_MEMORY")
+ self.assertEqual(PTE_KIND_NAMES[0x00], "PITCH")
+ self.assertEqual(PTE_KIND_NAMES[0x07], "INVALID")
+ self.assertEqual(PTE_KIND_NAMES[0x0F], "SMSKED_MESSAGE")
+ self.assertEqual(PTE_KIND_NAMES[0xFE], "GENERIC_16BX2")
+ self.assertEqual(len(PTE_KIND_NAMES), 243)
+
+ def test_unknown_kind_field_is_reported_not_guessed(self):
+ value = make_pte(kind=0xFF)
+ d = decode_pte(value)
+ self.assertEqual(d["kind"], 0xFF)
+ self.assertIsNone(d["kind_name"])
+ self.assertFalse(d["kind_known"])
+
+ def test_level_metadata(self):
+ self.assertEqual([lv["depth"] for lv in LEVELS],
+ list(SUPPORTED_LEVELS))
+ self.assertEqual(decode_pte(make_pte(), level=3)["page_size"], "2m")
+ self.assertEqual(decode_pte(make_pte(), level=4)["page_size"],
+ "4k_or_64k")
+ self.assertEqual(decode_pte(make_pte())["level_candidates"], [3, 4])
+
+
+class ReservedBitTests(unittest.TestCase):
+ """Set bits with no named field must be reported, never dropped."""
+
+ def test_pde_all_bits_set_reports_exact_reserved_set(self):
+ d = decode_pde(((1 << 64) - 1) & ~1) # bit 0 clear = directory entry
+ self.assertEqual(d["reserved_bits"], [4, 6, 7] + list(range(54, 64)))
+ self.assertEqual(d["reserved_mask"],
+ sum(1 << b for b in d["reserved_bits"]))
+
+ def test_pde_named_bits_are_not_reported(self):
+ d = decode_pde(make_pde(aperture=2, volatile=1, no_ats=1,
+ address=0x12345000))
+ self.assertEqual(d["reserved_bits"], [])
+
+ def test_dual_pde_all_bits_set_reports_exact_reserved_set(self):
+ d = decode_dual_pde((1 << 128) - 1)
+ expected = list(range(54, 65)) + list(range(68, 72)) + \
+ list(range(118, 128))
+ self.assertEqual(d["reserved_bits"], expected)
+ self.assertEqual(d["reserved_mask"],
+ sum(1 << b for b in expected))
+
+ def test_dual_pde_unnamed_small_half_bit_64_is_reported(self):
+ d = decode_dual_pde(1 << 64)
+ self.assertEqual(d["reserved_bits"], [64])
+
+ def test_pte_has_no_reserved_bits(self):
+ d = decode_pte((1 << 64) - 1)
+ self.assertEqual(d["reserved_bits"], [])
+ self.assertEqual(d["reserved_mask"], 0)
+
+
+class ErrorBehaviourTests(unittest.TestCase):
+ """Unsupported requests error; they never guess."""
+
+ def test_unknown_encoding_kind_errors(self):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_pte(0, pte_kind="legacy")
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_pde(0, pde_kind="ver1")
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(b"", base_pa=0, kind="nope")
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+
+ def test_wrong_role_for_encoding_errors(self):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_pte(0, pte_kind=KIND_VER2_DUAL)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_dual_pde(0, pde_kind=KIND_VER2)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+
+ def test_unsupported_levels_error(self):
+ value = make_pde().to_bytes(8, "little")
+ for bad in (5, 99, -1, (0, 7)):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(value, base_pa=0, levels=bad)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_pte(0, level=0)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+
+ def test_entry_size_must_match_kind(self):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(b"\x00" * 8, base_pa=0, kind=KIND_VER2_DUAL)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(b"\x00" * 16, base_pa=0, entry_size=16)
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+
+ def test_bad_values_error(self):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_pte(1 << 64)
+ self.assertEqual(ctx.exception.code, E_BAD_VALUE)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_dual_pde(-1)
+ self.assertEqual(ctx.exception.code, E_BAD_VALUE)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(b"", base_pa=-1)
+ self.assertEqual(ctx.exception.code, E_BAD_VALUE)
+
+
+class TableDecodeTests(unittest.TestCase):
+ def test_empty_blob_is_empty_list(self):
+ self.assertEqual(decode_table(b"", base_pa=0x1000), [])
+ self.assertEqual(decode_table(b"", base_pa=0x1000, kind=KIND_VER2_DUAL,
+ entry_size=16), [])
+
+ def test_short_blob_errors(self):
+ for blob in (b"\x01", b"\x01\x02\x03", b"\x00" * 4, b"\x00" * 12):
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(blob, base_pa=0)
+ self.assertEqual(ctx.exception.code, E_TRUNCATED)
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_table(b"\x00" * 24, base_pa=0, kind=KIND_VER2_DUAL,
+ entry_size=16)
+ self.assertEqual(ctx.exception.code, E_TRUNCATED)
+
+ def test_two_entry_table_round_trip_and_addresses(self):
+ pte = make_pte(aperture=2, address=0x40000000)
+ pde = make_pde(aperture=1, address=0x50000000)
+ blob = pte.to_bytes(8, "little") + pde.to_bytes(8, "little")
+ entries = decode_table(blob, base_pa=0x1000)
+ self.assertEqual(len(entries), 2)
+ self.assertEqual(entries[0]["index"], 0)
+ self.assertEqual(entries[0]["entry_pa"], 0x1000)
+ self.assertEqual(entries[0]["role"], "pte")
+ self.assertEqual(entries[0]["address"], 0x40000000)
+ self.assertEqual(entries[1]["index"], 1)
+ self.assertEqual(entries[1]["entry_pa"], 0x1008)
+ self.assertEqual(entries[1]["role"], "pde")
+ self.assertEqual(entries[1]["address"], 0x50000000)
+
+ def test_auto_role_uses_bit_zero_and_reports_inference(self):
+ entries = decode_table(make_pte().to_bytes(8, "little"),
+ base_pa=0)
+ self.assertEqual(entries[0]["role"], "pte")
+ self.assertTrue(entries[0]["role_inferred"])
+ self.assertIsNone(entries[0]["level"])
+ self.assertEqual(entries[0]["level_candidates"], [3, 4])
+ entries = decode_table(make_pde().to_bytes(8, "little"), base_pa=0)
+ self.assertEqual(entries[0]["role"], "pde")
+ self.assertTrue(entries[0]["role_inferred"])
+ self.assertEqual(entries[0]["level_candidates"], [0, 1, 2, 3])
+
+ def test_pinned_level_selects_role(self):
+ pte = make_pte()
+ pde = make_pde()
+ leaf = decode_table(pte.to_bytes(8, "little"), base_pa=0, levels=3)
+ self.assertEqual((leaf[0]["role"], leaf[0]["level"]), ("pte", 3))
+ self.assertEqual(leaf[0]["page_size"], "2m")
+ ptr = decode_table(pde.to_bytes(8, "little"), base_pa=0, levels=3)
+ self.assertEqual((ptr[0]["role"], ptr[0]["level"]), ("pde", 3))
+ upper = decode_table(pde.to_bytes(8, "little"), base_pa=0, levels=1)
+ self.assertEqual((upper[0]["role"], upper[0]["level"]), ("pde", 1))
+ page = decode_table(pte.to_bytes(8, "little"), base_pa=0, levels=4)
+ self.assertEqual((page[0]["role"], page[0]["level"]), ("pte", 4))
+
+ def test_level_sequence_narrows_candidates(self):
+ entries = decode_table(make_pte().to_bytes(8, "little"), base_pa=0,
+ levels=(3, 4))
+ self.assertEqual(entries[0]["level_candidates"], [3, 4])
+ entries = decode_table(make_pde().to_bytes(8, "little"), base_pa=0,
+ levels=(3, 4))
+ self.assertEqual(entries[0]["level_candidates"], [3])
+
+ def test_dual_table_entry(self):
+ value = make_dual(address_big=0x1000, address_small=0x2000)
+ entries = decode_table(value.to_bytes(16, "little"), base_pa=0x2000,
+ kind=KIND_VER2_DUAL, entry_size=16)
+ self.assertEqual(len(entries), 1)
+ self.assertEqual(entries[0]["role"], "dual_pde")
+ self.assertEqual(entries[0]["entry_pa"], 0x2000)
+ self.assertEqual(entries[0]["encoding"], KIND_VER2_DUAL)
+ self.assertFalse(entries[0]["role_inferred"])
+ self.assertEqual(entries[0]["big"]["address"], 0x1000)
+ self.assertEqual(entries[0]["small"]["address"], 0x2000)
+
+ def test_decode_entry_explicit_roles(self):
+ pte = decode_entry(make_pte(), role="pte")
+ self.assertEqual(pte["role"], "pte")
+ pde = decode_entry(make_pde(), role="pde")
+ self.assertEqual(pde["role"], "pde")
+ dual = decode_entry(make_dual(), kind=KIND_VER2_DUAL, role="dual_pde")
+ self.assertEqual(dual["role"], "dual_pde")
+ with self.assertRaises(GmmuDecodeError) as ctx:
+ decode_entry(0, role="bogus")
+ self.assertEqual(ctx.exception.code, E_UNSUPPORTED)
+
+ def test_output_is_deterministic(self):
+ blob = (make_pte(aperture=2, address=0x1000).to_bytes(8, "little")
+ + make_pde(aperture=1, address=0x2000).to_bytes(8, "little")
+ + make_dual(address_big=0x3000,
+ address_small=0x4000).to_bytes(16, "little"))
+ first = decode_table(blob, base_pa=0x1000)
+ second = decode_table(blob, base_pa=0x1000)
+ self.assertEqual(first, second)
+ self.assertEqual(json.dumps(first, sort_keys=True),
+ json.dumps(second, sort_keys=True))
+
+ def test_blob_accepts_bytearray_and_memoryview(self):
+ pte = make_pte(aperture=2, address=0x1000).to_bytes(8, "little")
+ for blob in (bytearray(pte), memoryview(pte)):
+ entries = decode_table(blob, base_pa=0)
+ self.assertEqual(entries[0]["address"], 0x1000)
+
+
+class CliTests(unittest.TestCase):
+ def test_value_json(self):
+ buf = io.StringIO()
+ with contextlib.redirect_stdout(buf):
+ rc = main(["--value", hex(make_pte(aperture=2, address=0x4000)),
+ "--role", "pte", "--json"])
+ self.assertEqual(rc, 0)
+ data = json.loads(buf.getvalue())
+ self.assertEqual(len(data), 1)
+ self.assertEqual(data[0]["role"], "pte")
+ self.assertEqual(data[0]["address"], 0x4000)
+
+ def test_value_level_is_honoured(self):
+ buf = io.StringIO()
+ with contextlib.redirect_stdout(buf):
+ rc = main(["--value", hex(make_pte()), "--role", "pte",
+ "--levels", "3", "--json"])
+ self.assertEqual(rc, 0)
+ data = json.loads(buf.getvalue())
+ self.assertEqual(data[0]["level"], 3)
+ self.assertEqual(data[0]["page_size"], "2m")
+ err = io.StringIO()
+ with contextlib.redirect_stderr(err):
+ rc = main(["--value", hex(make_pte()), "--role", "pte",
+ "--levels", "3,4"])
+ self.assertEqual(rc, 2)
+ self.assertIn(E_BAD_VALUE, err.getvalue())
+
+ def test_blob_json(self):
+ blob = (make_pte(aperture=2, address=0x1000).to_bytes(8, "little")
+ + make_pde(aperture=1, address=0x2000).to_bytes(8, "little"))
+ with tempfile.TemporaryDirectory() as tmp:
+ path = os.path.join(tmp, "table.bin")
+ with open(path, "wb") as handle:
+ handle.write(blob)
+ buf = io.StringIO()
+ with contextlib.redirect_stdout(buf):
+ rc = main(["--blob", path, "--base-pa", "0x1000", "--json"])
+ self.assertEqual(rc, 0)
+ data = json.loads(buf.getvalue())
+ self.assertEqual([e["role"] for e in data], ["pte", "pde"])
+ self.assertEqual(data[1]["entry_pa"], 0x1008)
+
+ def test_text_output_is_deterministic(self):
+ value = hex(make_pte(aperture=2, address=0x4000))
+ outputs = []
+ for _ in range(2):
+ buf = io.StringIO()
+ with contextlib.redirect_stdout(buf):
+ rc = main(["--value", value, "--role", "pte"])
+ self.assertEqual(rc, 0)
+ outputs.append(buf.getvalue())
+ self.assertEqual(outputs[0], outputs[1])
+ self.assertIn("role=pte", outputs[0])
+
+ def test_truncated_blob_exits_two(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ path = os.path.join(tmp, "short.bin")
+ with open(path, "wb") as handle:
+ handle.write(b"\x01\x02\x03")
+ err = io.StringIO()
+ with contextlib.redirect_stderr(err):
+ rc = main(["--blob", path, "--base-pa", "0x1000"])
+ self.assertEqual(rc, 2)
+ self.assertIn(E_TRUNCATED, err.getvalue())
+
+ def test_unsupported_level_exits_two(self):
+ err = io.StringIO()
+ with contextlib.redirect_stderr(err):
+ rc = main(["--value", "0x1", "--role", "pde", "--levels", "9"])
+ self.assertEqual(rc, 2)
+ self.assertIn(E_UNSUPPORTED, err.getvalue())
+
+
+if __name__ == "__main__":
+ unittest.main()