deb-mock/tests/test_plugin_system.py
robojerk c51819c836
Some checks failed
Build Deb-Mock Package / build (push) Failing after 1m9s
Lint Code / Lint All Code (push) Failing after 1s
Test Deb-Mock Build / test (push) Failing after 35s
Add comprehensive testing framework, performance monitoring, and plugin system
- Add complete pytest testing framework with conftest.py and test files
- Add performance monitoring and benchmarking capabilities
- Add plugin system with ccache plugin example
- Add comprehensive documentation (API, deployment, testing, etc.)
- Add Docker API wrapper for service deployment
- Add advanced configuration examples
- Remove old wget package file
- Update core modules with enhanced functionality
2025-08-19 20:49:32 -07:00

632 lines
22 KiB
Python

"""
Tests for deb-mock plugin system
"""
import pytest
import tempfile
import os
import sys
from unittest.mock import Mock, patch, MagicMock
from pathlib import Path
from deb_mock.plugin import PluginManager, HookStages, BasePlugin
from deb_mock.exceptions import PluginError
class TestHookStages:
"""Test HookStages enum"""
def test_hook_stages_defined(self):
"""Test that all hook stages are defined"""
assert hasattr(HookStages, 'PREBUILD')
assert hasattr(HookStages, 'BUILD_START')
assert hasattr(HookStages, 'BUILD_END')
assert hasattr(HookStages, 'POSTBUILD')
assert hasattr(HookStages, 'PRECHROOT_INIT')
assert hasattr(HookStages, 'POSTCHROOT_INIT')
assert hasattr(HookStages, 'PRECACHE')
assert hasattr(HookStages, 'POSTCACHE')
def test_hook_stages_values(self):
"""Test that hook stages have string values"""
for stage_name in dir(HookStages):
if not stage_name.startswith('_'):
stage_value = getattr(HookStages, stage_name)
assert isinstance(stage_value, str)
assert stage_value == stage_name.lower()
class TestBasePlugin:
"""Test BasePlugin class"""
def test_base_plugin_creation(self):
"""Test creating a base plugin"""
plugin = BasePlugin()
assert plugin.name == "BasePlugin"
assert plugin.version == "1.0.0"
assert plugin.description == "Base plugin class"
assert plugin.enabled is True
def test_base_plugin_custom_values(self):
"""Test creating a base plugin with custom values"""
plugin = BasePlugin(
name="TestPlugin",
version="2.0.0",
description="Test plugin description",
enabled=False
)
assert plugin.name == "TestPlugin"
assert plugin.version == "2.0.0"
assert plugin.description == "Test plugin description"
assert plugin.enabled is False
def test_base_plugin_methods(self):
"""Test base plugin methods"""
plugin = BasePlugin()
# Test default implementations
assert plugin.init() is None
assert plugin.cleanup() is None
assert plugin.get_hooks() == {}
def test_base_plugin_hook_registration(self):
"""Test hook registration in base plugin"""
plugin = BasePlugin()
# Register a hook
plugin.register_hook(HookStages.PREBUILD, "test_hook")
hooks = plugin.get_hooks()
assert HookStages.PREBUILD in hooks
assert "test_hook" in hooks[HookStages.PREBUILD]
def test_base_plugin_multiple_hooks(self):
"""Test registering multiple hooks"""
plugin = BasePlugin()
# Register multiple hooks
plugin.register_hook(HookStages.PREBUILD, "hook1")
plugin.register_hook(HookStages.PREBUILD, "hook2")
plugin.register_hook(HookStages.POSTBUILD, "hook3")
hooks = plugin.get_hooks()
assert len(hooks[HookStages.PREBUILD]) == 2
assert len(hooks[HookStages.POSTBUILD]) == 1
assert "hook1" in hooks[HookStages.PREBUILD]
assert "hook2" in hooks[HookStages.PREBUILD]
assert "hook3" in hooks[HookStages.POSTBUILD]
class TestPluginManager:
"""Test PluginManager class"""
def test_initialization(self, test_config):
"""Test PluginManager initialization"""
manager = PluginManager(test_config)
assert manager.config == test_config
assert manager.plugins == {}
assert manager.hooks == {}
assert manager.plugin_dir == test_config.plugin_dir
def test_initialization_with_custom_plugin_dir(self, test_config):
"""Test PluginManager initialization with custom plugin directory"""
test_config.plugin_dir = "/custom/plugin/dir"
manager = PluginManager(test_config)
assert manager.plugin_dir == "/custom/plugin/dir"
def test_discover_plugins_no_directory(self, test_config):
"""Test plugin discovery when plugin directory doesn't exist"""
test_config.plugin_dir = "/nonexistent/directory"
manager = PluginManager(test_config)
plugins = manager.discover_plugins()
assert plugins == []
def test_discover_plugins_empty_directory(self, test_config, temp_dir):
"""Test plugin discovery in empty directory"""
plugin_dir = os.path.join(temp_dir, "plugins")
os.makedirs(plugin_dir)
test_config.plugin_dir = plugin_dir
manager = PluginManager(test_config)
plugins = manager.discover_plugins()
assert plugins == []
def test_discover_plugins_with_python_files(self, test_config, temp_dir):
"""Test plugin discovery with Python files"""
plugin_dir = os.path.join(temp_dir, "plugins")
os.makedirs(plugin_dir)
# Create a Python file that's not a plugin
with open(os.path.join(plugin_dir, "not_a_plugin.py"), "w") as f:
f.write("# This is not a plugin\n")
# Create a Python file that could be a plugin
with open(os.path.join(plugin_dir, "test_plugin.py"), "w") as f:
f.write("""
class TestPlugin:
pass
""")
test_config.plugin_dir = plugin_dir
manager = PluginManager(test_config)
plugins = manager.discover_plugins()
# Should find Python files but not load them as plugins
assert len(plugins) == 0
def test_load_plugin_success(self, test_config):
"""Test successfully loading a plugin"""
manager = PluginManager(test_config)
# Create a mock plugin class
class MockPlugin(BasePlugin):
def __init__(self):
super().__init__(name="MockPlugin")
# Mock the plugin module
mock_module = Mock()
mock_module.MockPlugin = MockPlugin
with patch('builtins.__import__', return_value=mock_module):
plugin = manager.load_plugin("MockPlugin", "mock_plugin")
assert plugin is not None
assert plugin.name == "MockPlugin"
def test_load_plugin_missing_class(self, test_config):
"""Test loading a plugin with missing class"""
manager = PluginManager(test_config)
# Mock the plugin module without the expected class
mock_module = Mock()
mock_module.spec = None
with patch('builtins.__import__', return_value=mock_module):
with pytest.raises(PluginError, match="Plugin class 'TestPlugin' not found"):
manager.load_plugin("TestPlugin", "test_plugin")
def test_load_plugin_import_error(self, test_config):
"""Test loading a plugin with import error"""
manager = PluginManager(test_config)
with patch('builtins.__import__', side_effect=ImportError("Test import error")):
with pytest.raises(PluginError, match="Failed to import plugin 'test_plugin'"):
manager.load_plugin("TestPlugin", "test_plugin")
def test_load_plugin_instantiation_error(self, test_config):
"""Test loading a plugin with instantiation error"""
manager = PluginManager(test_config)
# Create a plugin class that raises an error when instantiated
class ErrorPlugin(BasePlugin):
def __init__(self):
raise Exception("Test instantiation error")
# Mock the plugin module
mock_module = Mock()
mock_module.ErrorPlugin = ErrorPlugin
with patch('builtins.__import__', return_value=mock_module):
with pytest.raises(PluginError, match="Failed to instantiate plugin 'ErrorPlugin'"):
manager.load_plugin("ErrorPlugin", "error_plugin")
def test_init_plugins(self, test_config):
"""Test initializing plugins"""
manager = PluginManager(test_config)
# Mock plugin discovery and loading
mock_plugin = Mock()
mock_plugin.name = "TestPlugin"
mock_plugin.enabled = True
mock_plugin.init.return_value = None
manager.plugins = {"test_plugin": mock_plugin}
# Mock deb_mock instance
mock_deb_mock = Mock()
result = manager.init_plugins(mock_deb_mock)
assert result is True
mock_plugin.init.assert_called_once_with(mock_deb_mock)
def test_init_plugins_disabled_plugin(self, test_config):
"""Test initializing plugins with disabled plugin"""
manager = PluginManager(test_config)
# Mock plugin discovery and loading
mock_plugin = Mock()
mock_plugin.name = "TestPlugin"
mock_plugin.enabled = False
mock_plugin.init.return_value = None
manager.plugins = {"test_plugin": mock_plugin}
# Mock deb_mock instance
mock_deb_mock = Mock()
result = manager.init_plugins(mock_deb_mock)
assert result is True
mock_plugin.init.assert_not_called()
def test_init_plugins_with_error(self, test_config):
"""Test initializing plugins with plugin error"""
manager = PluginManager(test_config)
# Mock plugin discovery and loading
mock_plugin = Mock()
mock_plugin.name = "TestPlugin"
mock_plugin.enabled = True
mock_plugin.init.side_effect = Exception("Test plugin error")
manager.plugins = {"test_plugin": mock_plugin}
# Mock deb_mock instance
mock_deb_mock = Mock()
with pytest.raises(PluginError, match="Failed to initialize plugin 'TestPlugin'"):
manager.init_plugins(mock_deb_mock)
def test_register_hooks(self, test_config):
"""Test registering hooks from plugins"""
manager = PluginManager(test_config)
# Mock plugin with hooks
mock_plugin = Mock()
mock_plugin.get_hooks.return_value = {
HookStages.PREBUILD: ["hook1", "hook2"],
HookStages.POSTBUILD: ["hook3"]
}
manager.plugins = {"test_plugin": mock_plugin}
manager.register_hooks()
# Verify hooks were registered
assert HookStages.PREBUILD in manager.hooks
assert HookStages.POSTBUILD in manager.hooks
assert "hook1" in manager.hooks[HookStages.PREBUILD]
assert "hook2" in manager.hooks[HookStages.PREBUILD]
assert "hook3" in manager.hooks[HookStages.POSTBUILD]
def test_call_hooks(self, test_config):
"""Test calling hooks"""
manager = PluginManager(test_config)
# Mock plugin with hooks
mock_plugin = Mock()
mock_plugin.get_hooks.return_value = {
HookStages.PREBUILD: ["hook1"]
}
# Mock hook methods
def hook1(*args, **kwargs):
return "hook1_result"
mock_plugin.hook1 = hook1
manager.plugins = {"test_plugin": mock_plugin}
manager.register_hooks()
# Call hooks
results = manager.call_hooks(HookStages.PREBUILD, "arg1", kwarg1="value1")
assert len(results) == 1
assert results[0] == "hook1_result"
def test_call_hooks_no_hooks(self, test_config):
"""Test calling hooks when no hooks are registered"""
manager = PluginManager(test_config)
results = manager.call_hooks(HookStages.PREBUILD, "arg1")
assert results == []
def test_call_hooks_with_error(self, test_config):
"""Test calling hooks with hook error"""
manager = PluginManager(test_config)
# Mock plugin with hooks
mock_plugin = Mock()
mock_plugin.get_hooks.return_value = {
HookStages.PREBUILD: ["hook1"]
}
# Mock hook method that raises an error
def hook1(*args, **kwargs):
raise Exception("Test hook error")
mock_plugin.hook1 = hook1
manager.plugins = {"test_plugin": mock_plugin}
manager.register_hooks()
# Call hooks - should handle errors gracefully
results = manager.call_hooks(HookStages.PREBUILD, "arg1")
# Should return empty list when hooks fail
assert results == []
def test_cleanup_plugins(self, test_config):
"""Test cleaning up plugins"""
manager = PluginManager(test_config)
# Mock plugin with cleanup method
mock_plugin = Mock()
mock_plugin.cleanup.return_value = None
manager.plugins = {"test_plugin": mock_plugin}
result = manager.cleanup_plugins()
assert result is True
mock_plugin.cleanup.assert_called_once()
def test_cleanup_plugins_with_error(self, test_config):
"""Test cleaning up plugins with plugin error"""
manager = PluginManager(test_config)
# Mock plugin with cleanup method that raises an error
mock_plugin = Mock()
mock_plugin.cleanup.side_effect = Exception("Test cleanup error")
manager.plugins = {"test_plugin": mock_plugin}
with pytest.raises(PluginError, match="Failed to cleanup plugin 'test_plugin'"):
manager.cleanup_plugins()
def test_get_plugin_info(self, test_config):
"""Test getting plugin information"""
manager = PluginManager(test_config)
# Mock plugin
mock_plugin = Mock()
mock_plugin.name = "TestPlugin"
mock_plugin.version = "1.0.0"
mock_plugin.description = "Test plugin description"
mock_plugin.enabled = True
manager.plugins = {"test_plugin": mock_plugin}
info = manager.get_plugin_info("test_plugin")
assert info["name"] == "TestPlugin"
assert info["version"] == "1.0.0"
assert info["description"] == "Test plugin description"
assert info["enabled"] is True
def test_get_plugin_info_not_found(self, test_config):
"""Test getting plugin information for non-existent plugin"""
manager = PluginManager(test_config)
with pytest.raises(PluginError, match="Plugin 'nonexistent' not found"):
manager.get_plugin_info("nonexistent")
def test_list_plugins(self, test_config):
"""Test listing all plugins"""
manager = PluginManager(test_config)
# Mock plugins
mock_plugin1 = Mock()
mock_plugin1.name = "Plugin1"
mock_plugin1.version = "1.0.0"
mock_plugin1.enabled = True
mock_plugin2 = Mock()
mock_plugin2.name = "Plugin2"
mock_plugin2.version = "2.0.0"
mock_plugin2.enabled = False
manager.plugins = {
"plugin1": mock_plugin1,
"plugin2": mock_plugin2
}
plugins = manager.list_plugins()
assert len(plugins) == 2
assert plugins["plugin1"]["name"] == "Plugin1"
assert plugins["plugin2"]["name"] == "Plugin2"
assert plugins["plugin1"]["enabled"] is True
assert plugins["plugin2"]["enabled"] is False
def test_enable_plugin(self, test_config):
"""Test enabling a plugin"""
manager = PluginManager(test_config)
# Mock plugin
mock_plugin = Mock()
mock_plugin.enabled = False
manager.plugins = {"test_plugin": mock_plugin}
result = manager.enable_plugin("test_plugin")
assert result is True
assert mock_plugin.enabled is True
def test_enable_plugin_not_found(self, test_config):
"""Test enabling a non-existent plugin"""
manager = PluginManager(test_config)
with pytest.raises(PluginError, match="Plugin 'nonexistent' not found"):
manager.enable_plugin("nonexistent")
def test_disable_plugin(self, test_config):
"""Test disabling a plugin"""
manager = PluginManager(test_config)
# Mock plugin
mock_plugin = Mock()
mock_plugin.enabled = True
manager.plugins = {"test_plugin": mock_plugin}
result = manager.disable_plugin("test_plugin")
assert result is True
assert mock_plugin.enabled is False
def test_disable_plugin_not_found(self, test_config):
"""Test disabling a non-existent plugin"""
manager = PluginManager(test_config)
with pytest.raises(PluginError, match="Plugin 'nonexistent' not found"):
manager.disable_plugin("nonexistent")
def test_reload_plugin(self, test_config):
"""Test reloading a plugin"""
manager = PluginManager(test_config)
# Mock plugin
mock_plugin = Mock()
mock_plugin.name = "TestPlugin"
mock_plugin.cleanup.return_value = None
manager.plugins = {"test_plugin": mock_plugin}
# Mock plugin loading
with patch.object(manager, 'load_plugin', return_value=mock_plugin):
result = manager.reload_plugin("test_plugin")
assert result is True
mock_plugin.cleanup.assert_called_once()
def test_reload_plugin_not_found(self, test_config):
"""Test reloading a non-existent plugin"""
manager = PluginManager(test_config)
with pytest.raises(PluginError, match="Plugin 'nonexistent' not found"):
manager.reload_plugin("nonexistent")
class TestPluginIntegration:
"""Test plugin system integration"""
def test_plugin_lifecycle(self, test_config):
"""Test complete plugin lifecycle"""
manager = PluginManager(test_config)
# Create a test plugin
class TestPlugin(BasePlugin):
def __init__(self):
super().__init__(
name="TestPlugin",
version="1.0.0",
description="Test plugin for integration testing"
)
self.init_called = False
self.cleanup_called = False
def init(self, deb_mock):
self.init_called = True
return None
def cleanup(self):
self.cleanup_called = True
return None
def get_hooks(self):
return {
HookStages.PREBUILD: ["prebuild_hook"],
HookStages.POSTBUILD: ["postbuild_hook"]
}
def prebuild_hook(self, *args, **kwargs):
return "prebuild_result"
def postbuild_hook(self, *args, **kwargs):
return "postbuild_result"
# Mock plugin module
mock_module = Mock()
mock_module.TestPlugin = TestPlugin
with patch('builtins.__import__', return_value=mock_module):
# Load plugin
plugin = manager.load_plugin("TestPlugin", "test_plugin")
# Add to plugins
manager.plugins["test_plugin"] = plugin
# Initialize plugins
mock_deb_mock = Mock()
result = manager.init_plugins(mock_deb_mock)
assert result is True
assert plugin.init_called is True
# Register hooks
manager.register_hooks()
# Call hooks
prebuild_results = manager.call_hooks(HookStages.PREBUILD, "arg1")
postbuild_results = manager.call_hooks(HookStages.POSTBUILD, "arg2")
assert prebuild_results == ["prebuild_result"]
assert postbuild_results == ["postbuild_result"]
# Cleanup plugins
cleanup_result = manager.cleanup_plugins()
assert cleanup_result is True
assert plugin.cleanup_called is True
def test_plugin_configuration(self, plugin_test_config):
"""Test plugin configuration integration"""
manager = PluginManager(plugin_test_config)
# Mock plugin discovery
with patch.object(manager, 'discover_plugins', return_value=[]):
# Initialize plugins
mock_deb_mock = Mock()
result = manager.init_plugins(mock_deb_mock)
assert result is True
# Verify plugin configuration was loaded
assert "test_plugin" in plugin_test_config.plugin_conf
assert plugin_test_config.plugin_conf["test_plugin"]["enabled"] is True
assert plugin_test_config.plugin_conf["test_plugin"]["config_option"] == "test_value"
def test_plugin_error_handling(self, test_config):
"""Test plugin error handling"""
manager = PluginManager(test_config)
# Create a plugin that raises errors
class ErrorPlugin(BasePlugin):
def __init__(self):
super().__init__(name="ErrorPlugin")
def init(self, deb_mock):
raise Exception("Init error")
def cleanup(self):
raise Exception("Cleanup error")
# Mock plugin module
mock_module = Mock()
mock_module.ErrorPlugin = ErrorPlugin
with patch('builtins.__import__', return_value=mock_module):
# Load plugin
plugin = manager.load_plugin("ErrorPlugin", "error_plugin")
# Add to plugins
manager.plugins["error_plugin"] = plugin
# Initialize plugins should fail
mock_deb_mock = Mock()
with pytest.raises(PluginError, match="Failed to initialize plugin 'ErrorPlugin'"):
manager.init_plugins(mock_deb_mock)
# Cleanup plugins should fail
with pytest.raises(PluginError, match="Failed to cleanup plugin 'error_plugin'"):
manager.cleanup_plugins()