feat(pygame): add glyph caching for performance improvement
- Add _glyph_cache dict to PygameDisplay.__init__ - Cache font.render() results per (char, fg, bg) combination - Use blits() for batch rendering instead of individual blit calls - Add TestRenderBorder tests (8 new tests) for border rendering - Update NullDisplay.show() to support border=True for consistency - Add test_show_with_border_uses_render_border for TerminalDisplay Closes #28
This commit is contained in:
@@ -3,9 +3,11 @@ Tests for engine.display module.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from engine.display import DisplayRegistry, NullDisplay, TerminalDisplay
|
||||
import pytest
|
||||
|
||||
from engine.display import DisplayRegistry, NullDisplay, TerminalDisplay, render_border
|
||||
from engine.display.backends.multi import MultiDisplay
|
||||
|
||||
|
||||
@@ -133,7 +135,6 @@ class TestTerminalDisplay:
|
||||
The fix adds \\033[H\\033[J (cursor home + erase down) before each frame.
|
||||
"""
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch
|
||||
|
||||
display = TerminalDisplay()
|
||||
display.init(80, 24)
|
||||
@@ -157,7 +158,6 @@ class TestTerminalDisplay:
|
||||
Regression test: Ensures each show() call includes the clear sequence.
|
||||
"""
|
||||
from io import BytesIO
|
||||
from unittest.mock import patch
|
||||
|
||||
# Use target_fps=0 to disable frame skipping in test
|
||||
display = TerminalDisplay(target_fps=0)
|
||||
@@ -193,6 +193,40 @@ class TestTerminalDisplay:
|
||||
# returns different values each call
|
||||
assert len(set(dims)) == 1, f"Dimensions should be stable, got: {set(dims)}"
|
||||
|
||||
def test_show_with_border_uses_render_border(self):
|
||||
"""show with border=True calls render_border with FPS."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
display = TerminalDisplay()
|
||||
display.init(80, 24)
|
||||
|
||||
buffer = ["line1", "line2"]
|
||||
|
||||
# Mock get_monitor to provide FPS
|
||||
mock_monitor = MagicMock()
|
||||
mock_monitor.get_stats.return_value = {
|
||||
"pipeline": {"avg_ms": 16.5},
|
||||
"frame_count": 100,
|
||||
}
|
||||
|
||||
# Mock render_border to verify it's called
|
||||
with (
|
||||
patch("engine.display.get_monitor", return_value=mock_monitor),
|
||||
patch("engine.display.render_border", wraps=render_border) as mock_render,
|
||||
):
|
||||
display.show(buffer, border=True)
|
||||
|
||||
# Verify render_border was called with correct arguments
|
||||
assert mock_render.called
|
||||
args, kwargs = mock_render.call_args
|
||||
# Arguments: buffer, width, height, fps, frame_time (positional)
|
||||
assert args[0] == buffer
|
||||
assert args[1] == 80
|
||||
assert args[2] == 24
|
||||
assert args[3] == pytest.approx(60.6, rel=0.1) # fps = 1000/16.5
|
||||
assert args[4] == pytest.approx(16.5, rel=0.1)
|
||||
assert kwargs == {} # no keyword arguments
|
||||
|
||||
|
||||
class TestNullDisplay:
|
||||
"""Tests for NullDisplay class."""
|
||||
@@ -241,6 +275,92 @@ class TestNullDisplay:
|
||||
assert display._last_buffer == ["second"]
|
||||
|
||||
|
||||
class TestRenderBorder:
|
||||
"""Tests for render_border function."""
|
||||
|
||||
def test_render_border_adds_corners(self):
|
||||
"""render_border adds corner characters."""
|
||||
from engine.display import render_border
|
||||
|
||||
buffer = ["hello", "world"]
|
||||
result = render_border(buffer, width=10, height=5)
|
||||
|
||||
assert result[0][0] in "┌┎┍" # top-left
|
||||
assert result[0][-1] in "┐┒┓" # top-right
|
||||
assert result[-1][0] in "└┚┖" # bottom-left
|
||||
assert result[-1][-1] in "┘┛┙" # bottom-right
|
||||
|
||||
def test_render_border_dimensions(self):
|
||||
"""render_border output matches requested dimensions."""
|
||||
from engine.display import render_border
|
||||
|
||||
buffer = ["line1", "line2", "line3"]
|
||||
result = render_border(buffer, width=20, height=10)
|
||||
|
||||
# Output should be exactly height lines
|
||||
assert len(result) == 10
|
||||
# Each line should be exactly width characters
|
||||
for line in result:
|
||||
assert len(line) == 20
|
||||
|
||||
def test_render_border_with_fps(self):
|
||||
"""render_border includes FPS in top border when provided."""
|
||||
from engine.display import render_border
|
||||
|
||||
buffer = ["test"]
|
||||
result = render_border(buffer, width=20, height=5, fps=60.0)
|
||||
|
||||
top_line = result[0]
|
||||
assert "FPS:60" in top_line or "FPS: 60" in top_line
|
||||
|
||||
def test_render_border_with_frame_time(self):
|
||||
"""render_border includes frame time in bottom border when provided."""
|
||||
from engine.display import render_border
|
||||
|
||||
buffer = ["test"]
|
||||
result = render_border(buffer, width=20, height=5, frame_time=16.5)
|
||||
|
||||
bottom_line = result[-1]
|
||||
assert "16.5ms" in bottom_line
|
||||
|
||||
def test_render_border_crops_content_to_fit(self):
|
||||
"""render_border crops content to fit within borders."""
|
||||
from engine.display import render_border
|
||||
|
||||
# Buffer larger than viewport
|
||||
buffer = ["x" * 100] * 50
|
||||
result = render_border(buffer, width=20, height=10)
|
||||
|
||||
# Result shrinks to fit viewport
|
||||
assert len(result) == 10
|
||||
for line in result[1:-1]: # Skip border lines
|
||||
assert len(line) == 20
|
||||
|
||||
def test_render_border_preserves_content(self):
|
||||
"""render_border preserves content within borders."""
|
||||
from engine.display import render_border
|
||||
|
||||
buffer = ["hello world", "test line"]
|
||||
result = render_border(buffer, width=20, height=5)
|
||||
|
||||
# Content should appear in the middle rows
|
||||
content_lines = result[1:-1]
|
||||
assert any("hello world" in line for line in content_lines)
|
||||
|
||||
def test_render_border_with_small_buffer(self):
|
||||
"""render_border handles buffers smaller than viewport."""
|
||||
from engine.display import render_border
|
||||
|
||||
buffer = ["hi"]
|
||||
result = render_border(buffer, width=20, height=10)
|
||||
|
||||
# Should still produce full viewport with padding
|
||||
assert len(result) == 10
|
||||
# All lines should be full width
|
||||
for line in result:
|
||||
assert len(line) == 20
|
||||
|
||||
|
||||
class TestMultiDisplay:
|
||||
"""Tests for MultiDisplay class."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user