""" 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()