Files
LEDMatrix/test/plugins/test_clock_simple.py
T
Claude 911c559fd4 ci: run the whole test tree and make the plugin-safety job assert something real
The unit-tests CI job ran an explicit 24-file allowlist that had rotted:
63 of 90 test files (display, vegas, store manager, web API, web_interface)
never ran on a PR. The job now runs all of test/ (minus test/plugins, which
the plugin-safety job owns) so new test files are enrolled by default and
any exclusion needs a visible, commented --ignore.

The plugin-safety job was a green no-op: plugins/ is empty in CI, so every
test skipped with 'Manifest not found'. It now renders a bundled
deterministic fixture plugin (test/fixtures/plugins/ci-fixture-plugin,
golden images included for all 8 default sizes) via LEDMATRIX_PLUGINS_DIR,
and sets LEDMATRIX_REQUIRE_PLUGINS=1 so discovering zero plugins fails
loudly instead of skipping green. The per-plugin suites document that they
target dev machines with real plugins installed.

Coverage is now measured and enforced in exactly one place — the CI
unit-tests step (--cov=src --cov=web_interface --cov-fail-under=45, from a
measured 47% baseline). pytest.ini previously declared --cov-fail-under=30
but CI always passed --no-cov, so the gate had never run anywhere; local
pytest is now coverage-free and fast.

Enabling the 63 unenrolled files surfaced three cases of test rot, fixed
here: test_display_controller_vegas_tick.py could not collect without the
hardware rgbmatrix module (now uses the emulator convention), the
state-reconciliation unrecoverable-cache tests broke when production added
the is_plugin_uninstalled tombstone check (bare Mock returned truthy),
and test_get_system_status assumed the optional psutil dependency
(now installed via requirements-test.txt and guarded by importorskip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NohXi78cwsAKtN1sCfxjUh
2026-08-06 22:30:21 +00:00

104 lines
3.7 KiB
Python

"""
Integration tests for clock-simple plugin.
Requires the real plugin to be installed (plugins/ or plugin-repos/,
or the dir named by LEDMATRIX_PLUGINS_DIR) — on machines without it,
every test here skips by design. CI covers plugin safety with the
bundled fixture plugin via test_plugin_matrix.py instead.
"""
import pytest
from test.plugins.test_plugin_base import PluginTestBase
class TestClockSimplePlugin(PluginTestBase):
"""Test clock-simple plugin integration."""
@pytest.fixture
def plugin_id(self):
return 'clock-simple'
def test_manifest_exists(self, plugin_id):
"""Test that plugin manifest exists."""
super().test_manifest_exists(plugin_id)
def test_manifest_has_required_fields(self, plugin_id):
"""Test that manifest has all required fields."""
super().test_manifest_has_required_fields(plugin_id)
def test_plugin_can_be_loaded(self, plugin_id):
"""Test that plugin module can be loaded."""
super().test_plugin_can_be_loaded(plugin_id)
def test_plugin_class_exists(self, plugin_id):
"""Test that plugin class exists."""
super().test_plugin_class_exists(plugin_id)
def test_plugin_can_be_instantiated(self, plugin_id):
"""Test that plugin can be instantiated."""
super().test_plugin_can_be_instantiated(plugin_id)
def test_plugin_has_required_methods(self, plugin_id):
"""Test that plugin has required methods."""
super().test_plugin_has_required_methods(plugin_id)
def test_plugin_update_method(self, plugin_id):
"""Test that plugin update() method works."""
# Clock doesn't need external APIs, so this should always work
super().test_plugin_update_method(plugin_id)
def test_plugin_display_method(self, plugin_id):
"""Test that plugin display() method works."""
super().test_plugin_display_method(plugin_id)
def test_plugin_has_display_modes(self, plugin_id):
"""Test that plugin has display modes."""
manifest = self.load_plugin_manifest(plugin_id)
assert 'display_modes' in manifest
assert 'clock-simple' in manifest['display_modes']
def test_clock_displays_time(self, plugin_id):
"""Test that clock plugin actually displays time."""
manifest = self.load_plugin_manifest(plugin_id)
plugin_dir = self.plugins_dir / plugin_id
entry_point = manifest['entry_point']
class_name = manifest['class_name']
module = self.plugin_loader.load_module(
plugin_id=plugin_id,
plugin_dir=plugin_dir,
entry_point=entry_point
)
plugin_class = self.plugin_loader.get_plugin_class(
plugin_id=plugin_id,
module=module,
class_name=class_name
)
config = self.base_config.copy()
config['timezone'] = 'UTC'
config['time_format'] = '12h'
config['show_date'] = True
plugin_instance = self.plugin_loader.instantiate_plugin(
plugin_id=plugin_id,
plugin_class=plugin_class,
config=config,
display_manager=self.mock_display_manager,
cache_manager=self.mock_cache_manager,
plugin_manager=self.mock_plugin_manager
)
# Update and display
plugin_instance.update()
plugin_instance.display(force_clear=True)
# Verify time was formatted
assert hasattr(plugin_instance, 'current_time')
assert plugin_instance.current_time is not None
# Verify display was called
assert self.mock_display_manager.clear.called
assert self.mock_display_manager.update_display.called