mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
448 lines
14 KiB
Python
448 lines
14 KiB
Python
"""Test fixtures and configuration for NoDupeLabs test suite.
|
|
|
|
This module provides comprehensive test fixtures for database operations,
|
|
file system operations, tool system mocking, and various utility functions
|
|
for testing the NoDupeLabs duplicate detection system.
|
|
"""
|
|
|
|
# NoDupeLabs Test Fixtures and Configuration
|
|
# Comprehensive test fixtures for database operations, file system operations, tool system mocking, etc.
|
|
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Generator, Dict, Any, Optional, Callable
|
|
import pytest # type: ignore
|
|
from unittest.mock import MagicMock, patch
|
|
import nodupe.core.container as container_module
|
|
from nodupe.core.container import ServiceContainer
|
|
import nodupe.core.config as config
|
|
from nodupe.core.tool_system.registry import ToolRegistry
|
|
from nodupe.core.tool_system.loader import ToolLoader
|
|
from nodupe.tools.databases.connection import DatabaseConnection
|
|
|
|
## Mock Database Connection for Testing
|
|
|
|
class MockDatabaseConnection:
|
|
"""Mock database connection for testing purposes."""
|
|
def __init__(self, db_path: str = ":memory:"):
|
|
"""Initialize the mock database connection.
|
|
|
|
Args:
|
|
db_path: Path to the database file. Defaults to in-memory database.
|
|
"""
|
|
self.db_path = db_path
|
|
self.closed = False
|
|
|
|
def get_connection(self):
|
|
"""Return a mock connection."""
|
|
import sqlite3
|
|
return sqlite3.connect(self.db_path)
|
|
|
|
def close(self):
|
|
"""Close the connection."""
|
|
self.closed = True
|
|
|
|
@pytest.fixture(scope="function")
|
|
def temp_db_connection() -> Generator[MockDatabaseConnection, None, None]:
|
|
"""Create a temporary in-memory SQLite database connection for testing."""
|
|
conn = MockDatabaseConnection(":memory:")
|
|
try:
|
|
yield conn
|
|
finally:
|
|
conn.close()
|
|
|
|
@pytest.fixture(scope="function")
|
|
def temp_db_file() -> Generator[str, None, None]:
|
|
"""Create a temporary database file that gets cleaned up after tests."""
|
|
temp_file = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
|
temp_file.close()
|
|
|
|
try:
|
|
yield temp_file.name
|
|
finally:
|
|
if os.path.exists(temp_file.name):
|
|
os.unlink(temp_file.name)
|
|
|
|
## File System Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def temp_dir() -> Generator[Path, None, None]:
|
|
"""Create a temporary directory that gets cleaned up after tests."""
|
|
temp_dir = Path(tempfile.mkdtemp())
|
|
try:
|
|
yield temp_dir
|
|
finally:
|
|
if temp_dir.exists():
|
|
shutil.rmtree(temp_dir)
|
|
|
|
@pytest.fixture(scope="function")
|
|
def temp_file(temp_dir: Path) -> Generator[Path, None, None]:
|
|
"""Create a temporary file in the temp directory."""
|
|
temp_file = temp_dir / "test_file.txt"
|
|
temp_file.write_text("This is a test file content.")
|
|
yield temp_file
|
|
|
|
@pytest.fixture(scope="function")
|
|
def sample_files(temp_dir: Path) -> Generator[Dict[str, Path], None, None]:
|
|
"""Create a set of sample files for duplicate detection testing."""
|
|
files = {
|
|
"small.txt": temp_dir / "small.txt",
|
|
"medium.txt": temp_dir / "medium.txt",
|
|
"large.txt": temp_dir / "large.txt",
|
|
"duplicate_small.txt": temp_dir / "duplicate_small.txt",
|
|
"binary.dat": temp_dir / "binary.dat"
|
|
}
|
|
|
|
# Create text files with different content
|
|
files["small.txt"].write_text("Small file content")
|
|
files["medium.txt"].write_text("Medium file content " * 100)
|
|
files["large.txt"].write_text("Large file content " * 1000)
|
|
files["duplicate_small.txt"].write_text("Small file content") # Duplicate of small.txt
|
|
files["binary.dat"].write_bytes(os.urandom(1024))
|
|
|
|
yield files
|
|
|
|
@pytest.fixture(scope="function")
|
|
def file_system_with_duplicates(temp_dir: Path) -> Generator[Dict[str, Path], None, None]:
|
|
"""Create a file system structure with duplicates for testing."""
|
|
# Create directory structure
|
|
dirs = {
|
|
"documents": temp_dir / "documents",
|
|
"images": temp_dir / "images",
|
|
"backups": temp_dir / "backups"
|
|
}
|
|
|
|
for dir_path in dirs.values():
|
|
dir_path.mkdir()
|
|
|
|
# Create duplicate files in different directories
|
|
original_content = "This is original content for duplicate testing."
|
|
|
|
files = {
|
|
"original": dirs["documents"] / "original.txt",
|
|
"duplicate1": dirs["images"] / "copy.txt",
|
|
"duplicate2": dirs["backups"] / "backup.txt",
|
|
"unique1": dirs["documents"] / "unique1.txt",
|
|
"unique2": dirs["images"] / "unique2.txt"
|
|
}
|
|
|
|
# Write content
|
|
files["original"].write_text(original_content)
|
|
files["duplicate1"].write_text(original_content) # Exact duplicate
|
|
files["duplicate2"].write_text(original_content) # Exact duplicate
|
|
files["unique1"].write_text("Unique content 1")
|
|
files["unique2"].write_text("Unique content 2")
|
|
|
|
yield files
|
|
|
|
## Configuration Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_config() -> Generator[Dict[str, Any], None, None]:
|
|
"""Provide a mock configuration dictionary for testing."""
|
|
yield {
|
|
"database": {
|
|
"path": ":memory:",
|
|
"timeout": 10.0,
|
|
"journal_mode": "WAL"
|
|
},
|
|
"scan": {
|
|
"min_file_size": "1KB",
|
|
"max_file_size": "10MB",
|
|
"default_extensions": ["txt", "pdf", "jpg"],
|
|
"exclude_dirs": [".git", ".venv"]
|
|
},
|
|
"performance": {
|
|
"max_workers": 4,
|
|
"batch_size": 100,
|
|
"chunk_size": "1MB"
|
|
},
|
|
"logging": {
|
|
"level": "DEBUG",
|
|
"file": "test.log",
|
|
"max_size": "5MB",
|
|
"backup_count": 3
|
|
}
|
|
}
|
|
|
|
@pytest.fixture(scope="function")
|
|
def temp_config_file(temp_dir: Path, mock_config: Dict[str, Any]) -> Generator[Path, None, None]:
|
|
"""Create a temporary configuration file."""
|
|
config_file = temp_dir / "config.toml"
|
|
import tomlkit as toml
|
|
|
|
with open(config_file, "w") as f:
|
|
toml.dump(mock_config, f)
|
|
|
|
yield config_file
|
|
|
|
@pytest.fixture(scope="function")
|
|
def loaded_config(temp_config_file: Path) -> Generator[config.ConfigManager, None, None]:
|
|
"""Load and provide a configuration object from the temp config file."""
|
|
cfg = config.ConfigManager(str(temp_config_file))
|
|
yield cfg
|
|
|
|
## Tool System Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def reset_tool_registry() -> Generator[None, None, None]:
|
|
"""Reset ToolRegistry singleton between tests for test isolation.
|
|
|
|
This fixture ensures each test starts with a clean ToolRegistry state,
|
|
preventing test pollution from the singleton pattern.
|
|
"""
|
|
try:
|
|
yield
|
|
finally:
|
|
# Reset the singleton instance after each test
|
|
ToolRegistry._reset_instance()
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_tool_registry(reset_tool_registry: None) -> Generator[ToolRegistry, None, None]:
|
|
"""Create a mock tool registry for testing.
|
|
|
|
Uses reset_tool_registry fixture to ensure test isolation.
|
|
"""
|
|
registry = ToolRegistry()
|
|
yield registry
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_tool_loader(mock_tool_registry: ToolRegistry) -> Generator[ToolLoader, None, None]:
|
|
"""Create a mock tool loader for testing."""
|
|
loader = ToolLoader(mock_tool_registry)
|
|
yield loader
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_tool() -> Generator[MagicMock, None, None]:
|
|
"""Create a mock tool object for testing."""
|
|
mock = MagicMock()
|
|
mock.name = "test_tool"
|
|
mock.version = "1.0.0"
|
|
mock.author = "Test Author"
|
|
mock.description = "A test tool for NoDupeLabs"
|
|
mock.initialize = MagicMock()
|
|
mock.execute = MagicMock()
|
|
mock.cleanup = MagicMock()
|
|
yield mock
|
|
|
|
@pytest.fixture(scope="function")
|
|
def registered_mock_tool(mock_tool_registry: ToolRegistry, mock_tool: MagicMock) -> Generator[MagicMock, None, None]:
|
|
"""Register a mock tool and yield it."""
|
|
mock_tool_registry.register_tool(mock_tool)
|
|
yield mock_tool
|
|
|
|
## Resource Management Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def memory_mapped_file(temp_file: Path) -> Generator[Any, None, None]:
|
|
"""Create a memory-mapped file for testing."""
|
|
import mmap
|
|
|
|
with open(temp_file, "r+b") as f:
|
|
# Write some content first
|
|
f.write(b"This is test content for memory mapping")
|
|
f.flush()
|
|
|
|
# Create memory map
|
|
mmap_obj = mmap.mmap(f.fileno(), 0)
|
|
try:
|
|
yield mmap_obj
|
|
finally:
|
|
mmap_obj.close()
|
|
|
|
@pytest.fixture(scope="function")
|
|
def resource_pool() -> Generator[Any, None, None]:
|
|
"""Create a test resource pool.
|
|
|
|
Note: ResourcePool module not available, using MagicMock.
|
|
"""
|
|
from unittest.mock import MagicMock
|
|
pool = MagicMock()
|
|
pool.max_size = 5
|
|
try:
|
|
yield pool
|
|
finally:
|
|
pass # Mock doesn't need cleanup
|
|
|
|
## Container and Dependency Injection Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def test_container() -> Generator[ServiceContainer, None, None]:
|
|
"""Create a test container with basic services."""
|
|
cont = ServiceContainer()
|
|
|
|
# Register basic services
|
|
cont.register_service("config", mock_config())
|
|
cont.register_service("db_connection", DatabaseConnection(":memory:"))
|
|
cont.register_service("tool_registry", ToolRegistry())
|
|
|
|
yield cont
|
|
|
|
@pytest.fixture(scope="function")
|
|
def container_with_mocks(test_container: ServiceContainer) -> Generator[ServiceContainer, None, None]:
|
|
"""Create a container with mock services for testing."""
|
|
# Add mock services
|
|
test_container.register_service("mock_service", MagicMock())
|
|
test_container.register_service("file_system", MagicMock())
|
|
test_container.register_service("logger", MagicMock())
|
|
|
|
yield test_container
|
|
|
|
## Error and Exception Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_error_condition() -> Generator[Callable[[Exception], None], None, None]:
|
|
"""Fixture to simulate error conditions.
|
|
|
|
Returns:
|
|
A function that raises a specified error type with a given message.
|
|
"""
|
|
def raise_error(error_type: Exception = Exception, message: str = "Test error") -> Callable:
|
|
"""Create a function that raises a specified error.
|
|
|
|
Args:
|
|
error_type: The type of exception to raise.
|
|
message: The error message.
|
|
|
|
Returns:
|
|
A function that when called raises the specified error.
|
|
"""
|
|
def _inner():
|
|
"""Helper function that raises the specified error."""
|
|
raise error_type(message)
|
|
return _inner
|
|
|
|
yield raise_error
|
|
|
|
@pytest.fixture(scope="function")
|
|
def mock_timeout() -> Generator[Callable[[float], None], None, None]:
|
|
"""Fixture to simulate timeout conditions.
|
|
|
|
Returns:
|
|
A function that sleeps longer than the specified timeout duration.
|
|
"""
|
|
def timeout_after(seconds: float) -> Callable:
|
|
"""Create a function that sleeps longer than the specified timeout.
|
|
|
|
Args:
|
|
seconds: The timeout duration in seconds.
|
|
|
|
Returns:
|
|
A function that when called sleeps longer than the timeout.
|
|
"""
|
|
import time
|
|
def _inner():
|
|
"""Helper function that raises the specified error."""
|
|
time.sleep(seconds + 0.1)
|
|
return _inner
|
|
|
|
yield timeout_after
|
|
|
|
## Performance Testing Fixtures
|
|
|
|
@pytest.fixture(scope="function")
|
|
def performance_monitor() -> Generator[Callable, None, None]:
|
|
"""Fixture for monitoring test performance."""
|
|
import time
|
|
|
|
def monitor(func: Callable, *args, **kwargs) -> Dict[str, float]:
|
|
"""Monitor function execution time and memory usage.
|
|
|
|
Args:
|
|
func: The function to execute and monitor.
|
|
*args: Positional arguments for the function.
|
|
**kwargs: Keyword arguments for the function.
|
|
|
|
Returns:
|
|
Dictionary containing execution_time, memory_increase, and result.
|
|
"""
|
|
start_time = time.time()
|
|
start_mem = get_memory_usage()
|
|
|
|
result = func(*args, **kwargs)
|
|
|
|
end_time = time.time()
|
|
end_mem = get_memory_usage()
|
|
|
|
return {
|
|
"execution_time": end_time - start_time,
|
|
"memory_increase": end_mem - start_mem,
|
|
"result": result
|
|
}
|
|
|
|
def get_memory_usage() -> float:
|
|
"""Get the current memory usage of the process.
|
|
|
|
Returns:
|
|
Memory usage in megabytes.
|
|
"""
|
|
import psutil
|
|
import os
|
|
process = psutil.Process(os.getpid())
|
|
return process.memory_info().rss / 1024 / 1024 # MB
|
|
|
|
yield monitor
|
|
|
|
## Helper Functions
|
|
|
|
def create_test_file(path: Path, size: int = 1024, content: str = None) -> Path:
|
|
"""Helper function to create test files."""
|
|
if content is None:
|
|
content = "A" * size
|
|
path.write_text(content)
|
|
return path
|
|
|
|
def create_file_structure(base_dir: Path, structure: Dict[str, Any]) -> Dict[str, Path]:
|
|
"""Helper function to create complex file structures."""
|
|
created_files = {}
|
|
|
|
for name, item in structure.items():
|
|
if isinstance(item, dict):
|
|
# It's a directory
|
|
dir_path = base_dir / name
|
|
dir_path.mkdir(exist_ok=True)
|
|
created_files.update(create_file_structure(dir_path, item))
|
|
else:
|
|
# It's a file
|
|
file_path = base_dir / name
|
|
if isinstance(item, str):
|
|
file_path.write_text(item)
|
|
else:
|
|
file_path.write_bytes(item)
|
|
created_files[name] = file_path
|
|
|
|
return created_files
|
|
|
|
## Test Configuration Hooks
|
|
|
|
def pytest_configure(config):
|
|
"""Pytest configuration hook."""
|
|
# Add custom markers
|
|
config.addinivalue_line(
|
|
"markers",
|
|
"performance: mark test as performance test"
|
|
)
|
|
config.addinivalue_line(
|
|
"markers",
|
|
"stress: mark test as stress test"
|
|
)
|
|
|
|
def pytest_collection_modifyitems(items):
|
|
"""Modify test collection order."""
|
|
# Run performance tests last
|
|
performance_tests = [item for item in items if "performance" in item.keywords]
|
|
other_tests = [item for item in items if "performance" not in item.keywords]
|
|
items[:] = other_tests + performance_tests
|
|
|
|
def pytest_runtest_setup(item):
|
|
"""Test setup hook."""
|
|
# Add test timing information
|
|
if "performance" in item.keywords:
|
|
item.user_properties.append(("test_type", "performance"))
|
|
elif "stress" in item.keywords:
|
|
item.user_properties.append(("test_type", "stress"))
|
|
else:
|
|
item.user_properties.append(("test_type", "standard"))
|