diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-04 18:42:36 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-04 18:42:36 -0400 |
| commit | 1f2073d4fbb1d412d9bbf373312e3c51b83db92a (patch) | |
| tree | a661537d7ec9510324e0a40a5a65e4b8c853f01b /debug/risc-v-gsp-rm-vm/tests/test_gmmu_pte.py | |
| parent | c1adf2727983f5cace8ca0f5d1119ac2256c348e (diff) | |
| download | open-iov-1f2073d4fbb1d412d9bbf373312e3c51b83db92a.tar.gz open-iov-1f2073d4fbb1d412d9bbf373312e3c51b83db92a.zip | |
greeneye: GMMU/FBIF observability, PTE decoder and oracle, MPU/libos simulator wave
Simulator development wave (docs 42-44): GMMU page-table entry decode with its oracle/workflow harness, FBIF observability, and MPU/LIBOS device surfaces in the device model, plus refreshed boot/machine/profile data (tu104-570.144), the qemu-line results, and the new unit tests. Docs 00/06/35-41 updated to match; harness helpers and .gitignore included.
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.py | 534 |
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() |
