Research-Stack/4-Infrastructure/NoDupeLabs/tests/gpu/test_gpu_plugin.py

357 lines
12 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Allaun
"""Tests for nodupe/tools/gpu/gpu_plugin.py - GPUBackendTool."""
from unittest.mock import MagicMock, patch
import pytest
# Import directly from the plugin file to avoid __init__.py import chain issues
from nodupe.tools.gpu import gpu_plugin
GPUBackendTool = gpu_plugin.GPUBackendTool
register_tool = gpu_plugin.register_tool
class TestGPUBackendToolProperties:
"""Test GPUBackendTool properties."""
def test_name_property(self):
"""GPUBackendTool.name returns correct value."""
tool = GPUBackendTool()
assert tool.name == "gpu_acceleration"
def test_version_property(self):
"""GPUBackendTool.version returns correct value."""
tool = GPUBackendTool()
assert tool.version == "1.0.0"
def test_dependencies_property(self):
"""GPUBackendTool.dependencies returns empty list."""
tool = GPUBackendTool()
assert tool.dependencies == []
class TestGPUBackendToolInitialization:
"""Test GPUBackendTool initialization."""
def test_init_creates_backend(self):
"""GPUBackendTool initializes with a backend."""
tool = GPUBackendTool()
assert tool.backend is not None
def test_api_methods_property(self):
"""GPUBackendTool.api_methods returns correct methods."""
tool = GPUBackendTool()
api_methods = tool.api_methods
assert 'compute_embeddings' in api_methods
assert 'matrix_multiply' in api_methods
assert 'get_device_info' in api_methods
assert 'is_available' in api_methods
# Verify they are bound to backend methods
assert api_methods['compute_embeddings'] == tool.backend.compute_embeddings
assert api_methods['matrix_multiply'] == tool.backend.matrix_multiply
assert api_methods['get_device_info'] == tool.backend.get_device_info
assert api_methods['is_available'] == tool.backend.is_available
class TestGPUBackendToolInitialize:
"""Test GPUBackendTool.initialize() method."""
def test_initialize_registers_service(self):
"""initialize() registers gpu_backend service."""
tool = GPUBackendTool()
container = MagicMock()
tool.initialize(container)
container.register_service.assert_called_once_with('gpu_backend', tool.backend)
def test_initialize_with_mock_container(self):
"""initialize() works with mock container."""
tool = GPUBackendTool()
container = MagicMock()
container.register_service = MagicMock()
tool.initialize(container)
assert container.register_service.called
class TestGPUBackendToolShutdown:
"""Test GPUBackendTool.shutdown() method."""
def test_shutdown_no_error(self):
"""shutdown() completes without error."""
tool = GPUBackendTool()
# Should not raise
tool.shutdown()
def test_shutdown_multiple_times(self):
"""shutdown() can be called multiple times without error."""
tool = GPUBackendTool()
tool.shutdown()
tool.shutdown() # Should not raise
class TestGPUBackendToolGetCapabilities:
"""Test GPUBackendTool.get_capabilities() method."""
def test_get_capabilities_returns_dict(self):
"""get_capabilities() returns a dictionary with expected keys."""
tool = GPUBackendTool()
capabilities = tool.get_capabilities()
assert isinstance(capabilities, dict)
assert 'device_type' in capabilities
assert 'device_name' in capabilities
assert 'available' in capabilities
def test_get_capabilities_device_type(self):
"""get_capabilities() returns correct device_type from backend."""
tool = GPUBackendTool()
capabilities = tool.get_capabilities()
# Should be 'cpu' since we use CPU fallback by default
assert capabilities['device_type'] in ['cpu', 'cuda', 'metal', 'unknown']
def test_get_capabilities_available(self):
"""get_capabilities() returns boolean for available."""
tool = GPUBackendTool()
capabilities = tool.get_capabilities()
assert isinstance(capabilities['available'], bool)
def test_get_capabilities_with_mocked_backend(self):
"""get_capabilities() uses backend info correctly."""
tool = GPUBackendTool()
# Mock the backend's get_device_info
tool.backend.get_device_info = MagicMock(return_value={
'type': 'test_gpu',
'name': 'Test GPU',
'memory': '8GB'
})
capabilities = tool.get_capabilities()
assert capabilities['device_type'] == 'test_gpu'
assert capabilities['device_name'] == 'Test GPU'
def test_get_capabilities_unknown_device_type(self):
"""get_capabilities() handles missing device type."""
tool = GPUBackendTool()
tool.backend.get_device_info = MagicMock(return_value={})
capabilities = tool.get_capabilities()
assert capabilities['device_type'] == 'unknown'
assert capabilities['device_name'] == 'unknown'
class TestRegisterTool:
"""Test register_tool() function."""
def test_register_tool_returns_gpu_backend_tool(self):
"""register_tool() returns a GPUBackendTool instance."""
tool = register_tool()
assert isinstance(tool, GPUBackendTool)
def test_register_tool_creates_new_instance(self):
"""register_tool() creates a new instance each call."""
tool1 = register_tool()
tool2 = register_tool()
assert tool1 is not tool2
class TestGPUBackendToolDescribeUsage:
"""Test GPUBackendTool.describe_usage() method."""
def test_describe_usage_returns_string(self):
"""describe_usage() returns a string."""
tool = GPUBackendTool()
description = tool.describe_usage()
assert isinstance(description, str)
assert "hardware acceleration" in description.lower()
def test_describe_usage_mentions_cuda(self):
"""describe_usage() mentions CUDA support."""
tool = GPUBackendTool()
description = tool.describe_usage()
assert "cuda" in description.lower()
def test_describe_usage_mentions_metal(self):
"""describe_usage() mentions Metal support."""
tool = GPUBackendTool()
description = tool.describe_usage()
assert "metal" in description.lower()
def test_describe_usage_mentions_cpu_fallback(self):
"""describe_usage() mentions CPU fallback."""
tool = GPUBackendTool()
description = tool.describe_usage()
assert "cpu" in description.lower() or "fallback" in description.lower()
class TestGPUBackendToolRunStandalone:
"""Test GPUBackendTool.run_standalone() method."""
def test_run_standalone_returns_zero(self, capsys):
"""run_standalone() returns 0 and prints output."""
tool = GPUBackendTool()
result = tool.run_standalone([])
assert result == 0
captured = capsys.readouterr()
assert "GPU Backend Tool: Self-test mode." in captured.out
assert "Backend available:" in captured.out
assert "Device info:" in captured.out
def test_run_standalone_with_args(self, capsys):
"""run_standalone() handles args parameter."""
tool = GPUBackendTool()
result = tool.run_standalone(['--verbose', '--test'])
assert result == 0
def test_run_standalone_empty_args(self, capsys):
"""run_standalone() works with empty args."""
tool = GPUBackendTool()
result = tool.run_standalone([])
assert result == 0
captured = capsys.readouterr()
assert "GPU Backend Tool: Self-test mode." in captured.out
class TestGPUBackendToolWithMockedBackend:
"""Test GPUBackendTool with mocked backend for complete coverage."""
@patch('nodupe.tools.gpu.gpu_plugin.get_gpu_backend')
def test_with_mocked_cuda_backend(self, mock_get_backend):
"""Test with mocked CUDA backend."""
mock_backend = MagicMock()
mock_backend.is_available.return_value = True
mock_backend.get_device_info.return_value = {
'type': 'cuda',
'name': 'NVIDIA GeForce RTX 3080',
'memory': '10GB'
}
mock_backend.compute_embeddings.return_value = [[0.1, 0.2, 0.3]]
mock_backend.matrix_multiply.return_value = [[1.0, 0.0], [0.0, 1.0]]
mock_get_backend.return_value = mock_backend
tool = GPUBackendTool()
assert tool.backend.is_available() is True
caps = tool.get_capabilities()
assert caps['device_type'] == 'cuda'
assert caps['available'] is True
@patch('nodupe.tools.gpu.gpu_plugin.get_gpu_backend')
def test_with_mocked_metal_backend(self, mock_get_backend):
"""Test with mocked Metal backend."""
mock_backend = MagicMock()
mock_backend.is_available.return_value = True
mock_backend.get_device_info.return_value = {
'type': 'metal',
'name': 'Apple M1',
'memory': 'Integrated'
}
mock_get_backend.return_value = mock_backend
tool = GPUBackendTool()
caps = tool.get_capabilities()
assert caps['device_type'] == 'metal'
@patch('nodupe.tools.gpu.gpu_plugin.get_gpu_backend')
def test_with_mocked_cpu_backend(self, mock_get_backend):
"""Test with mocked CPU backend."""
mock_backend = MagicMock()
mock_backend.is_available.return_value = True
mock_backend.get_device_info.return_value = {
'type': 'cpu',
'name': 'CPU Fallback',
'memory': 'N/A'
}
mock_get_backend.return_value = mock_backend
tool = GPUBackendTool()
caps = tool.get_capabilities()
assert caps['device_type'] == 'cpu'
@patch('nodupe.tools.gpu.gpu_plugin.get_gpu_backend')
def test_api_methods_call_backend(self, mock_get_backend):
"""Test that api_methods correctly call backend methods."""
mock_backend = MagicMock()
mock_backend.is_available.return_value = True
mock_backend.get_device_info.return_value = {'type': 'cpu', 'name': 'CPU'}
mock_backend.compute_embeddings.return_value = [[0.1, 0.2]]
mock_backend.matrix_multiply.return_value = [[1.0]]
mock_get_backend.return_value = mock_backend
tool = GPUBackendTool()
# Test compute_embeddings
tool.api_methods['compute_embeddings']([[1, 2, 3]])
mock_backend.compute_embeddings.assert_called_once()
# Test matrix_multiply
tool.api_methods['matrix_multiply']([[1]], [[1]])
mock_backend.matrix_multiply.assert_called_once()
# Test get_device_info
tool.api_methods['get_device_info']()
mock_backend.get_device_info.assert_called_once()
# Test is_available
tool.api_methods['is_available']()
mock_backend.is_available.assert_called_once()
class TestGPUBackendToolEdgeCases:
"""Test GPUBackendTool edge cases."""
@patch('nodupe.tools.gpu.gpu_plugin.get_gpu_backend')
def test_backend_raises_exception(self, mock_get_backend):
"""Test handling when backend raises exception."""
mock_backend = MagicMock()
mock_backend.is_available.side_effect = Exception("Backend error")
mock_get_backend.return_value = mock_backend
tool = GPUBackendTool()
with pytest.raises(Exception, match="Backend error"):
tool.backend.is_available()
@patch('nodupe.tools.gpu.gpu_plugin.get_gpu_backend')
def test_backend_returns_none_info(self, mock_get_backend):
"""Test handling when backend returns None for device info."""
mock_backend = MagicMock()
mock_backend.is_available.return_value = True
mock_backend.get_device_info.return_value = None
mock_get_backend.return_value = mock_backend
tool = GPUBackendTool()
# Should handle None gracefully
caps = tool.get_capabilities()
assert caps['device_type'] == 'unknown'
assert caps['device_name'] == 'unknown'
def test_tool_instantiation_multiple_times(self):
"""Test that multiple tool instances can be created."""
tool1 = GPUBackendTool()
tool2 = GPUBackendTool()
assert tool1 is not tool2
assert tool1.name == tool2.name
assert tool1.version == tool2.version