gameboy-rust/tools/test_prepare_gba_tests.py
0xrsydn 6e736c634b test(bios): integrate pinned BIOS read-protection baseline
Add --suite bios with hash-verified upstream files and the ARM evaluation checkpoint at 0x08000248. Preserve the r12=0 assertion, original BIOS boot, and all test bytes. Add synthetic adapter and CLI regressions.

On Darwin arm64 the public ROM fails test 1 in debug and release after 64 steps and 543 nominal cycles. Document that this tests protected reads of Nintendo firmware opcodes, not general BIOS service compatibility. Online and offline preparation agree; 39 Python tests pass. Core behavior is unchanged.
2026-10-05 21:31:04 +07:00

239 lines
11 KiB
Python

"""Adapter regressions with original synthetic bytes; no network or public ROMs."""
import hashlib
import io
import json
from pathlib import Path
import struct
import tempfile
import unittest
from unittest import mock
import prepare_gba_tests as adapter
class PreparationTests(unittest.TestCase):
suite = "arm"
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name).resolve()
self.output = self.root / "prepared"
self.rom = f"{self.suite}/{self.suite}.gba"
if self.suite == "arm":
data = struct.pack("<II", 0xEAFFFFFE, 0xE92D0003)
pc, result = "0x08000004", 12
elif self.suite == "thumb":
data = struct.pack("<IIHHI", 0xEAFFFFFE, 0xE1A00000, 0xA000, 0x4700, 0xE92D0003)
pc, result = "0x0800000c", 7
elif self.suite == "memory":
data = struct.pack("<III", 0xEAFFFFFE, 0xE1A00000, 0xE92D0003)
pc, result = "0x08000008", 12
else:
data = struct.pack("<IIII", 0xEAFFFFFE, 0xE1A00000, 0xE1A00000, 0xE92D0003)
pc, result = "0x0800000c", 12
self.files = {
self.rom: data,
"LICENSE": b"Original fixture, not upstream license content.\n",
f"{self.suite}/{self.suite}.asm": b"Original fixture source marker.\n",
}
self.lock = {
"repository": "original/test-fixture",
"revision": "0" * 40,
"license": "fixture",
"rom": self.rom,
"completion_pc": pc,
"result_register": result,
"files": {name: {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
for name, data in self.files.items()},
}
self.lock_path = self.root / "lock.json"
self.save_lock()
# The unselected path does not exist. Reading the wrong lock must fail.
locks = {suite: self.root / f"missing-{suite}.json" for suite in adapter.LOCKS}
locks[self.suite] = self.lock_path
patch = mock.patch.object(adapter, "LOCKS", locks)
patch.start()
self.addCleanup(patch.stop)
def save_lock(self):
self.lock_path.write_text(json.dumps(self.lock), encoding="utf-8")
def download(self, url):
prefix = "https://raw.githubusercontent.com/original/test-fixture/" + "0" * 40 + "/"
self.assertTrue(url.startswith(prefix))
return self.files[url[len(prefix):]]
def prepare(self):
# Exercise default ARM preparation and explicit selection of the other suites.
options = {} if self.suite == "arm" else {"suite": self.suite}
return adapter.prepare(self.output, download=self.download, **options)
def offline_source(self):
source = self.root / "source"
for name, data in self.files.items():
target = source / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(data)
return source
def test_verified_bytes_license_and_provenance_are_retained(self):
manifest_path = self.prepare()
for name, expected in self.files.items():
self.assertEqual((self.output / name).read_bytes(), expected)
self.assertEqual(json.loads((self.output / "source-lock.json").read_text()), self.lock)
manifest = json.loads(manifest_path.read_text())
self.assertEqual(manifest, {
"version": 1,
"cases": [{
"name": f"jsmolka-{self.suite}",
"rom": self.rom,
"step_limit": 1_000_000,
# The Thumb ROM returns to ARM before evaluation.
"completion": {"pc": self.lock["completion_pc"], "instruction_set": "arm"},
"checks": [{"kind": "register", "index": self.lock["result_register"], "equals": 0}],
}],
})
def test_existing_directory_is_rejected_before_fetching(self):
self.output.mkdir()
sentinel = self.output / "keep"
sentinel.write_text("unchanged")
with self.assertRaisesRegex(ValueError, "already exists"):
adapter.prepare(self.output, download=lambda _: self.fail("unexpected download"), suite=self.suite)
self.assertEqual(sentinel.read_text(), "unchanged")
def test_failed_hash_or_size_leaves_no_output(self):
for replacement in [b"short", b"x" * len(self.files[self.rom])]:
with self.subTest(replacement=replacement):
self.files[self.rom] = replacement
with self.assertRaisesRegex(ValueError, "mismatch"):
self.prepare()
self.assertFalse(self.output.exists())
def test_bad_checkpoint_is_rejected_even_with_matching_hash(self):
for pc in ["0x07fffffc", "0x08000000", "0x08000003", "0x08001000"]:
with self.subTest(pc=pc):
self.lock["completion_pc"] = pc
self.save_lock()
with self.assertRaisesRegex(ValueError, "checkpoint"):
self.prepare()
self.assertFalse(self.output.exists())
def test_offline_tree_gets_the_same_checks_and_no_network(self):
source = self.offline_source()
adapter.prepare(self.output, source, download=lambda _: self.fail("unexpected network"), suite=self.suite)
self.assertEqual((self.output / self.rom).read_bytes(), self.files[self.rom])
(source / "LICENSE").write_bytes(b"modified")
with self.assertRaisesRegex(ValueError, "mismatch"):
adapter.prepare(self.root / "second", source, suite=self.suite)
self.assertFalse((self.root / "second").exists())
def test_download_failure_does_not_publish_partial_fixture(self):
calls = 0
def download(url):
nonlocal calls
calls += 1
if calls == 2:
raise OSError("network unavailable")
return self.download(url)
with self.assertRaisesRegex(OSError, "network unavailable"):
adapter.prepare(self.output, download=download, suite=self.suite)
self.assertFalse(self.output.exists())
def test_tracked_repository_paths_are_rejected(self):
fake_repo = self.root / "repo"
fake_repo.mkdir()
(fake_repo / "roms").mkdir()
with mock.patch.object(adapter, "ROOT", fake_repo):
with self.assertRaisesRegex(ValueError, "outside the repository"):
adapter.destination(fake_repo / "assets")
self.assertEqual(adapter.destination(fake_repo / "roms" / "local"), fake_repo / "roms" / "local")
def test_file_capacity_is_independent_of_declared_size(self):
data = b"x" * (adapter.MAX_FILE_BYTES + 1)
expected = {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
with self.assertRaisesRegex(ValueError, "size mismatch"):
adapter.verify("fixture", data, expected)
def test_cli_prepares_offline_and_reports_errors_without_network(self):
source = self.offline_source()
argv = ["prepare_gba_tests.py", str(self.output), "--source-dir", str(source)]
if self.suite != "arm":
argv += ["--suite", self.suite]
with mock.patch("sys.argv", argv), mock.patch("sys.stdout", new_callable=io.StringIO) as stdout:
self.assertEqual(adapter.main(), 0)
self.assertIn(f"public {self.suite} suite", stdout.getvalue())
# Existing output is an error, not an overwrite or a successful preparation.
with mock.patch("sys.argv", argv), mock.patch("sys.stderr", new_callable=io.StringIO) as stderr:
self.assertEqual(adapter.main(), 1)
self.assertIn("already exists", stderr.getvalue())
class ThumbPreparationTests(PreparationTests):
suite = "thumb"
def test_thumb_bridge_is_checked_even_with_matching_hash(self):
original = self.files[self.rom]
# Wrong ADR, wrong BX, or no bridge before an otherwise valid eval word.
for data, pc in [
(original[:8] + struct.pack("<HHI", 0xA001, 0x4700, 0xE92D0003), "0x0800000c"),
(original[:8] + struct.pack("<HHI", 0xA000, 0x4708, 0xE92D0003), "0x0800000c"),
(struct.pack("<I", 0xE92D0003), "0x08000000"),
]:
with self.subTest(data=data):
self.files[self.rom] = data
self.lock["files"][self.rom] = {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
self.lock["completion_pc"] = pc
self.save_lock()
with self.assertRaisesRegex(ValueError, "Thumb-to-ARM checkpoint bridge"):
self.prepare()
self.assertFalse(self.output.exists())
class MemoryPreparationTests(PreparationTests):
suite = "memory"
class BiosPreparationTests(PreparationTests):
suite = "bios"
class SuiteSelectionTests(unittest.TestCase):
def test_unknown_suite_is_rejected_before_download_or_output(self):
with tempfile.TemporaryDirectory() as temp:
output = Path(temp) / "prepared"
with self.assertRaisesRegex(ValueError, "unsupported public suite"):
adapter.prepare(output, suite="../unknown", download=lambda _: self.fail("unexpected network"))
self.assertFalse(output.exists())
with mock.patch("sys.argv", ["prepare_gba_tests.py", str(output), "--suite", "unknown"]):
with mock.patch("sys.stderr", new_callable=io.StringIO), self.assertRaises(SystemExit) as exit:
adapter.main()
self.assertEqual(exit.exception.code, 2)
self.assertFalse(output.exists())
def test_committed_locks_keep_distinct_verified_protocols(self):
for suite, pc, register in [
("arm", "0x08001d4c", 12),
("thumb", "0x08000934", 7),
("memory", "0x08000350", 12),
("bios", "0x08000248", 12),
]:
lock = json.loads(adapter.LOCKS[suite].read_text())
self.assertEqual(lock["repository"], "jsmolka/gba-tests")
self.assertEqual(lock["revision"], "a7113b67e63f83a9b321696ddd7042ccfad6c881")
self.assertEqual(lock["license"], "MIT")
self.assertEqual(lock["completion_pc"], pc)
self.assertEqual(lock["result_register"], register)
self.assertEqual(lock["rom"], f"{suite}/{suite}.gba")
self.assertIn(lock["rom"], lock["files"])
self.assertIn("LICENSE", lock["files"])
for name, metadata in lock["files"].items():
self.assertFalse(Path(name).is_absolute())
self.assertNotIn("..", Path(name).parts)
self.assertRegex(metadata["sha256"], r"^[0-9a-f]{64}$")
self.assertTrue(0 < metadata["bytes"] <= adapter.MAX_FILE_BYTES)
if __name__ == "__main__":
unittest.main()