114 lines
5.1 KiB
Python
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()
|