mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-15 10:03:31 +00:00
Remove unused imports across 86 files in src/, web_interface/, test/, and scripts/ using autoflake. No logic changes — only dead import statements and unused names in from-imports are removed. Also remove bare exception aliases where the variable is never referenced in the handler body: - src/cache/disk_cache.py: except (IOError, OSError, PermissionError) as e - src/cache_manager.py: except (OSError, IOError, PermissionError) as perm_error - src/plugin_system/resource_monitor.py: except Exception as e - web_interface/app.py: except Exception as read_err 86 files changed, 205 lines removed, 18 pre-existing test failures unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
344 lines
12 KiB
Python
344 lines
12 KiB
Python
"""
|
|
Tests for plugin loading failure scenarios.
|
|
|
|
Tests various failure modes that can occur during plugin loading:
|
|
- Missing manifest.json
|
|
- Invalid manifest.json
|
|
- Missing entry_point file
|
|
- Import errors in plugin module
|
|
- Missing class_name in module
|
|
- Class doesn't inherit from BasePlugin
|
|
- validate_config() returns False
|
|
- Dependencies installation failure
|
|
"""
|
|
|
|
import pytest
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
import sys
|
|
|
|
# Add project root to path
|
|
project_root = Path(__file__).parent.parent
|
|
if str(project_root) not in sys.path:
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
from src.plugin_system.plugin_manager import PluginManager
|
|
from src.plugin_system.plugin_state import PluginState
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_managers():
|
|
"""Create mock managers for plugin loading tests."""
|
|
return {
|
|
"config_manager": MagicMock(),
|
|
"display_manager": MagicMock(),
|
|
"cache_manager": MagicMock(),
|
|
"font_manager": MagicMock()
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_plugin_dir(tmp_path):
|
|
"""Create a temporary plugin directory."""
|
|
plugins_dir = tmp_path / "plugins"
|
|
plugins_dir.mkdir()
|
|
return plugins_dir
|
|
|
|
|
|
class TestMissingManifest:
|
|
"""Test handling of missing manifest.json."""
|
|
|
|
def test_plugin_without_manifest_not_discovered(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin directory without manifest.json should not be discovered."""
|
|
# Create plugin directory without manifest
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
(plugin_dir / "manager.py").write_text("# Empty plugin")
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
plugins = manager.discover_plugins()
|
|
|
|
assert "test-plugin" not in plugins
|
|
|
|
|
|
class TestInvalidManifest:
|
|
"""Test handling of invalid manifest.json files."""
|
|
|
|
def test_manifest_invalid_json(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin with invalid JSON manifest should not be discovered."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
(plugin_dir / "manifest.json").write_text("{ invalid json }")
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
plugins = manager.discover_plugins()
|
|
|
|
assert "test-plugin" not in plugins
|
|
|
|
def test_manifest_missing_required_fields(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin manifest missing required fields should fail gracefully."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
|
|
# Manifest missing 'class_name' and 'entry_point'
|
|
manifest = {"id": "test-plugin", "name": "Test Plugin"}
|
|
(plugin_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
plugins = manager.discover_plugins()
|
|
|
|
# Plugin might be discovered but should fail to load
|
|
if "test-plugin" in plugins:
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
|
|
class TestMissingEntryPoint:
|
|
"""Test handling of missing entry_point file."""
|
|
|
|
def test_missing_entry_point_file(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin with missing entry_point file should fail to load."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
|
|
manifest = {
|
|
"id": "test-plugin",
|
|
"name": "Test Plugin",
|
|
"entry_point": "manager.py", # File doesn't exist
|
|
"class_name": "TestPlugin"
|
|
}
|
|
(plugin_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
manager.discover_plugins()
|
|
|
|
# Force the manifest to be loaded
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
|
|
class TestImportErrors:
|
|
"""Test handling of import errors in plugin modules."""
|
|
|
|
def test_syntax_error_in_plugin(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin with Python syntax error should fail to load."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
|
|
manifest = {
|
|
"id": "test-plugin",
|
|
"name": "Test Plugin",
|
|
"entry_point": "manager.py",
|
|
"class_name": "TestPlugin"
|
|
}
|
|
(plugin_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
|
|
# Create manager.py with syntax error
|
|
(plugin_dir / "manager.py").write_text("""
|
|
class TestPlugin
|
|
def __init__(self): # Missing colon above
|
|
pass
|
|
""")
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
manager.discover_plugins()
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
def test_missing_dependency_in_plugin(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin importing missing module should fail to load."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
|
|
manifest = {
|
|
"id": "test-plugin",
|
|
"name": "Test Plugin",
|
|
"entry_point": "manager.py",
|
|
"class_name": "TestPlugin"
|
|
}
|
|
(plugin_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
|
|
# Create manager.py that imports non-existent module
|
|
(plugin_dir / "manager.py").write_text("""
|
|
import nonexistent_module_xyz123
|
|
|
|
class TestPlugin:
|
|
pass
|
|
""")
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
manager.discover_plugins()
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
|
|
class TestMissingClassName:
|
|
"""Test handling when class_name is not found in module."""
|
|
|
|
def test_class_not_in_module(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin with class_name not matching any class should fail."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
|
|
manifest = {
|
|
"id": "test-plugin",
|
|
"name": "Test Plugin",
|
|
"entry_point": "manager.py",
|
|
"class_name": "NonExistentClass" # Doesn't exist in manager.py
|
|
}
|
|
(plugin_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
|
|
(plugin_dir / "manager.py").write_text("""
|
|
class ActualPlugin:
|
|
pass
|
|
""")
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
manager.discover_plugins()
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
|
|
class TestValidateConfigFailure:
|
|
"""Test handling when validate_config() returns False."""
|
|
|
|
def test_validate_config_returns_false(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin where validate_config() returns False should fail to load."""
|
|
plugin_dir = temp_plugin_dir / "test-plugin"
|
|
plugin_dir.mkdir()
|
|
|
|
manifest = {
|
|
"id": "test-plugin",
|
|
"name": "Test Plugin",
|
|
"entry_point": "manager.py",
|
|
"class_name": "TestPlugin"
|
|
}
|
|
(plugin_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
|
|
# Create a mock plugin that fails validation
|
|
mock_plugin = MagicMock()
|
|
mock_plugin.validate_config.return_value = False
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
manager.discover_plugins()
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
# Mock the plugin loader to return our mock plugin
|
|
with patch.object(manager.plugin_loader, 'load_plugin', return_value=(mock_plugin, MagicMock())):
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
def test_validate_config_raises_exception(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin where validate_config() raises exception should fail to load."""
|
|
mock_plugin = MagicMock()
|
|
mock_plugin.validate_config.side_effect = ValueError("Config validation error")
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
|
|
manifest = {
|
|
"id": "test-plugin",
|
|
"name": "Test Plugin",
|
|
"entry_point": "manager.py",
|
|
"class_name": "TestPlugin"
|
|
}
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
with patch.object(manager.plugin_loader, 'load_plugin', return_value=(mock_plugin, MagicMock())):
|
|
with patch.object(manager.plugin_loader, 'find_plugin_directory', return_value=temp_plugin_dir):
|
|
result = manager.load_plugin("test-plugin")
|
|
assert result is False
|
|
|
|
|
|
class TestPluginStateOnFailure:
|
|
"""Test that plugin state is correctly set on various failures."""
|
|
|
|
def test_state_set_to_error_on_load_failure(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin state should be ERROR when loading fails."""
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
|
|
manifest = {"id": "test-plugin", "name": "Test Plugin"}
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
# Try to load non-existent plugin
|
|
result = manager.load_plugin("test-plugin")
|
|
|
|
assert result is False
|
|
state = manager.state_manager.get_state("test-plugin")
|
|
assert state == PluginState.ERROR
|
|
|
|
|
|
class TestErrorAggregation:
|
|
"""Test that errors are properly recorded in error aggregator."""
|
|
|
|
def test_plugin_load_error_recorded(self, temp_plugin_dir, mock_managers):
|
|
"""Plugin load errors should be recorded in error aggregator."""
|
|
from src.error_aggregator import get_error_aggregator
|
|
|
|
# Get the aggregator
|
|
aggregator = get_error_aggregator()
|
|
|
|
with patch('src.common.permission_utils.ensure_directory_permissions'):
|
|
manager = PluginManager(
|
|
plugins_dir=str(temp_plugin_dir),
|
|
**mock_managers
|
|
)
|
|
|
|
manifest = {"id": "test-plugin", "name": "Test Plugin"}
|
|
manager.plugin_manifests["test-plugin"] = manifest
|
|
|
|
# This should trigger an error recording
|
|
manager.load_plugin("test-plugin")
|
|
|
|
# Errors may or may not be recorded depending on execution path
|
|
# This test verifies the aggregator is accessible
|
|
assert aggregator is not None
|