gameboy-rust/tools/test_prepare_homebrew_pong.py

114 lines
5.1 KiB
Python

"""Offline adapter tests. All fixture bytes are original synthetic data."""
import hashlib
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import prepare_homebrew_pong as adapter
from prepare_gba_tests import ROOT, MAX_FILE_BYTES
class PongPreparation(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.base = Path(self.temp.name)
self.output = self.base / "prepared"
self.source = self.base / "source"
self.source.mkdir()
real = json.loads(adapter.LOCK.read_text())
self.files = {name: f"original fixture: {name}\n".encode() for name in real["files"]}
self.lock = dict(real, files={name: {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
for name, data in self.files.items()})
self.lock_path = self.base / "lock.json"
self.lock_path.write_text(json.dumps(self.lock))
self.addCleanup(patch.stopall)
patch.object(adapter, "LOCK", self.lock_path).start()
for name, data in self.files.items():
path = self.source / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
def test_offline_preparation_retains_license_and_is_repeatable(self):
first = adapter.prepare(self.output, self.source)
second = adapter.prepare(self.base / "second", self.source)
self.assertEqual(first.read_bytes(), second.read_bytes())
self.assertEqual(json.loads(first.read_text()), adapter.manifest(self.lock["rom"]))
self.assertEqual(json.loads((self.output / "source-lock.json").read_text()), self.lock)
for name, data in self.files.items():
self.assertEqual((self.output / name).read_bytes(), data)
with self.assertRaisesRegex(ValueError, "already exists"):
adapter.prepare(self.output, self.source)
self.assertEqual((self.output / "LICENSE").read_bytes(), self.files["LICENSE"])
def test_download_uses_only_pinned_revision_urls(self):
base = f"https://raw.githubusercontent.com/{self.lock['repository']}/{self.lock['revision']}/"
calls = []
def download(url):
self.assertTrue(url.startswith(base))
calls.append(url.removeprefix(base))
return self.files[calls[-1]]
adapter.prepare(self.output, download=download)
self.assertEqual(calls, list(self.files))
def test_bad_hash_size_missing_and_oversized_files_leave_no_output(self):
rom = self.source / self.lock["rom"]
original = rom.read_bytes()
for replacement in [bytes(len(original)), original + b"x", b"x" * (MAX_FILE_BYTES + 1)]:
rom.write_bytes(replacement)
with self.assertRaises(ValueError):
adapter.prepare(self.output, self.source)
self.assertFalse(self.output.exists())
rom.unlink()
with self.assertRaises(ValueError):
adapter.prepare(self.output, self.source)
self.assertFalse(self.output.exists())
def test_network_failure_leaves_no_output(self):
def fail(_):
raise OSError("offline")
with self.assertRaises(OSError):
adapter.prepare(self.output, download=fail)
self.assertFalse(self.output.exists())
def test_tracked_and_symlinked_destinations_are_rejected_before_fetch(self):
def fail(_):
self.fail("must not fetch for unsafe destination")
with self.assertRaises(ValueError):
adapter.prepare(ROOT / "tracked-pong-test", download=fail)
link = self.base / "linked"
link.symlink_to(ROOT, target_is_directory=True)
with self.assertRaises(ValueError):
adapter.prepare(link / "tracked-pong-test", download=fail)
with self.assertRaises(ValueError):
adapter.prepare(self.base / "missing" / "out", download=fail)
def test_manifest_has_bounded_distinct_scenarios_and_complete_button_snapshots(self):
manifest = adapter.manifest("test.gba")
self.assertEqual(manifest["version"], 2)
cases = manifest["cases"]
self.assertEqual(len({case["name"] for case in cases}), len(cases))
self.assertLessEqual(sum(case["step_limit"] for case in cases), 100000000)
for case in cases:
self.assertEqual(case["rom"], "test.gba")
frames = [event["vblank"] for event in case["inputs"]]
self.assertEqual(frames, sorted(set(frames)))
self.assertTrue(all(frame < case["completion"]["vblanks"] for frame in frames))
for event in case["inputs"]:
self.assertLessEqual(event["buttons"], 1023)
if case["inputs"]:
self.assertEqual(case["inputs"][-1]["buttons"], 0)
self.assertTrue(case["checks"])
for check in case["checks"]:
self.assertEqual(check["kind"], "pixel")
self.assertLess(check["x"], 240)
self.assertLess(check["y"], 160)
self.assertLessEqual(check["equals"], 0xffffff)
if __name__ == "__main__":
unittest.main()