mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 16:58:06 +00:00
Investigating a user report that Vegas scroll mode doesn't update scores or game status. Root cause: PR #299 (Mar 28) added a mechanism so a live score change reached the ticker within a few seconds instead of waiting for a full scroll cycle -- _tick_plugin_updates_for_vegas() diffed plugin_last_update timestamps to detect which plugins got fresh data and called coordinator.mark_plugin_updated() for each, and should_recompose() checked has_pending_updates_for_visible_segments() to trigger an immediate hot-swap. PR #330 (May 14, multi-display wireless sync) refactored both call sites while adding sync support and silently deleted this entire mechanism -- not just gated it behind the new sync-mode deferral it legitimately needed, but removed it outright. The result: VegasModeCoordinator. mark_plugin_updated() and StreamManager.has_pending_updates_for_visible_ segments() have been fully implemented but never called from anywhere since. Vegas mode's only remaining freshness sources are a 5s content cache TTL (fine) and full recompose at cycle boundaries, which depending on min/max_cycle_duration can be minutes away -- so live scores/status can sit stale far longer than a user would expect from a "live" ticker. Fix: - Restored _tick_plugin_updates_for_vegas() in display_controller.py, wired as the Vegas coordinator's update callback in place of the plain _tick_plugin_updates(). Diffs plugin_last_update before/after the tick and calls vegas_coordinator.mark_plugin_updated(plugin_id) for each plugin that actually got new data (rather than returning the list, since the callback interface no longer consumes a return value). - Restored the has_pending_updates_for_visible_segments() check in render_pipeline.should_recompose(), positioned after (not instead of) the sync-mode early return PR #330 added, so standalone installations regain immediate refresh while synced leader/follower pairs correctly keep deferring hot-swaps to cycle boundaries as PR #330 intended. Test plan: - Added test_display_controller_vegas_tick.py and test_vegas_render_pipeline_recompose.py -- neither area had any prior test coverage, which is very likely why this regression went unnoticed for ~2.5 months. - Verified both new test files fail against the pre-fix code (swapped in the current main versions of both files) with exactly the expected errors -- AttributeError for the deleted method, and the recompose assertion returning False instead of True -- then pass against the fix. - Confirmed the sync-mode deferral this restoration must not break still holds: test_sync_active_defers_pending_updates_to_cycle_boundary. - Full related suite (test_vegas_plugin_adapter, test_vegas_config, test_display_controller_plugin_toggle, test_display_controller_ optimizations, test_plugin_system): 108 passed, 1 pre-existing failure unrelated to this change (test_circuit_breaker, stale mock signature). - Full CI plugin-safety suite (test_harness, test_visual_rendering, test_plugin_matrix): 52 passed, 2 pre-existing skips.
73 lines
3.3 KiB
Python
73 lines
3.3 KiB
Python
"""
|
|
Regression tests for RenderPipeline.should_recompose()'s pending-updates check.
|
|
|
|
PR #299 added a check so a plugin's live score/status change (a "pending
|
|
update" in StreamManager) triggers a hot-swap within a few seconds instead
|
|
of waiting for a full scroll cycle to complete. PR #330 (multi-display sync)
|
|
refactored should_recompose() and dropped that check entirely -- not just
|
|
gated behind the new sync-mode deferral it added, but removed outright, so
|
|
even standalone (non-sync) installations silently lost live-refresh and fell
|
|
back to waiting for full cycle boundaries (which, depending on
|
|
min/max_cycle_duration, can be minutes).
|
|
"""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from src.vegas_mode.config import VegasModeConfig
|
|
from src.vegas_mode.render_pipeline import RenderPipeline
|
|
|
|
|
|
class FakeDisplayManager:
|
|
width = 64
|
|
height = 32
|
|
|
|
|
|
def _make_pipeline(sync_manager=None):
|
|
stream_manager = MagicMock()
|
|
stream_manager.get_buffer_status.return_value = {'staging_count': 0}
|
|
pipeline = RenderPipeline(VegasModeConfig(), FakeDisplayManager(), stream_manager)
|
|
pipeline.sync_manager = sync_manager
|
|
return pipeline, stream_manager
|
|
|
|
|
|
class TestShouldRecompose:
|
|
def test_cycle_complete_always_recomposes(self):
|
|
pipeline, stream_manager = _make_pipeline()
|
|
pipeline._cycle_complete = True
|
|
stream_manager.has_pending_updates_for_visible_segments.return_value = False
|
|
assert pipeline.should_recompose() is True
|
|
|
|
def test_no_pending_updates_no_staging_does_not_recompose(self):
|
|
pipeline, stream_manager = _make_pipeline()
|
|
stream_manager.has_pending_updates_for_visible_segments.return_value = False
|
|
assert pipeline.should_recompose() is False
|
|
|
|
def test_pending_updates_on_visible_segment_triggers_recompose(self):
|
|
"""The actual regression: a live-updated plugin currently in view
|
|
must trigger a recompose instead of waiting for cycle end."""
|
|
pipeline, stream_manager = _make_pipeline()
|
|
stream_manager.has_pending_updates_for_visible_segments.return_value = True
|
|
assert pipeline.should_recompose() is True
|
|
|
|
def test_staging_buffer_content_triggers_recompose(self):
|
|
pipeline, stream_manager = _make_pipeline()
|
|
stream_manager.get_buffer_status.return_value = {'staging_count': 1}
|
|
stream_manager.has_pending_updates_for_visible_segments.return_value = False
|
|
assert pipeline.should_recompose() is True
|
|
|
|
def test_sync_active_defers_pending_updates_to_cycle_boundary(self):
|
|
"""Sync-mode deferral (PR #330's actual intent) must still hold:
|
|
pending updates alone must NOT trigger a mid-cycle hot-swap when a
|
|
follower display is attached, since that causes a visible
|
|
freeze+jump on the follower. This must keep working after
|
|
restoring the non-sync pending-updates check above."""
|
|
pipeline, stream_manager = _make_pipeline(sync_manager=MagicMock())
|
|
stream_manager.has_pending_updates_for_visible_segments.return_value = True
|
|
assert pipeline.should_recompose() is False
|
|
|
|
def test_sync_active_still_recomposes_on_cycle_complete(self):
|
|
pipeline, stream_manager = _make_pipeline(sync_manager=MagicMock())
|
|
pipeline._cycle_complete = True
|
|
stream_manager.has_pending_updates_for_visible_segments.return_value = True
|
|
assert pipeline.should_recompose() is True
|