feat(tests): improve coverage to 56%, add benchmark regression tests

- Add EffectPlugin ABC with @abstractmethod decorators for interface enforcement
- Add runtime interface checking in discover_plugins() with issubclass()
- Add EffectContext factory with sensible defaults
- Standardize Display __init__ (remove redundant init in TerminalDisplay)
- Document effect behavior when ticker_height=0
- Evaluate legacy effects: document coexistence, no deprecation needed
- Research plugin patterns (VST, Python entry points)
- Fix pysixel dependency (removed broken dependency)

Test coverage improvements:
- Add DisplayRegistry tests
- Add MultiDisplay tests
- Add SixelDisplay tests
- Add controller._get_display tests
- Add effects controller command handling tests
- Add benchmark regression tests (@pytest.mark.benchmark)
- Add pytest marker for benchmark tests in pyproject.toml

Documentation updates:
- Update AGENTS.md with 56% coverage stats and effect plugin docs
- Update README.md with Sixel display mode and benchmark commands
- Add new modules to architecture section
This commit is contained in:
2026-03-15 23:26:10 -07:00
parent dcd31469a5
commit 9e4d54a82e
17 changed files with 768 additions and 45 deletions

View File

@@ -2,7 +2,10 @@
Tests for engine.display module.
"""
from engine.display import NullDisplay, TerminalDisplay
from unittest.mock import MagicMock
from engine.display import DisplayRegistry, NullDisplay, TerminalDisplay
from engine.display.backends.multi import MultiDisplay
class TestDisplayProtocol:
@@ -25,6 +28,66 @@ class TestDisplayProtocol:
assert hasattr(display, "cleanup")
class TestDisplayRegistry:
"""Tests for DisplayRegistry class."""
def setup_method(self):
"""Reset registry before each test."""
DisplayRegistry._backends = {}
DisplayRegistry._initialized = False
def test_register_adds_backend(self):
"""register adds a backend to the registry."""
DisplayRegistry.register("test", TerminalDisplay)
assert DisplayRegistry.get("test") == TerminalDisplay
def test_register_case_insensitive(self):
"""register is case insensitive."""
DisplayRegistry.register("TEST", TerminalDisplay)
assert DisplayRegistry.get("test") == TerminalDisplay
def test_get_returns_none_for_unknown(self):
"""get returns None for unknown backend."""
assert DisplayRegistry.get("unknown") is None
def test_list_backends_returns_all(self):
"""list_backends returns all registered backends."""
DisplayRegistry.register("a", TerminalDisplay)
DisplayRegistry.register("b", NullDisplay)
backends = DisplayRegistry.list_backends()
assert "a" in backends
assert "b" in backends
def test_create_returns_instance(self):
"""create returns a display instance."""
DisplayRegistry.register("test", NullDisplay)
display = DisplayRegistry.create("test")
assert isinstance(display, NullDisplay)
def test_create_returns_none_for_unknown(self):
"""create returns None for unknown backend."""
display = DisplayRegistry.create("unknown")
assert display is None
def test_initialize_registers_defaults(self):
"""initialize registers default backends."""
DisplayRegistry.initialize()
assert DisplayRegistry.get("terminal") == TerminalDisplay
assert DisplayRegistry.get("null") == NullDisplay
from engine.display.backends.sixel import SixelDisplay
from engine.display.backends.websocket import WebSocketDisplay
assert DisplayRegistry.get("websocket") == WebSocketDisplay
assert DisplayRegistry.get("sixel") == SixelDisplay
def test_initialize_idempotent(self):
"""initialize can be called multiple times safely."""
DisplayRegistry.initialize()
DisplayRegistry._backends["custom"] = TerminalDisplay
DisplayRegistry.initialize()
assert "custom" in DisplayRegistry.list_backends()
class TestTerminalDisplay:
"""Tests for TerminalDisplay class."""
@@ -77,3 +140,62 @@ class TestNullDisplay:
"""cleanup does nothing."""
display = NullDisplay()
display.cleanup()
class TestMultiDisplay:
"""Tests for MultiDisplay class."""
def test_init_stores_dimensions(self):
"""init stores dimensions and forwards to displays."""
mock_display1 = MagicMock()
mock_display2 = MagicMock()
multi = MultiDisplay([mock_display1, mock_display2])
multi.init(120, 40)
assert multi.width == 120
assert multi.height == 40
mock_display1.init.assert_called_once_with(120, 40)
mock_display2.init.assert_called_once_with(120, 40)
def test_show_forwards_to_all_displays(self):
"""show forwards buffer to all displays."""
mock_display1 = MagicMock()
mock_display2 = MagicMock()
multi = MultiDisplay([mock_display1, mock_display2])
buffer = ["line1", "line2"]
multi.show(buffer)
mock_display1.show.assert_called_once_with(buffer)
mock_display2.show.assert_called_once_with(buffer)
def test_clear_forwards_to_all_displays(self):
"""clear forwards to all displays."""
mock_display1 = MagicMock()
mock_display2 = MagicMock()
multi = MultiDisplay([mock_display1, mock_display2])
multi.clear()
mock_display1.clear.assert_called_once()
mock_display2.clear.assert_called_once()
def test_cleanup_forwards_to_all_displays(self):
"""cleanup forwards to all displays."""
mock_display1 = MagicMock()
mock_display2 = MagicMock()
multi = MultiDisplay([mock_display1, mock_display2])
multi.cleanup()
mock_display1.cleanup.assert_called_once()
mock_display2.cleanup.assert_called_once()
def test_empty_displays_list(self):
"""handles empty displays list gracefully."""
multi = MultiDisplay([])
multi.init(80, 24)
multi.show(["test"])
multi.clear()
multi.cleanup()