mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
149 lines
5.4 KiB
Python
149 lines
5.4 KiB
Python
"""Tests for ``tile_check.py --replay``: the MAME replay's pixel verdict.
|
|
|
|
The replay mode in ``harness/mame/vidstream.lua`` paints a stream produced by
|
|
``make_tiles.py`` and dumps the screen after every clip frame. This checker
|
|
decodes ``stream.bin`` itself -- full frames and deltas, with no code shared
|
|
with the encoder -- renders each frame through the dictionary, and compares it
|
|
with the dump.
|
|
|
|
The tests here build a replay directory in the *host's* format (the same files
|
|
the MAME run writes) and check that the checker passes a good replay and fails
|
|
each kind of bad one. They are the negative controls for the MAME step: a
|
|
checker that cannot fail would make the replay evidence worthless.
|
|
"""
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from harness.mame import tile_check
|
|
from harness.udpstream import make_tiles
|
|
|
|
#: Two frames is enough to have one keyframe and one delta.
|
|
CLIP_FRAMES = 2
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def tiles():
|
|
return make_tiles.build(make_tiles.synthetic_clip(CLIP_FRAMES))
|
|
|
|
|
|
def write_replay(directory: Path, tiles, status_overrides=None):
|
|
"""Write the four things a replay run leaves behind."""
|
|
make_tiles.write_cache(directory, tiles, clip="synthetic", every=60,
|
|
seed=0)
|
|
for frame_no, indices in enumerate(tiles.indices):
|
|
(directory / ("vram-replay-%d.bin" % frame_no)).write_bytes(
|
|
make_tiles.render(indices, tiles.dictionary))
|
|
wire = sum(1 for r in tiles.records if r.kind != make_tiles.KIND_SAME)
|
|
status = {
|
|
"FRAMES": len(tiles.indices),
|
|
"WIRE": wire,
|
|
"RX_OK": wire,
|
|
"RX_BAD": 0,
|
|
"TILES_DROP": 0,
|
|
"DICT_READY": 1,
|
|
"DICT_BYTES": len(tiles.dictionary),
|
|
"DICT_MATCHED": 1,
|
|
"SENTINEL": 0xA5C3,
|
|
"OS_TOUCH": 0,
|
|
"TIMEOUT": 0,
|
|
}
|
|
status.update(status_overrides or {})
|
|
(directory / "replay-status.txt").write_text(
|
|
"".join("%s=%s\n" % (k, v) for k, v in status.items()))
|
|
return directory
|
|
|
|
|
|
def run(directory: Path, capsys):
|
|
code = tile_check.main(["--replay", str(directory)])
|
|
return code, capsys.readouterr().out
|
|
|
|
|
|
def test_a_correct_replay_passes(tmp_path, tiles, capsys):
|
|
write_replay(tmp_path, tiles)
|
|
code, out = run(tmp_path, capsys)
|
|
assert code == 0, out
|
|
assert "replay: %d/%d frames match" % (CLIP_FRAMES, CLIP_FRAMES) in out
|
|
|
|
|
|
def test_one_wrong_pixel_fails_and_names_the_frame(tmp_path, tiles, capsys):
|
|
write_replay(tmp_path, tiles)
|
|
dump = tmp_path / "vram-replay-1.bin"
|
|
data = bytearray(dump.read_bytes())
|
|
data[5 * 160 + 2] ^= 0x01 # one bit, inside tile row 5, tile 0
|
|
dump.write_bytes(data)
|
|
code, out = run(tmp_path, capsys)
|
|
assert code == 1
|
|
assert "frame 1" in out
|
|
|
|
|
|
def test_a_stream_that_paints_something_else_fails(tmp_path, tiles, capsys):
|
|
"""The checker must read the stream, not bless indices.bin."""
|
|
write_replay(tmp_path, tiles)
|
|
stream = bytearray((tmp_path / "stream.bin").read_bytes())
|
|
# The last record's payload: flip the last value byte of the delta.
|
|
stream[-1] ^= 0x7F
|
|
(tmp_path / "stream.bin").write_bytes(stream)
|
|
code, out = run(tmp_path, capsys)
|
|
assert code == 1
|
|
assert "frame 1" in out
|
|
|
|
|
|
def test_a_missing_dump_fails(tmp_path, tiles, capsys):
|
|
write_replay(tmp_path, tiles)
|
|
(tmp_path / "vram-replay-1.bin").unlink()
|
|
code, out = run(tmp_path, capsys)
|
|
assert code == 1
|
|
assert "frame 1" in out
|
|
|
|
|
|
@pytest.mark.parametrize("key,value", [("TILES_DROP", 1), ("RX_BAD", 1),
|
|
("TIMEOUT", 1), ("DICT_READY", 0),
|
|
("OS_TOUCH", 1)])
|
|
def test_a_bad_status_fails(tmp_path, tiles, capsys, key, value):
|
|
write_replay(tmp_path, tiles, {key: value})
|
|
code, out = run(tmp_path, capsys)
|
|
assert code == 1
|
|
assert key in out
|
|
|
|
|
|
def test_the_clip_comparison_measures_the_replayed_error(tmp_path, tiles,
|
|
capsys):
|
|
"""``--clip`` reports BER/PSNR of the *dumped* screens, not the render.
|
|
|
|
The screens equal the render (the check above), and the render differs
|
|
from the source by exactly the dictionary's error, so the measurement and
|
|
the encoder's own count have to agree bit for bit.
|
|
"""
|
|
frames = make_tiles.synthetic_clip(CLIP_FRAMES)
|
|
clip = tmp_path / "clip"
|
|
clip.mkdir()
|
|
for n, frame in enumerate(frames):
|
|
(clip / ("frame-%04d.bin" % n)).write_bytes(frame)
|
|
write_replay(tmp_path, tiles)
|
|
|
|
code = tile_check.main(["--replay", str(tmp_path), "--clip", str(clip)])
|
|
out = capsys.readouterr().out
|
|
assert code == 0, out
|
|
expected = sum(
|
|
sum(make_tiles.tile_errors(frame, indices, tiles.dictionary))
|
|
for frame, indices in zip(frames, tiles.indices))
|
|
assert "reconstruction: %d bit(s)" % expected in out
|
|
assert "PSNR" in out and "BER" in out
|
|
|
|
|
|
def test_a_clip_that_is_not_the_source_shows_up_only_in_the_measurement(
|
|
tmp_path, tiles, capsys):
|
|
"""A wrong clip changes the reported error, not the pixel verdict."""
|
|
frames = [bytes(1024 * 160) for _ in range(CLIP_FRAMES)]
|
|
clip = tmp_path / "clip"
|
|
clip.mkdir()
|
|
for n, frame in enumerate(frames):
|
|
(clip / ("frame-%04d.bin" % n)).write_bytes(frame)
|
|
write_replay(tmp_path, tiles)
|
|
|
|
code = tile_check.main(["--replay", str(tmp_path), "--clip", str(clip)])
|
|
out = capsys.readouterr().out
|
|
assert code == 0, out # the screen still equals the render
|
|
assert "reconstruction: 0 bit(s)" not in out
|