"""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)