Research-Stack/5-Applications/nodupe/tests/gpu/test_gpu_backend.py
2026-05-05 21:15:26 -05:00

192 lines
6.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Allaun
"""Tests for nodupe/tools/gpu/__init__.py - GPU Backend implementations."""
from unittest.mock import MagicMock, patch
import numpy as np
import pytest
# Import the GPU backend classes
from nodupe.tools.gpu import (
CPUFallbackBackend,
CUDABackend,
GPUBackend,
MetalBackend,
create_gpu_backend,
get_gpu_backend,
)
class TestGPUBackend:
"""Test abstract GPUBackend class."""
def test_is_abstract(self):
"""GPUBackend cannot be instantiated directly."""
with pytest.raises(TypeError):
GPUBackend()
class TestCPUFallbackBackend:
"""Test CPUFallbackBackend class."""
def test_cpu_backend_creation(self):
"""CPUFallbackBackend can be created."""
backend = CPUFallbackBackend()
assert backend is not None
def test_is_available(self):
"""CPUFallbackBackend is always available."""
backend = CPUFallbackBackend()
assert backend.is_available() is True
def test_get_device_info(self):
"""CPUFallbackBackend returns device info."""
backend = CPUFallbackBackend()
info = backend.get_device_info()
assert isinstance(info, dict)
assert info['type'] == 'cpu'
def test_compute_embeddings_empty_list(self):
"""CPUFallbackBackend handles empty list."""
backend = CPUFallbackBackend()
embeddings = backend.compute_embeddings([])
assert embeddings == []
def test_compute_embeddings_lists(self):
"""CPUFallbackBackend generates embeddings for lists."""
backend = CPUFallbackBackend()
embeddings = backend.compute_embeddings([[1, 2, 3], [4, 5, 6]])
assert len(embeddings) == 2
def test_compute_embeddings_numpy_arrays(self):
"""CPUFallbackBackend generates embeddings for numpy arrays."""
backend = CPUFallbackBackend()
arr1 = np.array([1.0, 2.0, 3.0])
arr2 = np.array([4.0, 5.0, 6.0])
embeddings = backend.compute_embeddings([arr1, arr2])
assert len(embeddings) == 2
def test_compute_embeddings_other_types(self):
"""CPUFallbackBackend handles other types."""
backend = CPUFallbackBackend()
embeddings = backend.compute_embeddings([42, "string", None])
assert len(embeddings) == 3
def test_matrix_multiply_basic(self):
"""CPUFallbackBackend performs matrix multiplication."""
backend = CPUFallbackBackend()
a = [[1, 2], [3, 4]]
b = [[5, 6], [7, 8]]
result = backend.matrix_multiply(a, b)
# [[1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8]] = [[19, 22], [43, 50]]
assert result[0][0] == 19
assert result[0][1] == 22
assert result[1][0] == 43
assert result[1][1] == 50
def test_matrix_multiply_empty(self):
"""CPUFallbackBackend handles empty matrices."""
backend = CPUFallbackBackend()
result = backend.matrix_multiply([], [])
# Empty input returns 0.0 or empty
assert result == [] or result == 0.0
def test_matrix_multiply_invalid(self):
"""CPUFallbackBackend handles invalid matrices."""
backend = CPUFallbackBackend()
result = backend.matrix_multiply([[1, 2]], [[3], [4]]) # Incompatible dimensions
# Should return empty or handle gracefully
assert isinstance(result, list)
class TestCUDABackend:
"""Test CUDABackend class."""
def test_cuda_backend_creation(self):
"""CUDABackend can be created."""
backend = CUDABackend()
assert backend is not None
def test_cuda_backend_with_device_id(self):
"""CUDABackend can be created with device_id."""
backend = CUDABackend(device_id=1)
assert backend.device_id == 1
def test_cuda_backend_not_available(self):
"""CUDABackend is not available without CUDA."""
backend = CUDABackend()
# Falls back to CPU when CUDA is not available
assert backend.is_available() is False
def test_get_device_info_no_device(self):
"""CUDABackend returns empty dict when not available."""
backend = CUDABackend()
info = backend.get_device_info()
assert info == {}
class TestMetalBackend:
"""Test MetalBackend class."""
def test_metal_backend_creation(self):
"""MetalBackend can be created."""
backend = MetalBackend()
assert backend is not None
def test_metal_backend_not_available(self):
"""MetalBackend is not available without Metal."""
backend = MetalBackend()
# Falls back to CPU when Metal is not available
assert backend.is_available() is False
def test_get_device_info_no_device(self):
"""MetalBackend returns empty dict when not available."""
backend = MetalBackend()
info = backend.get_device_info()
assert info == {}
class TestCreateGpuBackend:
"""Test create_gpu_backend factory function."""
def test_create_gpu_backend_auto(self):
"""create_gpu_backend with 'auto' returns CPUFallbackBackend."""
backend = create_gpu_backend('auto')
assert isinstance(backend, CPUFallbackBackend)
def test_create_gpu_backend_cpu(self):
"""create_gpu_backend with 'cpu' returns CPUFallbackBackend."""
backend = create_gpu_backend('cpu')
assert isinstance(backend, CPUFallbackBackend)
def test_create_gpu_backend_cuda(self):
"""create_gpu_backend with 'cuda' returns CUDABackend."""
backend = create_gpu_backend('cuda')
assert isinstance(backend, CUDABackend)
def test_create_gpu_backend_metal(self):
"""create_gpu_backend with 'metal' returns MetalBackend."""
backend = create_gpu_backend('metal')
assert isinstance(backend, MetalBackend)
def test_create_gpu_backend_unknown_type(self):
"""create_gpu_backend raises ValueError for unknown type."""
with pytest.raises(ValueError, match="Unknown GPU backend type"):
create_gpu_backend('unknown')
class TestGetGpuBackend:
"""Test get_gpu_backend function."""
def test_get_backend_returns_backend(self):
"""get_gpu_backend returns a GPUBackend."""
backend = get_gpu_backend()
assert isinstance(backend, GPUBackend)
def test_get_backend_singleton(self):
"""get_gpu_backend returns the same instance."""
backend1 = get_gpu_backend()
backend2 = get_gpu_backend()
assert backend1 is backend2