forked from genewildish/Mainline
- 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
242 lines
9.4 KiB
Python
242 lines
9.4 KiB
Python
"""
|
|
Tests for engine.effects.controller module.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from engine.effects.controller import (
|
|
_format_stats,
|
|
handle_effects_command,
|
|
set_effect_chain_ref,
|
|
show_effects_menu,
|
|
)
|
|
|
|
|
|
class TestHandleEffectsCommand:
|
|
"""Tests for handle_effects_command function."""
|
|
|
|
def test_list_effects(self):
|
|
"""list command returns formatted effects list."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_plugin.config.enabled = True
|
|
mock_plugin.config.intensity = 0.5
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
with patch("engine.effects.controller._get_effect_chain") as mock_chain:
|
|
mock_chain.return_value.get_order.return_value = ["noise"]
|
|
|
|
result = handle_effects_command("/effects list")
|
|
|
|
assert "noise: ON" in result
|
|
assert "intensity=0.5" in result
|
|
|
|
def test_enable_effect(self):
|
|
"""enable command calls registry.enable."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_registry.return_value.get.return_value = mock_plugin
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
result = handle_effects_command("/effects noise on")
|
|
|
|
assert "Enabled: noise" in result
|
|
mock_registry.return_value.enable.assert_called_once_with("noise")
|
|
|
|
def test_disable_effect(self):
|
|
"""disable command calls registry.disable."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_registry.return_value.get.return_value = mock_plugin
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
result = handle_effects_command("/effects noise off")
|
|
|
|
assert "Disabled: noise" in result
|
|
mock_registry.return_value.disable.assert_called_once_with("noise")
|
|
|
|
def test_set_intensity(self):
|
|
"""intensity command sets plugin intensity."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_plugin.config.intensity = 0.5
|
|
mock_registry.return_value.get.return_value = mock_plugin
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
result = handle_effects_command("/effects noise intensity 0.8")
|
|
|
|
assert "intensity to 0.8" in result
|
|
assert mock_plugin.config.intensity == 0.8
|
|
|
|
def test_invalid_intensity_range(self):
|
|
"""intensity outside 0.0-1.0 returns error."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_registry.return_value.get.return_value = mock_plugin
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
result = handle_effects_command("/effects noise intensity 1.5")
|
|
|
|
assert "between 0.0 and 1.0" in result
|
|
|
|
def test_reorder_pipeline(self):
|
|
"""reorder command calls chain.reorder."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_registry.return_value.list_all.return_value = {}
|
|
|
|
with patch("engine.effects.controller._get_effect_chain") as mock_chain:
|
|
mock_chain_instance = MagicMock()
|
|
mock_chain_instance.reorder.return_value = True
|
|
mock_chain.return_value = mock_chain_instance
|
|
|
|
result = handle_effects_command("/effects reorder noise,fade")
|
|
|
|
assert "Reordered pipeline" in result
|
|
mock_chain_instance.reorder.assert_called_once_with(["noise", "fade"])
|
|
|
|
def test_reorder_failure(self):
|
|
"""reorder returns error on failure."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_registry.return_value.list_all.return_value = {}
|
|
|
|
with patch("engine.effects.controller._get_effect_chain") as mock_chain:
|
|
mock_chain_instance = MagicMock()
|
|
mock_chain_instance.reorder.return_value = False
|
|
mock_chain.return_value = mock_chain_instance
|
|
|
|
result = handle_effects_command("/effects reorder bad")
|
|
|
|
assert "Failed to reorder" in result
|
|
|
|
def test_unknown_effect(self):
|
|
"""unknown effect returns error."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_registry.return_value.list_all.return_value = {}
|
|
|
|
result = handle_effects_command("/effects unknown on")
|
|
|
|
assert "Unknown effect" in result
|
|
|
|
def test_unknown_command(self):
|
|
"""unknown command returns error."""
|
|
result = handle_effects_command("/unknown")
|
|
assert "Unknown command" in result
|
|
|
|
def test_non_effects_command(self):
|
|
"""non-effects command returns error."""
|
|
result = handle_effects_command("not a command")
|
|
assert "Unknown command" in result
|
|
|
|
def test_invalid_intensity_value(self):
|
|
"""invalid intensity value returns error."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_registry.return_value.get.return_value = mock_plugin
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
result = handle_effects_command("/effects noise intensity bad")
|
|
|
|
assert "Invalid intensity" in result
|
|
|
|
def test_missing_action(self):
|
|
"""missing action returns usage."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_registry.return_value.get.return_value = mock_plugin
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
result = handle_effects_command("/effects noise")
|
|
|
|
assert "Usage" in result
|
|
|
|
def test_stats_command(self):
|
|
"""stats command returns formatted stats."""
|
|
with patch("engine.effects.controller.get_monitor") as mock_monitor:
|
|
mock_monitor.return_value.get_stats.return_value = {
|
|
"frame_count": 100,
|
|
"pipeline": {"avg_ms": 1.5, "min_ms": 1.0, "max_ms": 2.0},
|
|
"effects": {},
|
|
}
|
|
|
|
result = handle_effects_command("/effects stats")
|
|
|
|
assert "Performance Stats" in result
|
|
|
|
def test_list_only_effects(self):
|
|
"""list command works with just /effects."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_plugin.config.enabled = False
|
|
mock_plugin.config.intensity = 0.5
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
with patch("engine.effects.controller._get_effect_chain") as mock_chain:
|
|
mock_chain.return_value = None
|
|
|
|
result = handle_effects_command("/effects")
|
|
|
|
assert "noise: OFF" in result
|
|
|
|
|
|
class TestShowEffectsMenu:
|
|
"""Tests for show_effects_menu function."""
|
|
|
|
def test_returns_formatted_menu(self):
|
|
"""returns formatted effects menu."""
|
|
with patch("engine.effects.controller.get_registry") as mock_registry:
|
|
mock_plugin = MagicMock()
|
|
mock_plugin.config.enabled = True
|
|
mock_plugin.config.intensity = 0.75
|
|
mock_registry.return_value.list_all.return_value = {"noise": mock_plugin}
|
|
|
|
with patch("engine.effects.controller._get_effect_chain") as mock_chain:
|
|
mock_chain_instance = MagicMock()
|
|
mock_chain_instance.get_order.return_value = ["noise"]
|
|
mock_chain.return_value = mock_chain_instance
|
|
|
|
result = show_effects_menu()
|
|
|
|
assert "EFFECTS MENU" in result
|
|
assert "noise" in result
|
|
|
|
|
|
class TestFormatStats:
|
|
"""Tests for _format_stats function."""
|
|
|
|
def test_returns_error_when_no_monitor(self):
|
|
"""returns error when monitor unavailable."""
|
|
with patch("engine.effects.controller.get_monitor") as mock_monitor:
|
|
mock_monitor.return_value.get_stats.return_value = {"error": "No data"}
|
|
|
|
result = _format_stats()
|
|
|
|
assert "No data" in result
|
|
|
|
def test_formats_pipeline_stats(self):
|
|
"""formats pipeline stats correctly."""
|
|
with patch("engine.effects.controller.get_monitor") as mock_monitor:
|
|
mock_monitor.return_value.get_stats.return_value = {
|
|
"frame_count": 50,
|
|
"pipeline": {"avg_ms": 2.5, "min_ms": 2.0, "max_ms": 3.0},
|
|
"effects": {"noise": {"avg_ms": 0.5, "min_ms": 0.4, "max_ms": 0.6}},
|
|
}
|
|
|
|
result = _format_stats()
|
|
|
|
assert "Pipeline" in result
|
|
assert "noise" in result
|
|
|
|
|
|
class TestSetEffectChainRef:
|
|
"""Tests for set_effect_chain_ref function."""
|
|
|
|
def test_sets_global_ref(self):
|
|
"""set_effect_chain_ref updates global reference."""
|
|
mock_chain = MagicMock()
|
|
set_effect_chain_ref(mock_chain)
|
|
|
|
from engine.effects.controller import _get_effect_chain
|
|
|
|
result = _get_effect_chain()
|
|
assert result == mock_chain
|