mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
Two faults the 60 fps work exposed, both in the delivery tooling: * `demo.sh --no-load` waited for the terminal to answer ping. A loaded payload owns the LANCE and stops the OS's stack with it, so that gate can never pass once the payload is running -- the recovery path documented for "already jumped" deadlocked for its full 180 s. The gate now runs only when this invocation is going to load the payload. * `rdebug_load.py` used a 5 s socket timeout and no retry, so a readback peek that timed out on a freshly booted OS failed a load whose pokes had already been acknowledged. Timeout is 15 s (--timeout) and the readback retries once before it is believed. Tests: the retry path is pinned with a fake remote that times out on the first peek (one timeout, then the successful pass over every chunk) and one that never answers (it must raise rather than loop). 9 tests in harness/badapple pass.
111 lines
3.9 KiB
Python
111 lines
3.9 KiB
Python
"""Tests for the rdebug loader and the clip builder.
|
|
|
|
The pieces that touch a live terminal cannot be tested here; what is tested is
|
|
everything that decides *what* gets written where, because those are the parts a
|
|
wrong assumption silently corrupts: the chunking arithmetic, the linkage the
|
|
entry point is read from, and the 1-bpp packing the terminal actually reads.
|
|
"""
|
|
import struct
|
|
|
|
import pytest
|
|
|
|
from harness.badapple import clip_from_video
|
|
from harness.badapple import rdebug_load
|
|
|
|
# --- chunking -------------------------------------------------------------
|
|
|
|
def test_chunks_are_transfer_sized_and_cover_the_image_once():
|
|
data = bytes(range(200)) * 23 # 4,600 B: not a multiple of 512
|
|
parts = rdebug_load.chunks(data, 512)
|
|
assert [len(p) for p in parts] == [512] * 8 + [504]
|
|
assert b"".join(parts) == data
|
|
|
|
|
|
def test_chunks_refuse_a_size_the_firmware_cannot_take():
|
|
# 1024 is rejected by the firmware: it reads the body's first word as the
|
|
# next opcode ("Invalid command 0xA5A5A5A5" on a 0xA5-filled chunk).
|
|
with pytest.raises(ValueError):
|
|
rdebug_load.chunks(b"x", 1024)
|
|
with pytest.raises(ValueError):
|
|
rdebug_load.chunks(b"x", 0)
|
|
|
|
|
|
def test_addresses_advance_by_the_chunk_length_in_bits():
|
|
base = 0x018C0000
|
|
assert rdebug_load.chunk_addr(base, 0) == base
|
|
assert rdebug_load.chunk_addr(base, 1) == base + 512 * 8
|
|
|
|
|
|
# --- linkage --------------------------------------------------------------
|
|
|
|
def test_env_file_gives_the_entry_and_the_pool_size(tmp_path):
|
|
env = tmp_path / "vidstream.env"
|
|
env.write_text("POOL_BYTES=249856\nSTATIC_PTR=0x01C00080\n"
|
|
"ENTRY_PTR=0x018D0270\n")
|
|
got = rdebug_load.read_env(env)
|
|
assert got["ENTRY_PTR"] == 0x018D0270
|
|
assert got["POOL_BYTES"] == 249856
|
|
|
|
|
|
def test_missing_env_file_says_so_instead_of_guessing(tmp_path, capsys):
|
|
rc = rdebug_load.main(["--image", "x.bin", "--env", str(tmp_path / "no.env")])
|
|
assert rc == 2
|
|
err = capsys.readouterr().err
|
|
assert "no build environment" in err and "build-stream.sh" in err
|
|
|
|
|
|
# --- readback ------------------------------------------------------------
|
|
|
|
class _Flaky:
|
|
"""A remote end whose first peek times out, as a busy OS does."""
|
|
|
|
def __init__(self, transfers, fail_times=1):
|
|
self.mem = dict(transfers)
|
|
self.fail = fail_times
|
|
self.calls = 0
|
|
|
|
def peek(self, addr, length):
|
|
self.calls += 1
|
|
if self.fail:
|
|
self.fail -= 1
|
|
raise TimeoutError("timed out")
|
|
return self.mem[addr][:length]
|
|
|
|
|
|
def test_readback_retries_a_stalled_peek_instead_of_failing_the_load():
|
|
transfers = rdebug_load.plan(b"0123456789", 0x018C0000, 4)
|
|
rd = _Flaky(transfers, fail_times=1)
|
|
assert rdebug_load.readback(rd, transfers) == b"0123456789"
|
|
# The retry re-reads the whole image: one timed-out chunk plus the three
|
|
# chunks of the successful pass.
|
|
assert rd.calls == len(transfers) + 1
|
|
|
|
|
|
def test_readback_gives_up_after_the_retry():
|
|
transfers = rdebug_load.plan(b"0123", 0x018C0000, 4)
|
|
rd = _Flaky(transfers, fail_times=9)
|
|
with pytest.raises(TimeoutError):
|
|
rdebug_load.readback(rd, transfers)
|
|
|
|
|
|
# --- the clip format ------------------------------------------------------
|
|
|
|
def test_packed_rows_put_pixel_zero_on_bit_zero_of_byte_zero():
|
|
"""The terminal reads bit 0 of a byte as the leftmost pixel.
|
|
|
|
Same rule the payload's encoder uses (`vidsrc.Rows.pack_row`), and the one
|
|
an MSB-first packer gets wrong in a way that only shows on a display.
|
|
"""
|
|
pixels = [[0] * 16]
|
|
pixels[0][0] = 1 # leftmost pixel of the row
|
|
pixels[0][15] = 1 # last pixel of the second byte
|
|
row = clip_from_video.pack_row(pixels[0])
|
|
assert len(row) == 2
|
|
assert row[0] == 0x01
|
|
assert row[1] == 0x80
|
|
|
|
|
|
def test_pack_row_rejects_a_short_row():
|
|
with pytest.raises(ValueError):
|
|
clip_from_video.pack_row([0] * 15)
|