mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-10 21:28:05 +00:00
test(harness): flag a mode that draws nothing without reporting it
The controller skips a mode whose display() returns False and treats
anything else -- including None -- as "content was shown". A mode that
draws nothing and does not return False is therefore never skipped, and
because a mode switch clears the panel first, it sits on a blank screen
for its whole display duration. Two sports plugins shipped exactly that.
The harness rendered those modes and passed them, because it called
display() and discarded the result. Capture it, and warn when a render
produced no lit pixels while claiming content.
Warn-only by default, and deliberately so: a scroll mode's first frame
is legitimately its blank scroll-in buffer, which is 42 of these on the
F1 scoreboard alone. Plugins whose modes are known to draw on their
fixture data can opt into failing via harness.json {"empty_check":
"strict"}, matching how the fill check is staged.
Worth being clear about the limit: this only sees what the fixtures
render. It would not have caught the sports bug, whose fixture seeds
games so the empty path never renders -- that needs the source-level
gate in the plugins repo. What it does catch is the same mistake in any
plugin whose empty state the harness does happen to reach, which is
coverage there was none of before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
This commit is contained in:
co-authored by
Claude Opus 5
parent
fc25a70d75
commit
cad279a7d2
@@ -0,0 +1,103 @@
|
||||
"""Tests for the harness empty-frame check (src/plugin_system/testing/harness.py).
|
||||
|
||||
The display controller skips a mode whose display() returns False and treats
|
||||
anything else -- including None -- as "content was shown". A mode that draws
|
||||
nothing without returning False is therefore never skipped, and since a mode
|
||||
switch clears the panel first, it sits on a blank screen for its whole display
|
||||
duration.
|
||||
|
||||
Two sports plugins shipped exactly that: their display() returned None on every
|
||||
path, so an out-of-season league held a blank panel instead of being rotated
|
||||
past. The harness rendered those modes and passed them, because it discarded
|
||||
the return value entirely.
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from src.plugin_system.testing.harness import RenderResult, check_empty_claimed
|
||||
|
||||
|
||||
def _blank(w=64, h=32):
|
||||
return Image.new("RGB", (w, h), (0, 0, 0))
|
||||
|
||||
|
||||
def _drawn(w=64, h=32):
|
||||
img = _blank(w, h)
|
||||
img.paste(Image.new("RGB", (10, 10), (255, 255, 255)), (5, 5))
|
||||
return img
|
||||
|
||||
|
||||
def _result(image, returned=None, **kw):
|
||||
return RenderResult("p", 64, 32, "mode", image=image,
|
||||
display_returned=returned, **kw)
|
||||
|
||||
|
||||
class TestCheckEmptyClaimed:
|
||||
def test_blank_frame_returning_none_is_flagged(self):
|
||||
# The shape that shipped: nothing drawn, nothing reported.
|
||||
r = _result(_blank(), returned=None)
|
||||
check_empty_claimed([r])
|
||||
assert r.empty_claimed is True
|
||||
|
||||
def test_blank_frame_returning_true_is_flagged(self):
|
||||
# Just as broken, and more explicit about it.
|
||||
r = _result(_blank(), returned=True)
|
||||
check_empty_claimed([r])
|
||||
assert r.empty_claimed is True
|
||||
|
||||
def test_blank_frame_returning_false_is_fine(self):
|
||||
# The plugin correctly said "no content"; the controller will skip it.
|
||||
r = _result(_blank(), returned=False)
|
||||
check_empty_claimed([r])
|
||||
assert r.empty_claimed is None
|
||||
|
||||
def test_a_drawn_frame_is_fine_whatever_it_returns(self):
|
||||
for returned in (None, True, False):
|
||||
r = _result(_drawn(), returned=returned)
|
||||
check_empty_claimed([r])
|
||||
assert r.empty_claimed is None, returned
|
||||
|
||||
def test_near_black_still_counts_as_drawn(self):
|
||||
# Guard the threshold: content dim enough to look black to the eye is
|
||||
# still content, and flagging it would train people to ignore this.
|
||||
img = _blank()
|
||||
img.paste(Image.new("RGB", (4, 4), (60, 60, 60)), (2, 2))
|
||||
r = _result(img, returned=None)
|
||||
check_empty_claimed([r])
|
||||
assert r.empty_claimed is None
|
||||
|
||||
|
||||
class TestWarnVersusStrict:
|
||||
def test_warn_only_by_default(self):
|
||||
# A scroll mode's first frame is legitimately its blank scroll-in
|
||||
# buffer, so this must not fail a run unless opted in.
|
||||
r = _result(_blank(), returned=None)
|
||||
check_empty_claimed([r])
|
||||
assert r.empty_ok is None
|
||||
assert r.ok is True
|
||||
|
||||
def test_strict_fails_the_result(self):
|
||||
r = _result(_blank(), returned=None)
|
||||
check_empty_claimed([r], strict=True)
|
||||
assert r.empty_ok is False
|
||||
assert r.ok is False
|
||||
|
||||
def test_strict_still_allows_an_honest_false(self):
|
||||
r = _result(_blank(), returned=False)
|
||||
check_empty_claimed([r], strict=True)
|
||||
assert r.empty_ok is None
|
||||
assert r.ok is True
|
||||
|
||||
|
||||
class TestSkippedResults:
|
||||
def test_a_crashed_render_is_left_alone(self):
|
||||
# error already fails the result; adding a second reason just muddies
|
||||
# the report.
|
||||
r = _result(None, returned=None, error="boom")
|
||||
check_empty_claimed([r], strict=True)
|
||||
assert r.empty_claimed is None
|
||||
|
||||
def test_a_result_with_no_image_is_left_alone(self):
|
||||
r = _result(None, returned=None)
|
||||
check_empty_claimed([r], strict=True)
|
||||
assert r.empty_claimed is None
|
||||
Reference in New Issue
Block a user