test(assets): a truncated RLE8 stream must be refused, not painted (red)
1 of 3 new guards RED, on an ASSERT about behaviour: `DID NOT RAISE ExtractionError` on all four cuts. The other two are the section's own known-positives and pass by describing what is already true -- where each cut lands in the fixture's opcode stream, and that the UNCOMPRESSED BMP path already refuses the same shape with `asset_samples_invalid`. Measured by PM 2026-09-19 on a real R761 asset (352x548 = 192 896 pixels): a stream cut to 90 % was carried with 13 923 pixels wrong, to 50 % with 95 890, to 10 % with 166 525 -- no code, no row, a partly blank PNG standing under a content address that says it holds the source's pixels. The test brings its OWN permissive RLE8 decoder, so the pixel cost of each cut is a number this file computes (8, 11, 0 and 0 of 32) rather than the package agreeing with itself; two of the four cuts lose no pixel at all, which is the whole reason the rule has to be the terminator and not a pixel count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
28f879849c
commit
c2080c4b34
1 changed files with 109 additions and 0 deletions
|
|
@ -503,3 +503,112 @@ def test_the_refused_image_leaves_a_row_the_reader_can_see(tmp_path: Path) -> No
|
|||
assert "skjema.tiff" in text
|
||||
log = (bundle / corpus.LOG_NAME).read_text("utf-8")
|
||||
assert "image" in log.lower()
|
||||
|
||||
|
||||
# --- 7. a truncated RLE8 stream is refused, never painted -------------------
|
||||
|
||||
#: The code the UNCOMPRESSED path already raises for a body that stops short
|
||||
#: ("refusing to pad, because a short buffer means the header was read wrong").
|
||||
#: Written out here rather than imported, like the two codes above.
|
||||
CODE_SAMPLES_INVALID = "asset_samples_invalid"
|
||||
|
||||
|
||||
def rle8_indices(stream: bytes, width: int, height: int) -> bytes:
|
||||
"""A PERMISSIVE RLE8 decoder, written in this file, top-down.
|
||||
|
||||
It paints what the stream holds and leaves the rest at index 0. That is
|
||||
what the format says about a SKIPPED pixel and what every decoder does
|
||||
with a stream that simply stops -- which is exactly why a truncation is
|
||||
invisible without a check: no decoder disagrees, they all agree on the
|
||||
wrong picture. Its job here is to MEASURE what each cut costs, so the
|
||||
guard below is pinned to a pixel count this file computed rather than to
|
||||
the package's opinion of itself.
|
||||
"""
|
||||
rows = [bytearray(width) for _ in range(height)]
|
||||
position, end, x, y = 0, len(stream), 0, 0
|
||||
while position + 1 < end:
|
||||
count, value = stream[position], stream[position + 1]
|
||||
position += 2
|
||||
if count:
|
||||
if 0 <= y < height and x < width:
|
||||
stop = min(x + count, width)
|
||||
rows[y][x:stop] = bytes((value,)) * (stop - x)
|
||||
x += count
|
||||
continue
|
||||
if value == 0:
|
||||
x, y = 0, y + 1
|
||||
elif value == 1:
|
||||
break
|
||||
elif value == 2:
|
||||
if position + 2 > end:
|
||||
break
|
||||
x += stream[position]
|
||||
y += stream[position + 1]
|
||||
position += 2
|
||||
else:
|
||||
run = stream[position : position + value]
|
||||
position += value + (value & 1)
|
||||
if 0 <= y < height and x < width:
|
||||
stop = min(x + len(run), width)
|
||||
rows[y][x:stop] = run[: stop - x]
|
||||
x += value
|
||||
rows.reverse()
|
||||
return b"".join(bytes(row) for row in rows)
|
||||
|
||||
|
||||
def test_the_cuts_below_land_on_the_opcodes_this_file_names() -> None:
|
||||
"""The fixture's own known-positive: a cut that no longer truncates
|
||||
anything would make every guard below pass over nothing."""
|
||||
assert len(RLE8_STREAM) == 36
|
||||
assert RLE8_STREAM[32:34] == bytes((0x08, 0x01)), "the last encoded run"
|
||||
assert RLE8_STREAM[20:22] == bytes((0x00, 0x08)), "the absolute block"
|
||||
assert RLE8_STREAM[34:36] == bytes((0x00, 0x01)), "end-of-bitmap"
|
||||
assert rle8_indices(RLE8_STREAM, 8, 4) == bytes(
|
||||
index for row in EXPECTED_INDICES for index in row
|
||||
)
|
||||
|
||||
|
||||
def test_a_truncated_rle8_stream_is_refused_with_a_published_code() -> None:
|
||||
"""Eval point 6 again, on the one case that was silent.
|
||||
|
||||
Measured by PM 2026-09-19 on a real R761 asset (352x548 = 192 896 pixels):
|
||||
a stream cut to 90 % carried with 13 923 pixels wrong, to 50 % with 95 890,
|
||||
to 10 % with 166 525 -- no code, no row, a partly blank PNG under a name
|
||||
that says it holds the source's pixels. The uncompressed path already
|
||||
refuses the same shape. NO PIXEL MAY BE GUESSED: either every one of them
|
||||
is decoded from the stream, or the picture is refused.
|
||||
|
||||
THE RULE IS THE TERMINATOR, and it is read off the corpus rather than
|
||||
chosen: over the 19 real RLE8 assets of the frozen R761 delivery, 19 of 19
|
||||
end at an explicit end-of-bitmap escape, that escape is the stream's LAST
|
||||
two bytes on 19 of 19, and `biSizeImage` equals the available bytes on
|
||||
19 of 19. So a stream that runs out before its terminator is refused, and
|
||||
so is one that is whole but never states it is finished -- the two are the
|
||||
same bytes from a reader's side, and the measurement says no real writer
|
||||
here produces the second.
|
||||
"""
|
||||
full = rle8_indices(RLE8_STREAM, 8, 4)
|
||||
cuts = {
|
||||
"cut in the middle of an encoded run": (RLE8_STREAM[:33], 8),
|
||||
"cut in the middle of an absolute block": (RLE8_STREAM[:26], 11),
|
||||
"cut immediately before end-of-bitmap": (RLE8_STREAM[:34], 0),
|
||||
"ends with end-of-line instead of end-of-bitmap": (RLE8_STREAM[:34] + bytes((0, 0)), 0),
|
||||
}
|
||||
for label, (stream, lost) in cuts.items():
|
||||
differ = sum(a != b for a, b in zip(full, rle8_indices(stream, 8, 4)))
|
||||
assert differ == lost, f"{label}: {differ} pixels differ, this file says {lost}"
|
||||
with pytest.raises(ExtractionError) as caught:
|
||||
read_image(bmp_rle8(8, 4, PALETTE, stream), name="kuttet.bmp")
|
||||
assert caught.value.code == CODE_SAMPLES_INVALID, f"{label}: {caught.value.code}"
|
||||
|
||||
# The control: the refusal is about the cut, not about the fixture.
|
||||
whole = read_image(bmp_rle8(8, 4, PALETTE, RLE8_STREAM), name="hel.bmp")
|
||||
assert whole.media_type == "image/png"
|
||||
|
||||
|
||||
def test_the_uncompressed_path_refuses_the_same_shape() -> None:
|
||||
"""The twin this rule was aligned to, so the two BMP paths cannot drift."""
|
||||
short = bmp_24(RGB_ROWS)[:-30]
|
||||
with pytest.raises(ExtractionError) as caught:
|
||||
read_image(short, name="kort.bmp")
|
||||
assert caught.value.code == CODE_SAMPLES_INVALID, caught.value.code
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue