mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
3521 lines
104 KiB
Python
3521 lines
104 KiB
Python
"""NoDupeLabs Error Test Utilities
|
|
|
|
Helper functions for error condition simulation and testing.
|
|
"""
|
|
|
|
import contextlib
|
|
from typing import Dict, Any, List, Optional, Union, Callable, Type
|
|
from unittest.mock import MagicMock, patch
|
|
import random
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
def simulate_file_system_errors(
|
|
error_type: str = "permission",
|
|
operation: str = "read"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate file system errors.
|
|
|
|
Args:
|
|
error_type: Type of error to simulate
|
|
operation: File operation to fail
|
|
|
|
Returns:
|
|
Context manager for error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating file system errors."""
|
|
error_map = {
|
|
"permission": PermissionError("Operation not permitted"),
|
|
"not_found": FileNotFoundError("File not found"),
|
|
"disk_full": OSError("No space left on device"),
|
|
"io_error": IOError("Input/output error"),
|
|
"access_denied": PermissionError("Access denied")
|
|
}
|
|
|
|
error = error_map.get(error_type, IOError("File system error"))
|
|
|
|
original_open = open
|
|
original_path = Path
|
|
|
|
class MockPath:
|
|
"""Mock Path class to simulate file system errors."""
|
|
|
|
def __init__(self, *args):
|
|
"""Initialize MockPath with path arguments."""
|
|
self.path = original_path(*args)
|
|
|
|
def __truediv__(self, other):
|
|
"""Support path division operation."""
|
|
return MockPath(str(self.path) + "/" + str(other))
|
|
|
|
def __str__(self):
|
|
"""Return string representation of path."""
|
|
return str(self.path)
|
|
|
|
def read_text(self, *args, **kwargs):
|
|
"""Simulate read_text error if operation is 'read'."""
|
|
if operation == "read":
|
|
raise error
|
|
return self.path.read_text(*args, **kwargs)
|
|
|
|
def write_text(self, *args, **kwargs):
|
|
"""Simulate write_text error if operation is 'write'."""
|
|
if operation == "write":
|
|
raise error
|
|
return self.path.write_text(*args, **kwargs)
|
|
|
|
def exists(self):
|
|
"""Simulate exists error if operation is 'exists'."""
|
|
if operation == "exists":
|
|
raise error
|
|
return self.path.exists()
|
|
|
|
def unlink(self):
|
|
"""Simulate unlink error if operation is 'delete'."""
|
|
if operation == "delete":
|
|
raise error
|
|
return self.path.unlink()
|
|
|
|
def mock_open(*args, **kwargs):
|
|
"""Mock open function to simulate file open errors."""
|
|
if operation == "open":
|
|
raise error
|
|
return original_open(*args, **kwargs)
|
|
|
|
with patch('builtins.open', side_effect=mock_open):
|
|
with patch('pathlib.Path', side_effect=MockPath):
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error test scenarios.
|
|
|
|
Returns:
|
|
List of error test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "file_not_found",
|
|
"error_type": "FileNotFoundError",
|
|
"expected_behavior": "graceful_failure"
|
|
},
|
|
{
|
|
"name": "permission_denied",
|
|
"error_type": "PermissionError",
|
|
"expected_behavior": "retry_or_fail"
|
|
},
|
|
{
|
|
"name": "disk_full",
|
|
"error_type": "OSError",
|
|
"expected_behavior": "cleanup_and_fail"
|
|
},
|
|
{
|
|
"name": "network_timeout",
|
|
"error_type": "TimeoutError",
|
|
"expected_behavior": "retry_with_backoff"
|
|
},
|
|
{
|
|
"name": "invalid_input",
|
|
"error_type": "ValueError",
|
|
"expected_behavior": "validate_and_fail"
|
|
}
|
|
]
|
|
|
|
def simulate_network_errors(
|
|
error_type: str = "timeout",
|
|
failure_rate: float = 1.0
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate network errors.
|
|
|
|
Args:
|
|
error_type: Type of network error to simulate
|
|
failure_rate: Probability of failure (0.0 to 1.0)
|
|
|
|
Returns:
|
|
Context manager for network error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating network errors."""
|
|
error_map = {
|
|
"timeout": TimeoutError("Connection timed out"),
|
|
"connection_refused": ConnectionRefusedError("Connection refused"),
|
|
"dns_failure": OSError("Name or service not known"),
|
|
"ssl_error": OSError("SSL handshake failed"),
|
|
"network_unreachable": OSError("Network is unreachable")
|
|
}
|
|
|
|
error = error_map.get(error_type, OSError("Network error"))
|
|
|
|
original_requests = __import__('requests')
|
|
|
|
class MockRequests:
|
|
"""Mock requests class to simulate network errors."""
|
|
|
|
@staticmethod
|
|
def get(*args, **kwargs):
|
|
"""Simulate GET request with potential error."""
|
|
if random.random() < failure_rate:
|
|
raise error
|
|
return original_requests.get(*args, **kwargs)
|
|
|
|
@staticmethod
|
|
def post(*args, **kwargs):
|
|
"""Simulate POST request with potential error."""
|
|
if random.random() < failure_rate:
|
|
raise error
|
|
return original_requests.post(*args, **kwargs)
|
|
|
|
with patch('requests', MockRequests):
|
|
with patch('urllib.request.urlopen') as mock_urlopen:
|
|
if random.random() < failure_rate:
|
|
mock_urlopen.side_effect = error
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_exception_test_cases() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create exception test cases.
|
|
|
|
Returns:
|
|
List of exception test cases
|
|
"""
|
|
return [
|
|
{
|
|
"name": "value_error",
|
|
"exception": ValueError,
|
|
"message": "Invalid value provided",
|
|
"test_function": lambda: int("invalid")
|
|
},
|
|
{
|
|
"name": "type_error",
|
|
"exception": TypeError,
|
|
"message": "Invalid type provided",
|
|
"test_function": lambda: "string" + 123
|
|
},
|
|
{
|
|
"name": "index_error",
|
|
"exception": IndexError,
|
|
"message": "Index out of range",
|
|
"test_function": lambda: [1, 2, 3][10]
|
|
},
|
|
{
|
|
"name": "key_error",
|
|
"exception": KeyError,
|
|
"message": "Key not found",
|
|
"test_function": lambda: {"a": 1}["b"]
|
|
},
|
|
{
|
|
"name": "attribute_error",
|
|
"exception": AttributeError,
|
|
"message": "Attribute not found",
|
|
"test_function": lambda: "string".nonexistent_method()
|
|
}
|
|
]
|
|
|
|
def simulate_memory_errors(
|
|
error_type: str = "out_of_memory"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate memory errors.
|
|
|
|
Args:
|
|
error_type: Type of memory error to simulate
|
|
|
|
Returns:
|
|
Context manager for memory error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating memory errors."""
|
|
error_map = {
|
|
"out_of_memory": MemoryError("Out of memory"),
|
|
"memory_leak": MemoryError("Memory allocation failed"),
|
|
"stack_overflow": RecursionError("Maximum recursion depth exceeded")
|
|
}
|
|
|
|
error = error_map.get(error_type, MemoryError("Memory error"))
|
|
|
|
original_alloc = __import__('builtins').__dict__['object'].__new__
|
|
|
|
def mock_alloc(cls, *args, **kwargs):
|
|
"""Mock object allocation to simulate memory errors."""
|
|
if random.random() > 0.5: # 50% chance of failure
|
|
raise error
|
|
return original_alloc(cls, *args, **kwargs)
|
|
|
|
with patch('builtins.object.__new__', side_effect=mock_alloc):
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_recovery_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error recovery test scenarios.
|
|
|
|
Returns:
|
|
List of error recovery test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "automatic_retry",
|
|
"error_type": "temporary_failure",
|
|
"recovery_strategy": "retry",
|
|
"max_retries": 3,
|
|
"expected_result": "success"
|
|
},
|
|
{
|
|
"name": "fallback_mechanism",
|
|
"error_type": "permanent_failure",
|
|
"recovery_strategy": "fallback",
|
|
"expected_result": "degraded_functionality"
|
|
},
|
|
{
|
|
"name": "graceful_degradation",
|
|
"error_type": "resource_exhaustion",
|
|
"recovery_strategy": "degrade",
|
|
"expected_result": "reduced_performance"
|
|
},
|
|
{
|
|
"name": "manual_intervention",
|
|
"error_type": "critical_failure",
|
|
"recovery_strategy": "alert",
|
|
"expected_result": "admin_notification"
|
|
}
|
|
]
|
|
|
|
def simulate_database_errors(
|
|
error_type: str = "connection_failed"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate database errors.
|
|
|
|
Args:
|
|
error_type: Type of database error to simulate
|
|
|
|
Returns:
|
|
Context manager for database error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating database errors."""
|
|
import sqlite3
|
|
error_map = {
|
|
"connection_failed": sqlite3.OperationalError("Unable to connect to database"),
|
|
"query_failed": sqlite3.ProgrammingError("SQL syntax error"),
|
|
"constraint_violation": sqlite3.IntegrityError("Constraint violation"),
|
|
"timeout": sqlite3.OperationalError("Database locked"),
|
|
"disk_full": sqlite3.OperationalError("Database or disk is full")
|
|
}
|
|
|
|
error = error_map.get(error_type, sqlite3.Error("Database error"))
|
|
|
|
with patch('sqlite3.connect') as mock_connect:
|
|
mock_conn = MagicMock()
|
|
mock_cursor = MagicMock()
|
|
|
|
if error_type == "connection_failed":
|
|
mock_connect.side_effect = error
|
|
else:
|
|
mock_conn.cursor.return_value = mock_cursor
|
|
if error_type == "query_failed":
|
|
mock_cursor.execute.side_effect = error
|
|
elif error_type == "constraint_violation":
|
|
mock_cursor.execute.side_effect = error
|
|
elif error_type == "timeout":
|
|
mock_conn.commit.side_effect = error
|
|
elif error_type == "disk_full":
|
|
mock_cursor.execute.side_effect = error
|
|
|
|
mock_connect.return_value = mock_conn
|
|
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_injection_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error injection test scenarios.
|
|
|
|
Returns:
|
|
List of error injection test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "random_failures",
|
|
"failure_rate": 0.1,
|
|
"target_components": ["network", "database", "file_system"],
|
|
"expected_behavior": "resilient_operation"
|
|
},
|
|
{
|
|
"name": "cascading_failures",
|
|
"failure_sequence": ["database", "cache", "api"],
|
|
"expected_behavior": "failure_containment"
|
|
},
|
|
{
|
|
"name": "intermittent_failures",
|
|
"failure_pattern": "on_off",
|
|
"expected_behavior": "automatic_recovery"
|
|
}
|
|
]
|
|
|
|
def simulate_tool_errors(
|
|
error_type: str = "loading_failed"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate tool errors.
|
|
|
|
Args:
|
|
error_type: Type of tool error to simulate
|
|
|
|
Returns:
|
|
Context manager for tool error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating tool errors."""
|
|
error_map = {
|
|
"loading_failed": ImportError("Cannot load tool"),
|
|
"initialization_failed": RuntimeError("Tool initialization failed"),
|
|
"execution_failed": ValueError("Tool execution error"),
|
|
"compatibility_error": RuntimeError("Tool compatibility issue"),
|
|
"security_violation": RuntimeError("Tool security violation")
|
|
}
|
|
|
|
error = error_map.get(error_type, RuntimeError("Tool error"))
|
|
|
|
with patch('importlib.import_module') as mock_import:
|
|
if error_type == "loading_failed":
|
|
mock_import.side_effect = error
|
|
else:
|
|
mock_tool = MagicMock()
|
|
if error_type == "initialization_failed":
|
|
mock_tool.initialize.side_effect = error
|
|
elif error_type == "execution_failed":
|
|
mock_tool.execute.side_effect = error
|
|
elif error_type == "compatibility_error":
|
|
mock_tool.metadata = {"version": "incompatible"}
|
|
elif error_type == "security_violation":
|
|
mock_tool.execute.side_effect = error
|
|
|
|
mock_import.return_value = mock_tool
|
|
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_handling_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error handling test scenarios.
|
|
|
|
Returns:
|
|
List of error handling test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "exception_handling",
|
|
"error_type": "ValueError",
|
|
"handling_strategy": "catch_and_log",
|
|
"expected_result": "logged_error"
|
|
},
|
|
{
|
|
"name": "resource_cleanup",
|
|
"error_type": "IOError",
|
|
"handling_strategy": "cleanup_and_rethrow",
|
|
"expected_result": "cleaned_up_resources"
|
|
},
|
|
{
|
|
"name": "fallback_operation",
|
|
"error_type": "TimeoutError",
|
|
"handling_strategy": "use_fallback",
|
|
"expected_result": "fallback_used"
|
|
},
|
|
{
|
|
"name": "retry_operation",
|
|
"error_type": "TemporaryError",
|
|
"handling_strategy": "retry_with_backoff",
|
|
"expected_result": "operation_retry"
|
|
}
|
|
]
|
|
|
|
def simulate_concurrency_errors(
|
|
error_type: str = "race_condition"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate concurrency errors.
|
|
|
|
Args:
|
|
error_type: Type of concurrency error to simulate
|
|
|
|
Returns:
|
|
Context manager for concurrency error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating concurrency errors."""
|
|
import threading
|
|
|
|
error_map = {
|
|
"race_condition": RuntimeError("Race condition detected"),
|
|
"deadlock": RuntimeError("Deadlock detected"),
|
|
"thread_failure": RuntimeError("Thread execution failed"),
|
|
"resource_contention": RuntimeError("Resource contention detected")
|
|
}
|
|
|
|
error = error_map.get(error_type, RuntimeError("Concurrency error"))
|
|
|
|
original_thread = threading.Thread
|
|
|
|
class MockThread(threading.Thread):
|
|
"""Mock Thread class to simulate concurrency errors."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Initialize MockThread with error injection."""
|
|
super().__init__(*args, **kwargs)
|
|
self._error = error if random.random() > 0.7 else None
|
|
|
|
def run(self):
|
|
"""Run method that may inject concurrency errors."""
|
|
if self._error:
|
|
raise self._error
|
|
super().run()
|
|
|
|
with patch('threading.Thread', MockThread):
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_validation_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error validation test scenarios.
|
|
|
|
Returns:
|
|
List of error validation test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "input_validation",
|
|
"test_cases": [
|
|
{"input": None, "expected_error": "ValueError"},
|
|
{"input": -1, "expected_error": "ValueError"},
|
|
{"input": "invalid", "expected_error": "TypeError"}
|
|
]
|
|
},
|
|
{
|
|
"name": "state_validation",
|
|
"test_cases": [
|
|
{"state": "invalid_state", "expected_error": "RuntimeError"},
|
|
{"state": "corrupted_data", "expected_error": "DataError"}
|
|
]
|
|
},
|
|
{
|
|
"name": "security_validation",
|
|
"test_cases": [
|
|
{"permission": "denied", "expected_error": "PermissionError"},
|
|
{"access": "unauthorized", "expected_error": "SecurityError"}
|
|
]
|
|
}
|
|
]
|
|
|
|
def simulate_resource_exhaustion_errors(
|
|
resource_type: str = "memory"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate resource exhaustion errors.
|
|
|
|
Args:
|
|
resource_type: Type of resource to exhaust
|
|
|
|
Returns:
|
|
Context manager for resource exhaustion simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating resource exhaustion errors."""
|
|
error_map = {
|
|
"memory": MemoryError("Out of memory"),
|
|
"cpu": RuntimeError("CPU resources exhausted"),
|
|
"disk": OSError("No space left on device"),
|
|
"handles": OSError("Too many open files"),
|
|
"threads": RuntimeError("Too many threads")
|
|
}
|
|
|
|
error = error_map.get(resource_type, RuntimeError("Resource exhausted"))
|
|
|
|
if resource_type == "memory":
|
|
original_alloc = __import__('builtins').__dict__['object'].__new__
|
|
|
|
def mock_alloc(cls, *args, **kwargs):
|
|
"""Mock object allocation for memory exhaustion simulation."""
|
|
if random.random() > 0.3: # 70% chance of failure
|
|
raise error
|
|
return original_alloc(cls, *args, **kwargs)
|
|
|
|
with patch('builtins.object.__new__', side_effect=mock_alloc):
|
|
yield
|
|
|
|
elif resource_type == "disk":
|
|
def mock_write(*args, **kwargs):
|
|
"""Mock write operation for disk exhaustion simulation."""
|
|
if random.random() > 0.5: # 50% chance of failure
|
|
raise error
|
|
original_write(*args, **kwargs)
|
|
|
|
original_write = open
|
|
with patch('builtins.open', side_effect=mock_write):
|
|
yield
|
|
|
|
else:
|
|
# For other resource types, use a simpler approach
|
|
def resource_check():
|
|
"""Check resource availability for other resource types."""
|
|
if random.random() > 0.7: # 30% chance of failure
|
|
raise error
|
|
|
|
with patch('resource.getrlimit', side_effect=resource_check):
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_monitoring_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error monitoring test scenarios.
|
|
|
|
Returns:
|
|
List of error monitoring test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "error_logging",
|
|
"monitoring_type": "logging",
|
|
"expected_behavior": "error_logged",
|
|
"verification": "check_log_files"
|
|
},
|
|
{
|
|
"name": "error_metrics",
|
|
"monitoring_type": "metrics",
|
|
"expected_behavior": "metrics_updated",
|
|
"verification": "check_metrics_endpoint"
|
|
},
|
|
{
|
|
"name": "error_alerting",
|
|
"monitoring_type": "alerting",
|
|
"expected_behavior": "alert_sent",
|
|
"verification": "check_alert_system"
|
|
},
|
|
{
|
|
"name": "error_tracing",
|
|
"monitoring_type": "tracing",
|
|
"expected_behavior": "trace_recorded",
|
|
"verification": "check_tracing_system"
|
|
}
|
|
]
|
|
|
|
def simulate_timeout_errors(
|
|
timeout_type: str = "operation_timeout"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate timeout errors.
|
|
|
|
Args:
|
|
timeout_type: Type of timeout error to simulate
|
|
|
|
Returns:
|
|
Context manager for timeout error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating timeout errors."""
|
|
import time
|
|
|
|
error_map = {
|
|
"operation_timeout": TimeoutError("Operation timed out"),
|
|
"connection_timeout": TimeoutError("Connection timed out"),
|
|
"read_timeout": TimeoutError("Read operation timed out"),
|
|
"write_timeout": TimeoutError("Write operation timed out")
|
|
}
|
|
|
|
error = error_map.get(timeout_type, TimeoutError("Timeout error"))
|
|
|
|
original_monotonic = time.monotonic
|
|
original_sleep = time.sleep
|
|
start_time = original_monotonic()
|
|
|
|
def slow_monotonic():
|
|
"""Mock monotonic time that appears to run slowly."""
|
|
elapsed = original_monotonic() - start_time
|
|
if elapsed > 1.0: # After 1 second, start causing timeouts
|
|
raise error
|
|
return elapsed
|
|
|
|
def slow_sleep(seconds):
|
|
"""Mock sleep that causes timeouts for long durations."""
|
|
if seconds > 0.1: # Long sleeps cause timeouts
|
|
raise error
|
|
original_sleep(seconds)
|
|
|
|
with patch('time.monotonic', side_effect=slow_monotonic):
|
|
with patch('time.sleep', side_effect=slow_sleep):
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def create_error_recovery_validation_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create error recovery validation scenarios.
|
|
|
|
Returns:
|
|
List of error recovery validation scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "data_consistency_after_error",
|
|
"error_type": "database_error",
|
|
"recovery_method": "transaction_rollback",
|
|
"validation": "verify_data_integrity"
|
|
},
|
|
{
|
|
"name": "resource_cleanup_after_error",
|
|
"error_type": "file_error",
|
|
"recovery_method": "resource_release",
|
|
"validation": "verify_no_resource_leaks"
|
|
},
|
|
{
|
|
"name": "state_consistency_after_error",
|
|
"error_type": "state_error",
|
|
"recovery_method": "state_reset",
|
|
"validation": "verify_consistent_state"
|
|
}
|
|
]
|
|
|
|
|
|
import time
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List, Optional, Union, Callable
|
|
import contextlib
|
|
import os
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
try:
|
|
import resource
|
|
RESOURCE_AVAILABLE = True
|
|
except ImportError:
|
|
RESOURCE_AVAILABLE = False
|
|
|
|
try:
|
|
import psutil
|
|
PSUTIL_AVAILABLE = True
|
|
except ImportError:
|
|
PSUTIL_AVAILABLE = False
|
|
|
|
def benchmark_function_performance(
|
|
func: Callable,
|
|
iterations: int = 100,
|
|
warmup_iterations: int = 10,
|
|
*args,
|
|
**kwargs
|
|
) -> Dict[str, float]:
|
|
"""
|
|
Benchmark a function's performance.
|
|
|
|
Args:
|
|
func: Function to benchmark
|
|
iterations: Number of benchmark iterations
|
|
warmup_iterations: Number of warmup iterations
|
|
*args: Positional arguments for the function
|
|
**kwargs: Keyword arguments for the function
|
|
|
|
Returns:
|
|
Dictionary of performance metrics
|
|
"""
|
|
# Warmup
|
|
for _ in range(warmup_iterations):
|
|
func(*args, **kwargs)
|
|
|
|
# Benchmark
|
|
start_time = time.time()
|
|
|
|
for _ in range(iterations):
|
|
func(*args, **kwargs)
|
|
|
|
end_time = time.time()
|
|
|
|
total_time = end_time - start_time
|
|
avg_time = total_time / iterations
|
|
ops_per_sec = iterations / total_time
|
|
|
|
return {
|
|
"total_time": total_time,
|
|
"average_time": avg_time,
|
|
"operations_per_second": ops_per_sec,
|
|
"iterations": iterations
|
|
}
|
|
|
|
def measure_memory_usage(
|
|
func: Callable,
|
|
iterations: int = 10,
|
|
*args,
|
|
**kwargs
|
|
) -> Dict[str, float]:
|
|
"""
|
|
Measure memory usage of a function.
|
|
|
|
Args:
|
|
func: Function to measure
|
|
iterations: Number of iterations
|
|
*args: Positional arguments for the function
|
|
**kwargs: Keyword arguments for the function
|
|
|
|
Returns:
|
|
Dictionary of memory usage metrics
|
|
"""
|
|
if not PSUTIL_AVAILABLE:
|
|
# Fallback implementation when psutil is not available
|
|
# Run the function but return mock memory values
|
|
for _ in range(iterations):
|
|
func(*args, **kwargs)
|
|
|
|
return {
|
|
"initial_memory": 0,
|
|
"final_memory": 0,
|
|
"total_memory_used": 0,
|
|
"average_memory_per_call": 0,
|
|
"iterations": iterations,
|
|
"warning": "psutil not available, using fallback implementation"
|
|
}
|
|
|
|
# Get initial memory usage
|
|
process = psutil.Process(os.getpid())
|
|
initial_mem = process.memory_info().rss
|
|
|
|
# Run function multiple times
|
|
for _ in range(iterations):
|
|
func(*args, **kwargs)
|
|
|
|
# Get final memory usage
|
|
final_mem = process.memory_info().rss
|
|
|
|
# Calculate memory usage
|
|
total_memory_used = final_mem - initial_mem
|
|
avg_memory_per_call = total_memory_used / iterations
|
|
|
|
return {
|
|
"initial_memory": initial_mem,
|
|
"final_memory": final_mem,
|
|
"total_memory_used": total_memory_used,
|
|
"average_memory_per_call": avg_memory_per_call,
|
|
"iterations": iterations
|
|
}
|
|
|
|
def create_performance_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create performance test scenarios.
|
|
|
|
Returns:
|
|
List of performance test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "small_dataset",
|
|
"data_size": 100,
|
|
"expected_max_time": 0.1,
|
|
"expected_max_memory": 1024 * 1024 # 1MB
|
|
},
|
|
{
|
|
"name": "medium_dataset",
|
|
"data_size": 10000,
|
|
"expected_max_time": 1.0,
|
|
"expected_max_memory": 10 * 1024 * 1024 # 10MB
|
|
},
|
|
{
|
|
"name": "large_dataset",
|
|
"data_size": 1000000,
|
|
"expected_max_time": 10.0,
|
|
"expected_max_memory": 100 * 1024 * 1024 # 100MB
|
|
}
|
|
]
|
|
|
|
def simulate_resource_constraints(
|
|
cpu_limit: Optional[float] = None,
|
|
memory_limit: Optional[int] = None
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate resource constraints.
|
|
|
|
Args:
|
|
cpu_limit: CPU limit (percentage)
|
|
memory_limit: Memory limit in bytes
|
|
|
|
Returns:
|
|
Context manager for resource constraints
|
|
"""
|
|
@contextlib.contextmanager
|
|
def resource_context():
|
|
"""Inner context manager for simulating resource constraints."""
|
|
original_limits = {}
|
|
|
|
try:
|
|
if cpu_limit is not None:
|
|
# Simulate CPU limit by adding artificial delay
|
|
original_limits['cpu'] = cpu_limit
|
|
|
|
if memory_limit is not None and RESOURCE_AVAILABLE:
|
|
# Set memory limit using resource module
|
|
if hasattr(resource, 'RLIMIT_AS'):
|
|
original_limits['memory'] = resource.getrlimit(resource.RLIMIT_AS)
|
|
resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
|
|
else:
|
|
# Resource module available but RLIMIT_AS not supported
|
|
print("Warning: RLIMIT_AS not available on this platform")
|
|
elif memory_limit is not None and not RESOURCE_AVAILABLE:
|
|
print("Warning: resource module not available, memory limits will not be enforced")
|
|
|
|
yield
|
|
|
|
finally:
|
|
# Restore original limits
|
|
if 'memory' in original_limits and RESOURCE_AVAILABLE and hasattr(resource, 'RLIMIT_AS'):
|
|
resource.setrlimit(resource.RLIMIT_AS, original_limits['memory'])
|
|
|
|
return resource_context
|
|
|
|
def create_load_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create load test scenarios.
|
|
|
|
Returns:
|
|
List of load test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "low_load",
|
|
"concurrent_users": 10,
|
|
"request_rate": 100,
|
|
"duration": 60,
|
|
"expected_response_time": 0.1
|
|
},
|
|
{
|
|
"name": "medium_load",
|
|
"concurrent_users": 100,
|
|
"request_rate": 1000,
|
|
"duration": 300,
|
|
"expected_response_time": 0.5
|
|
},
|
|
{
|
|
"name": "high_load",
|
|
"concurrent_users": 1000,
|
|
"request_rate": 10000,
|
|
"duration": 600,
|
|
"expected_response_time": 1.0
|
|
}
|
|
]
|
|
|
|
def benchmark_file_operations(
|
|
file_size: int = 1024 * 1024, # 1MB
|
|
operations: List[str] = None,
|
|
iterations: int = 100
|
|
) -> Dict[str, float]:
|
|
"""
|
|
Benchmark file operations performance.
|
|
|
|
Args:
|
|
file_size: Size of test file in bytes
|
|
operations: List of operations to benchmark
|
|
iterations: Number of iterations
|
|
|
|
Returns:
|
|
Dictionary of file operation timings
|
|
"""
|
|
if operations is None:
|
|
operations = ["read", "write", "copy", "delete"]
|
|
|
|
results = {}
|
|
temp_dir = Path(tempfile.mkdtemp())
|
|
|
|
try:
|
|
test_file = temp_dir / "test_file.dat"
|
|
test_file_copy = temp_dir / "test_file_copy.dat"
|
|
|
|
# Create test file
|
|
with open(test_file, "wb") as f:
|
|
f.write(os.urandom(file_size))
|
|
|
|
for op in operations:
|
|
if op == "read":
|
|
def read_operation():
|
|
"""Read operation for benchmarking."""
|
|
with open(test_file, "rb") as f:
|
|
f.read()
|
|
|
|
results[op] = benchmark_function_performance(
|
|
read_operation, iterations
|
|
)["average_time"]
|
|
|
|
elif op == "write":
|
|
def write_operation():
|
|
"""Write operation for benchmarking."""
|
|
with open(test_file, "wb") as f:
|
|
f.write(os.urandom(file_size))
|
|
|
|
results[op] = benchmark_function_performance(
|
|
write_operation, iterations
|
|
)["average_time"]
|
|
|
|
elif op == "copy":
|
|
def copy_operation():
|
|
"""Copy operation for benchmarking."""
|
|
import shutil
|
|
shutil.copy2(test_file, test_file_copy)
|
|
if test_file_copy.exists():
|
|
test_file_copy.unlink()
|
|
|
|
results[op] = benchmark_function_performance(
|
|
copy_operation, iterations
|
|
)["average_time"]
|
|
|
|
elif op == "delete":
|
|
def delete_operation():
|
|
"""Delete operation for benchmarking."""
|
|
test_file.unlink()
|
|
with open(test_file, "wb") as f:
|
|
f.write(os.urandom(file_size))
|
|
|
|
results[op] = benchmark_function_performance(
|
|
delete_operation, iterations
|
|
)["average_time"]
|
|
|
|
finally:
|
|
# Cleanup
|
|
import shutil
|
|
shutil.rmtree(temp_dir)
|
|
|
|
return results
|
|
|
|
def create_performance_monitor() -> Callable:
|
|
"""
|
|
Create a performance monitor context manager.
|
|
|
|
Returns:
|
|
Context manager for performance monitoring
|
|
"""
|
|
@contextlib.contextmanager
|
|
def monitor_context():
|
|
"""Inner context manager for performance monitoring."""
|
|
start_time = time.time()
|
|
|
|
if PSUTIL_AVAILABLE:
|
|
start_mem = psutil.Process(os.getpid()).memory_info().rss
|
|
|
|
yield
|
|
|
|
end_time = time.time()
|
|
|
|
if PSUTIL_AVAILABLE:
|
|
end_mem = psutil.Process(os.getpid()).memory_info().rss
|
|
memory_used = end_mem - start_mem
|
|
cpu_usage = psutil.cpu_percent(interval=0.1)
|
|
else:
|
|
memory_used = 0
|
|
cpu_usage = 0
|
|
|
|
elapsed_time = end_time - start_time
|
|
|
|
print(f"Performance Monitor Results:")
|
|
print(f" Execution Time: {elapsed_time:.4f} seconds")
|
|
|
|
if PSUTIL_AVAILABLE:
|
|
print(f" Memory Used: {memory_used / 1024 / 1024:.2f} MB")
|
|
print(f" CPU Usage: {cpu_usage}%")
|
|
else:
|
|
print(f" Memory Used: N/A (psutil not available)")
|
|
print(f" CPU Usage: N/A (psutil not available)")
|
|
|
|
return monitor_context
|
|
|
|
def simulate_slow_operations(
|
|
delay: float = 0.1,
|
|
variability: float = 0.05
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate slow operations.
|
|
|
|
Args:
|
|
delay: Base delay in seconds
|
|
variability: Random variability in delay
|
|
|
|
Returns:
|
|
Context manager for slow operation simulation
|
|
"""
|
|
import random
|
|
|
|
@contextlib.contextmanager
|
|
def slow_context():
|
|
"""Inner context manager for simulating slow operations."""
|
|
original_monotonic = time.monotonic
|
|
start_time = original_monotonic()
|
|
|
|
def slow_monotonic():
|
|
"""Mock monotonic time that adds artificial delay."""
|
|
elapsed = original_monotonic() - start_time
|
|
variability_factor = 1.0 + random.uniform(-variability, variability)
|
|
return elapsed + (delay * variability_factor)
|
|
|
|
# Patch time functions
|
|
with patch('time.monotonic', side_effect=slow_monotonic):
|
|
with patch('time.sleep', side_effect=lambda x: time.sleep(x * 10)):
|
|
yield
|
|
|
|
return slow_context
|
|
|
|
def create_stress_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create stress test scenarios.
|
|
|
|
Returns:
|
|
List of stress test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "memory_stress",
|
|
"type": "memory",
|
|
"target": "high_memory_usage",
|
|
"duration": 300,
|
|
"expected_behavior": "graceful_degradation"
|
|
},
|
|
{
|
|
"name": "cpu_stress",
|
|
"type": "cpu",
|
|
"target": "high_cpu_usage",
|
|
"duration": 180,
|
|
"expected_behavior": "resource_throttling"
|
|
},
|
|
{
|
|
"name": "io_stress",
|
|
"type": "io",
|
|
"target": "high_disk_usage",
|
|
"duration": 240,
|
|
"expected_behavior": "queue_management"
|
|
}
|
|
]
|
|
|
|
def benchmark_database_operations(
|
|
db_connection: Any,
|
|
queries: List[str],
|
|
iterations: int = 100
|
|
) -> Dict[str, float]:
|
|
"""
|
|
Benchmark database operations performance.
|
|
|
|
Args:
|
|
db_connection: Database connection
|
|
queries: List of SQL queries to benchmark
|
|
iterations: Number of iterations
|
|
|
|
Returns:
|
|
Dictionary of database operation timings
|
|
"""
|
|
results = {}
|
|
|
|
for i, query in enumerate(queries):
|
|
def query_operation():
|
|
"""Execute a database query for benchmarking."""
|
|
cursor = db_connection.cursor()
|
|
cursor.execute(query)
|
|
cursor.fetchall()
|
|
cursor.close()
|
|
|
|
timing = benchmark_function_performance(
|
|
query_operation, iterations
|
|
)["average_time"]
|
|
|
|
results[f"query_{i}"] = timing
|
|
|
|
return results
|
|
|
|
def create_network_performance_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create network performance test scenarios.
|
|
|
|
Returns:
|
|
List of network performance test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "low_latency",
|
|
"latency": 10, # ms
|
|
"bandwidth": 100, # Mbps
|
|
"packet_loss": 0.0 # 0%
|
|
},
|
|
{
|
|
"name": "medium_latency",
|
|
"latency": 100, # ms
|
|
"bandwidth": 10, # Mbps
|
|
"packet_loss": 0.1 # 1%
|
|
},
|
|
{
|
|
"name": "high_latency",
|
|
"latency": 500, # ms
|
|
"bandwidth": 1, # Mbps
|
|
"packet_loss": 0.5 # 5%
|
|
}
|
|
]
|
|
|
|
def measure_concurrency_performance(
|
|
func: Callable,
|
|
worker_counts: List[int] = [1, 2, 4, 8, 16],
|
|
iterations: int = 100,
|
|
*args,
|
|
**kwargs
|
|
) -> Dict[int, float]:
|
|
"""
|
|
Measure performance with different levels of concurrency.
|
|
|
|
Args:
|
|
func: Function to test
|
|
worker_counts: List of worker counts to test
|
|
iterations: Number of iterations per worker count
|
|
*args: Positional arguments for the function
|
|
**kwargs: Keyword arguments for the function
|
|
|
|
Returns:
|
|
Dictionary mapping worker counts to performance metrics
|
|
"""
|
|
import concurrent.futures
|
|
|
|
results = {}
|
|
|
|
for workers in worker_counts:
|
|
def concurrent_operation():
|
|
"""Execute function with multiple workers."""
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
|
|
futures = [executor.submit(func, *args, **kwargs) for _ in range(iterations)]
|
|
concurrent.futures.wait(futures)
|
|
|
|
timing = benchmark_function_performance(
|
|
concurrent_operation, 1
|
|
)["total_time"]
|
|
|
|
results[workers] = {
|
|
"total_time": timing,
|
|
"throughput": iterations / timing
|
|
}
|
|
|
|
return results
|
|
|
|
def create_performance_regression_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create performance regression test scenarios.
|
|
|
|
Returns:
|
|
List of performance regression test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "baseline_performance",
|
|
"description": "Establish baseline performance metrics",
|
|
"metrics": {
|
|
"max_response_time": 0.5,
|
|
"max_memory_usage": 50 * 1024 * 1024, # 50MB
|
|
"min_throughput": 100 # operations per second
|
|
}
|
|
},
|
|
{
|
|
"name": "regression_detection",
|
|
"description": "Detect performance regressions",
|
|
"thresholds": {
|
|
"response_time_increase": 0.2, # 20% increase
|
|
"memory_increase": 0.1, # 10% increase
|
|
"throughput_decrease": 0.15 # 15% decrease
|
|
}
|
|
}
|
|
]
|
|
|
|
def simulate_performance_degradation(
|
|
degradation_factor: float = 0.1,
|
|
degradation_type: str = "linear"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate performance degradation.
|
|
|
|
Args:
|
|
degradation_factor: Performance degradation factor
|
|
degradation_type: Type of degradation (linear, exponential)
|
|
|
|
Returns:
|
|
Context manager for performance degradation simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def degradation_context():
|
|
"""Inner context manager for simulating performance degradation."""
|
|
call_count = 0
|
|
original_monotonic = time.monotonic
|
|
start_time = original_monotonic()
|
|
|
|
def degraded_monotonic():
|
|
"""Mock monotonic time with performance degradation."""
|
|
nonlocal call_count
|
|
call_count += 1
|
|
|
|
elapsed = original_monotonic() - start_time
|
|
|
|
if degradation_type == "linear":
|
|
degradation = degradation_factor * call_count
|
|
else: # exponential
|
|
degradation = degradation_factor ** call_count
|
|
|
|
return elapsed + degradation
|
|
|
|
def degraded_sleep(seconds):
|
|
"""Mock sleep with performance degradation."""
|
|
original_sleep = time.sleep
|
|
original_sleep(seconds * (1 + degradation_factor))
|
|
|
|
with patch('time.monotonic', side_effect=degraded_monotonic):
|
|
with patch('time.sleep', side_effect=lambda x: time.sleep(x * (1 + degradation_factor))):
|
|
yield
|
|
|
|
return degradation_context
|
|
|
|
def create_resource_monitoring_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create resource monitoring test scenarios.
|
|
|
|
Returns:
|
|
List of resource monitoring test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "normal_operation",
|
|
"expected_cpu_usage": 0.3, # 30%
|
|
"expected_memory_usage": 100 * 1024 * 1024, # 100MB
|
|
"expected_disk_io": 1024 * 1024 # 1MB/s
|
|
},
|
|
{
|
|
"name": "high_load_operation",
|
|
"expected_cpu_usage": 0.8, # 80%
|
|
"expected_memory_usage": 500 * 1024 * 1024, # 500MB
|
|
"expected_disk_io": 10 * 1024 * 1024 # 10MB/s
|
|
},
|
|
{
|
|
"name": "resource_leak_detection",
|
|
"monitoring_duration": 300, # 5 minutes
|
|
"leak_threshold": 0.05 # 5% increase
|
|
}
|
|
]
|
|
|
|
|
|
from typing import Dict, Any, List, Optional, Union, Callable
|
|
from pathlib import Path
|
|
import re
|
|
import hashlib
|
|
import json
|
|
import tempfile
|
|
from unittest.mock import MagicMock
|
|
|
|
def validate_test_data_structure(
|
|
data: Any,
|
|
schema: Dict[str, Any]
|
|
) -> bool:
|
|
"""
|
|
Validate test data structure against a schema.
|
|
|
|
Args:
|
|
data: Data to validate
|
|
schema: Schema definition
|
|
|
|
Returns:
|
|
True if data matches schema, False otherwise
|
|
"""
|
|
def _validate(item, schema_part):
|
|
"""Internal validation function for nested data structures."""
|
|
if "type" in schema_part:
|
|
expected_type = schema_part["type"]
|
|
if expected_type == "dict" and not isinstance(item, dict):
|
|
return False
|
|
elif expected_type == "list" and not isinstance(item, list):
|
|
return False
|
|
elif expected_type == "str" and not isinstance(item, str):
|
|
return False
|
|
elif expected_type == "int" and not isinstance(item, int):
|
|
return False
|
|
elif expected_type == "float" and not isinstance(item, float):
|
|
return False
|
|
elif expected_type == "bool" and not isinstance(item, bool):
|
|
return False
|
|
|
|
if "required" in schema_part and not all(key in item for key in schema_part["required"]):
|
|
return False
|
|
|
|
if "properties" in schema_part:
|
|
for key, prop_schema in schema_part["properties"].items():
|
|
if key in item:
|
|
if not _validate(item[key], prop_schema):
|
|
return False
|
|
|
|
if "items" in schema_part and isinstance(item, list):
|
|
for list_item in item:
|
|
if not _validate(list_item, schema_part["items"]):
|
|
return False
|
|
|
|
if "pattern" in schema_part and isinstance(item, str):
|
|
if not re.match(schema_part["pattern"], item):
|
|
return False
|
|
|
|
if "min" in schema_part and isinstance(item, (int, float)):
|
|
if item < schema_part["min"]:
|
|
return False
|
|
|
|
if "max" in schema_part and isinstance(item, (int, float)):
|
|
if item > schema_part["max"]:
|
|
return False
|
|
|
|
return True
|
|
|
|
return _validate(data, schema)
|
|
|
|
def create_data_validation_test_cases() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create data validation test cases.
|
|
|
|
Returns:
|
|
List of data validation test cases
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_config_data",
|
|
"data": {
|
|
"database": {
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"username": "admin",
|
|
"PASSWORD_REMOVED": "SECRET_REMOVED"
|
|
},
|
|
"logging": {
|
|
"level": "INFO",
|
|
"file": "/var/log/app.log"
|
|
}
|
|
},
|
|
"schema": {
|
|
"type": "dict",
|
|
"properties": {
|
|
"database": {
|
|
"type": "dict",
|
|
"required": ["host", "port", "username", "PASSWORD_REMOVED"],
|
|
"properties": {
|
|
"host": {"type": "str"},
|
|
"port": {"type": "int", "min": 1, "max": 65535},
|
|
"username": {"type": "str"},
|
|
"PASSWORD_REMOVED": {"type": "str"}
|
|
}
|
|
},
|
|
"logging": {
|
|
"type": "dict",
|
|
"required": ["level", "file"],
|
|
"properties": {
|
|
"level": {"type": "str", "pattern": "^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$"},
|
|
"file": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "invalid_config_data",
|
|
"data": {
|
|
"database": {
|
|
"host": "localhost",
|
|
"port": 70000, # Invalid port
|
|
"username": "admin"
|
|
# Missing PASSWORD_REMOVED
|
|
}
|
|
},
|
|
"schema": {
|
|
"type": "dict",
|
|
"properties": {
|
|
"database": {
|
|
"type": "dict",
|
|
"required": ["host", "port", "username", "PASSWORD_REMOVED"],
|
|
"properties": {
|
|
"host": {"type": "str"},
|
|
"port": {"type": "int", "min": 1, "max": 65535},
|
|
"username": {"type": "str"},
|
|
"PASSWORD_REMOVED": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
def validate_file_integrity(
|
|
file_path: Path,
|
|
expected_hash: str,
|
|
algorithm: str = "sha256"
|
|
) -> bool:
|
|
"""
|
|
Validate file integrity using hash comparison.
|
|
|
|
Args:
|
|
file_path: Path to file
|
|
expected_hash: Expected hash value
|
|
algorithm: Hash algorithm to use
|
|
|
|
Returns:
|
|
True if file integrity is valid, False otherwise
|
|
"""
|
|
hash_func = hashlib.new(algorithm)
|
|
|
|
with open(file_path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_func.update(chunk)
|
|
|
|
actual_hash = hash_func.hexdigest()
|
|
return actual_hash == expected_hash
|
|
|
|
def create_file_validation_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create file validation test scenarios.
|
|
|
|
Returns:
|
|
List of file validation test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_file_integrity",
|
|
"file_content": "test content for integrity check",
|
|
"expected_hash": "a1b2c3d4e5f6", # Placeholder - would be actual hash in real test
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "corrupted_file",
|
|
"file_content": "corrupted content",
|
|
"expected_hash": "a1b2c3d4e5f6", # Different from actual hash
|
|
"expected_result": False
|
|
},
|
|
{
|
|
"name": "missing_file",
|
|
"file_content": None,
|
|
"expected_hash": "a1b2c3d4e5f6",
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
def validate_json_schema(
|
|
json_data: Union[str, Dict],
|
|
schema: Dict[str, Any]
|
|
) -> bool:
|
|
"""
|
|
Validate JSON data against a schema.
|
|
|
|
Args:
|
|
json_data: JSON data to validate
|
|
schema: JSON schema definition
|
|
|
|
Returns:
|
|
True if JSON is valid, False otherwise
|
|
"""
|
|
if isinstance(json_data, str):
|
|
try:
|
|
data = json.loads(json_data)
|
|
except json.JSONDecodeError:
|
|
return False
|
|
else:
|
|
data = json_data
|
|
|
|
return validate_test_data_structure(data, schema)
|
|
|
|
def create_json_validation_test_cases() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create JSON validation test cases.
|
|
|
|
Returns:
|
|
List of JSON validation test cases
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_json_api_response",
|
|
"json_data": """
|
|
{
|
|
"status": "success",
|
|
"data": {
|
|
"users": [
|
|
{"id": 1, "name": "Alice"},
|
|
{"id": 2, "name": "Bob"}
|
|
]
|
|
},
|
|
"timestamp": "2023-01-01T00:00:00Z"
|
|
}
|
|
""",
|
|
"schema": {
|
|
"type": "dict",
|
|
"required": ["status", "data", "timestamp"],
|
|
"properties": {
|
|
"status": {"type": "str", "pattern": "^(success|error|warning)$"},
|
|
"data": {
|
|
"type": "dict",
|
|
"required": ["users"],
|
|
"properties": {
|
|
"users": {
|
|
"type": "list",
|
|
"items": {
|
|
"type": "dict",
|
|
"required": ["id", "name"],
|
|
"properties": {
|
|
"id": {"type": "int", "min": 1},
|
|
"name": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"timestamp": {"type": "str", "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"}
|
|
}
|
|
},
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "invalid_json_api_response",
|
|
"json_data": """
|
|
{
|
|
"status": "invalid_status",
|
|
"data": {
|
|
"users": [
|
|
{"id": "not_an_int", "name": "Alice"}
|
|
]
|
|
}
|
|
}
|
|
""",
|
|
"schema": {
|
|
"type": "dict",
|
|
"required": ["status", "data", "timestamp"],
|
|
"properties": {
|
|
"status": {"type": "str", "pattern": "^(success|error|warning)$"},
|
|
"data": {
|
|
"type": "dict",
|
|
"required": ["users"],
|
|
"properties": {
|
|
"users": {
|
|
"type": "list",
|
|
"items": {
|
|
"type": "dict",
|
|
"required": ["id", "name"],
|
|
"properties": {
|
|
"id": {"type": "int", "min": 1},
|
|
"name": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"timestamp": {"type": "str", "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"}
|
|
}
|
|
},
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
def validate_database_schema(
|
|
database_schema: Dict[str, Any],
|
|
expected_schema: Dict[str, Any]
|
|
) -> bool:
|
|
"""
|
|
Validate database schema structure.
|
|
|
|
Args:
|
|
database_schema: Actual database schema
|
|
expected_schema: Expected database schema
|
|
|
|
Returns:
|
|
True if schemas match, False otherwise
|
|
"""
|
|
# Compare tables
|
|
if set(database_schema.keys()) != set(expected_schema.keys()):
|
|
return False
|
|
|
|
# Compare table structures
|
|
for table_name, table_def in expected_schema.items():
|
|
if table_name not in database_schema:
|
|
return False
|
|
|
|
actual_table = database_schema[table_name]
|
|
|
|
# Compare columns
|
|
if set(table_def["columns"].keys()) != set(actual_table["columns"].keys()):
|
|
return False
|
|
|
|
# Compare column definitions
|
|
for col_name, col_def in table_def["columns"].items():
|
|
if col_name not in actual_table["columns"]:
|
|
return False
|
|
|
|
actual_col = actual_table["columns"][col_name]
|
|
|
|
if col_def["type"] != actual_col["type"]:
|
|
return False
|
|
|
|
if "constraints" in col_def and col_def["constraints"] != actual_col.get("constraints", []):
|
|
return False
|
|
|
|
return True
|
|
|
|
def create_database_validation_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create database validation test scenarios.
|
|
|
|
Returns:
|
|
List of database validation test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_database_schema",
|
|
"database_schema": {
|
|
"users": {
|
|
"columns": {
|
|
"id": {"type": "INTEGER", "constraints": ["PRIMARY KEY"]},
|
|
"name": {"type": "TEXT", "constraints": ["NOT NULL"]},
|
|
"email": {"type": "TEXT", "constraints": ["UNIQUE"]}
|
|
}
|
|
},
|
|
"posts": {
|
|
"columns": {
|
|
"id": {"type": "INTEGER", "constraints": ["PRIMARY KEY"]},
|
|
"user_id": {"type": "INTEGER", "constraints": ["FOREIGN KEY"]},
|
|
"title": {"type": "TEXT"},
|
|
"content": {"type": "TEXT"}
|
|
}
|
|
}
|
|
},
|
|
"expected_schema": {
|
|
"users": {
|
|
"columns": {
|
|
"id": {"type": "INTEGER", "constraints": ["PRIMARY KEY"]},
|
|
"name": {"type": "TEXT", "constraints": ["NOT NULL"]},
|
|
"email": {"type": "TEXT", "constraints": ["UNIQUE"]}
|
|
}
|
|
},
|
|
"posts": {
|
|
"columns": {
|
|
"id": {"type": "INTEGER", "constraints": ["PRIMARY KEY"]},
|
|
"user_id": {"type": "INTEGER", "constraints": ["FOREIGN KEY"]},
|
|
"title": {"type": "TEXT"},
|
|
"content": {"type": "TEXT"}
|
|
}
|
|
}
|
|
},
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "invalid_database_schema",
|
|
"database_schema": {
|
|
"users": {
|
|
"columns": {
|
|
"id": {"type": "INTEGER", "constraints": ["PRIMARY KEY"]},
|
|
"name": {"type": "TEXT"}
|
|
# Missing email column
|
|
}
|
|
}
|
|
},
|
|
"expected_schema": {
|
|
"users": {
|
|
"columns": {
|
|
"id": {"type": "INTEGER", "constraints": ["PRIMARY KEY"]},
|
|
"name": {"type": "TEXT", "constraints": ["NOT NULL"]},
|
|
"email": {"type": "TEXT", "constraints": ["UNIQUE"]}
|
|
}
|
|
}
|
|
},
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
def validate_tool_structure(
|
|
tool_definition: Dict[str, Any],
|
|
expected_structure: Dict[str, Any]
|
|
) -> bool:
|
|
"""
|
|
Validate tool structure and metadata.
|
|
|
|
Args:
|
|
tool_definition: Tool definition to validate
|
|
expected_structure: Expected tool structure
|
|
|
|
Returns:
|
|
True if tool structure is valid, False otherwise
|
|
"""
|
|
# Check required fields
|
|
required_fields = expected_structure.get("required_fields", [])
|
|
if not all(field in tool_definition for field in required_fields):
|
|
return False
|
|
|
|
# Check metadata structure
|
|
if "metadata" in expected_structure:
|
|
metadata_schema = expected_structure["metadata"]
|
|
if not validate_test_data_structure(tool_definition.get("metadata", {}), metadata_schema):
|
|
return False
|
|
|
|
# Check function signatures
|
|
if "functions" in expected_structure:
|
|
for func_name, func_schema in expected_structure["functions"].items():
|
|
if func_name not in tool_definition.get("functions", {}):
|
|
return False
|
|
|
|
# Check function parameters
|
|
actual_func = tool_definition["functions"][func_name]
|
|
expected_params = func_schema.get("parameters", [])
|
|
|
|
# Simple parameter count check
|
|
if "parameters" in func_schema:
|
|
try:
|
|
import inspect
|
|
sig = inspect.signature(actual_func)
|
|
if len(sig.parameters) != len(expected_params):
|
|
return False
|
|
except:
|
|
pass
|
|
|
|
return True
|
|
|
|
def create_tool_validation_test_cases() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create tool validation test cases.
|
|
|
|
Returns:
|
|
List of tool validation test cases
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_tool_structure",
|
|
"tool_definition": {
|
|
"name": "test_tool",
|
|
"version": "1.0.0",
|
|
"author": "Test Author",
|
|
"description": "Test tool",
|
|
"metadata": {
|
|
"category": "utility",
|
|
"compatibility": ["1.0", "2.0"]
|
|
},
|
|
"functions": {
|
|
"initialize": lambda: True,
|
|
"execute": lambda x: x * 2,
|
|
"cleanup": lambda: None
|
|
}
|
|
},
|
|
"expected_structure": {
|
|
"required_fields": ["name", "version", "author", "description"],
|
|
"metadata": {
|
|
"type": "dict",
|
|
"required": ["category", "compatibility"],
|
|
"properties": {
|
|
"category": {"type": "str"},
|
|
"compatibility": {"type": "list", "items": {"type": "str"}}
|
|
}
|
|
},
|
|
"functions": {
|
|
"initialize": {"parameters": []},
|
|
"execute": {"parameters": ["x"]},
|
|
"cleanup": {"parameters": []}
|
|
}
|
|
},
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "invalid_tool_structure",
|
|
"tool_definition": {
|
|
"name": "test_tool",
|
|
# Missing required fields
|
|
"functions": {
|
|
"initialize": lambda: True
|
|
# Missing required functions
|
|
}
|
|
},
|
|
"expected_structure": {
|
|
"required_fields": ["name", "version", "author", "description"],
|
|
"functions": {
|
|
"initialize": {"parameters": []},
|
|
"execute": {"parameters": ["x"]},
|
|
"cleanup": {"parameters": []}
|
|
}
|
|
},
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
def validate_api_response(
|
|
response: Dict[str, Any],
|
|
expected_schema: Dict[str, Any]
|
|
) -> bool:
|
|
"""
|
|
Validate API response structure.
|
|
|
|
Args:
|
|
response: API response to validate
|
|
expected_schema: Expected response schema
|
|
|
|
Returns:
|
|
True if response is valid, False otherwise
|
|
"""
|
|
return validate_test_data_structure(response, expected_schema)
|
|
|
|
def create_api_validation_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create API validation test scenarios.
|
|
|
|
Returns:
|
|
List of API validation test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_api_response",
|
|
"response": {
|
|
"status": "success",
|
|
"code": 200,
|
|
"data": {
|
|
"items": [
|
|
{"id": 1, "name": "Item 1"},
|
|
{"id": 2, "name": "Item 2"}
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"page_size": 10,
|
|
"total": 2
|
|
}
|
|
},
|
|
"timestamp": "2023-01-01T00:00:00Z"
|
|
},
|
|
"expected_schema": {
|
|
"type": "dict",
|
|
"required": ["status", "code", "data", "timestamp"],
|
|
"properties": {
|
|
"status": {"type": "str", "pattern": "^(success|error|warning)$"},
|
|
"code": {"type": "int", "min": 200, "max": 599},
|
|
"data": {
|
|
"type": "dict",
|
|
"required": ["items", "pagination"],
|
|
"properties": {
|
|
"items": {
|
|
"type": "list",
|
|
"items": {
|
|
"type": "dict",
|
|
"required": ["id", "name"],
|
|
"properties": {
|
|
"id": {"type": "int", "min": 1},
|
|
"name": {"type": "str"}
|
|
}
|
|
}
|
|
},
|
|
"pagination": {
|
|
"type": "dict",
|
|
"required": ["page", "page_size", "total"],
|
|
"properties": {
|
|
"page": {"type": "int", "min": 1},
|
|
"page_size": {"type": "int", "min": 1, "max": 100},
|
|
"total": {"type": "int", "min": 0}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"timestamp": {"type": "str", "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"}
|
|
}
|
|
},
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "invalid_api_response",
|
|
"response": {
|
|
"status": "invalid_status",
|
|
"code": 999, # Invalid code
|
|
"data": {
|
|
"items": [
|
|
{"id": "not_an_int", "name": "Item 1"} # Invalid ID type
|
|
]
|
|
}
|
|
},
|
|
"expected_schema": {
|
|
"type": "dict",
|
|
"required": ["status", "code", "data", "timestamp"],
|
|
"properties": {
|
|
"status": {"type": "str", "pattern": "^(success|error|warning)$"},
|
|
"code": {"type": "int", "min": 200, "max": 599},
|
|
"data": {
|
|
"type": "dict",
|
|
"required": ["items", "pagination"],
|
|
"properties": {
|
|
"items": {
|
|
"type": "list",
|
|
"items": {
|
|
"type": "dict",
|
|
"required": ["id", "name"],
|
|
"properties": {
|
|
"id": {"type": "int", "min": 1},
|
|
"name": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
def validate_configuration_files(
|
|
config_files: List[Path],
|
|
expected_structure: Dict[str, Any]
|
|
) -> Dict[str, bool]:
|
|
"""
|
|
Validate multiple configuration files.
|
|
|
|
Args:
|
|
config_files: List of configuration file paths
|
|
expected_structure: Expected configuration structure
|
|
|
|
Returns:
|
|
Dictionary mapping file paths to validation results
|
|
"""
|
|
results = {}
|
|
|
|
for config_file in config_files:
|
|
try:
|
|
with open(config_file, "r") as f:
|
|
config_data = json.load(f)
|
|
|
|
results[str(config_file)] = validate_test_data_structure(config_data, expected_structure)
|
|
except (json.JSONDecodeError, IOError):
|
|
results[str(config_file)] = False
|
|
|
|
return results
|
|
|
|
def create_configuration_validation_test_cases() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create configuration validation test cases.
|
|
|
|
Returns:
|
|
List of configuration validation test cases
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_configuration_files",
|
|
"config_files": [
|
|
{
|
|
"content": """
|
|
{
|
|
"database": {
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"username": "admin",
|
|
"PASSWORD_REMOVED": "SECRET_REMOVED"
|
|
},
|
|
"logging": {
|
|
"level": "INFO",
|
|
"file": "/var/log/app.log"
|
|
}
|
|
}
|
|
""",
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"content": """
|
|
{
|
|
"database": {
|
|
"host": "localhost",
|
|
"port": 5432,
|
|
"username": "admin",
|
|
"PASSWORD_REMOVED": "SECRET_REMOVED"
|
|
}
|
|
}
|
|
""",
|
|
"expected_result": True
|
|
}
|
|
],
|
|
"expected_structure": {
|
|
"type": "dict",
|
|
"properties": {
|
|
"database": {
|
|
"type": "dict",
|
|
"required": ["host", "port", "username", "PASSWORD_REMOVED"],
|
|
"properties": {
|
|
"host": {"type": "str"},
|
|
"port": {"type": "int", "min": 1, "max": 65535},
|
|
"username": {"type": "str"},
|
|
"PASSWORD_REMOVED": {"type": "str"}
|
|
}
|
|
},
|
|
"logging": {
|
|
"type": "dict",
|
|
"properties": {
|
|
"level": {"type": "str", "pattern": "^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$"},
|
|
"file": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "invalid_configuration_files",
|
|
"config_files": [
|
|
{
|
|
"content": """
|
|
{
|
|
"database": {
|
|
"host": "localhost",
|
|
"port": 70000,
|
|
"username": "admin"
|
|
}
|
|
}
|
|
""",
|
|
"expected_result": False
|
|
},
|
|
{
|
|
"content": "invalid json content",
|
|
"expected_result": False
|
|
}
|
|
],
|
|
"expected_structure": {
|
|
"type": "dict",
|
|
"properties": {
|
|
"database": {
|
|
"type": "dict",
|
|
"required": ["host", "port", "username", "PASSWORD_REMOVED"],
|
|
"properties": {
|
|
"host": {"type": "str"},
|
|
"port": {"type": "int", "min": 1, "max": 65535},
|
|
"username": {"type": "str"},
|
|
"PASSWORD_REMOVED": {"type": "str"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
def validate_data_consistency(
|
|
data_source: Any,
|
|
validation_rules: List[Dict[str, Any]]
|
|
) -> bool:
|
|
"""
|
|
Validate data consistency against validation rules.
|
|
|
|
Args:
|
|
data_source: Data to validate
|
|
validation_rules: List of validation rules
|
|
|
|
Returns:
|
|
True if data is consistent, False otherwise
|
|
"""
|
|
def get_nested_value(data, field_path):
|
|
"""Get value from nested data structure using dot notation or direct field name"""
|
|
if not isinstance(data, dict):
|
|
return None
|
|
|
|
# Try direct field access first
|
|
if field_path in data:
|
|
return data[field_path]
|
|
|
|
# Try nested access using dot notation
|
|
if '.' in field_path:
|
|
parts = field_path.split('.')
|
|
current = data
|
|
for part in parts:
|
|
if isinstance(current, dict) and part in current:
|
|
current = current[part]
|
|
else:
|
|
return None
|
|
return current
|
|
|
|
return None
|
|
|
|
for rule in validation_rules:
|
|
field = rule["field"]
|
|
validation_type = rule["type"]
|
|
|
|
# Get field value using nested access
|
|
value = get_nested_value(data_source, field)
|
|
|
|
if value is None and rule.get("required", False):
|
|
return False
|
|
|
|
# Apply validation
|
|
if validation_type == "range":
|
|
min_val = rule.get("min")
|
|
max_val = rule.get("max")
|
|
if not (min_val <= value <= max_val):
|
|
return False
|
|
|
|
elif validation_type == "pattern":
|
|
pattern = rule["pattern"]
|
|
if not re.match(pattern, str(value)):
|
|
return False
|
|
|
|
elif validation_type == "enum":
|
|
allowed_values = rule["values"]
|
|
if value not in allowed_values:
|
|
return False
|
|
|
|
elif validation_type == "custom":
|
|
validator = rule["validator"]
|
|
if not validator(value):
|
|
return False
|
|
|
|
return True
|
|
|
|
def create_data_consistency_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create data consistency test scenarios.
|
|
|
|
Returns:
|
|
List of data consistency test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "valid_data_consistency",
|
|
"data": {
|
|
"user": {
|
|
"id": 123,
|
|
"username": "test_user",
|
|
"email": "test@example.com",
|
|
"status": "active",
|
|
"age": 25
|
|
}
|
|
},
|
|
"validation_rules": [
|
|
{"field": "id", "type": "range", "min": 1, "max": 1000, "required": True},
|
|
{"field": "username", "type": "pattern", "pattern": "^[a-z_]+$", "required": True},
|
|
{"field": "email", "type": "pattern", "pattern": "^[^@]+@[^@]+\\.[^@]+$", "required": True},
|
|
{"field": "status", "type": "enum", "values": ["active", "inactive", "suspended"], "required": True},
|
|
{"field": "age", "type": "range", "min": 18, "max": 120}
|
|
],
|
|
"expected_result": True
|
|
},
|
|
{
|
|
"name": "invalid_data_consistency",
|
|
"data": {
|
|
"user": {
|
|
"id": 0, # Invalid ID
|
|
"username": "Invalid User", # Invalid username
|
|
"email": "not-an-email", # Invalid email
|
|
"status": "unknown", # Invalid status
|
|
"age": 15 # Invalid age
|
|
}
|
|
},
|
|
"validation_rules": [
|
|
{"field": "id", "type": "range", "min": 1, "max": 1000, "required": True},
|
|
{"field": "username", "type": "pattern", "pattern": "^[a-z_]+$", "required": True},
|
|
{"field": "email", "type": "pattern", "pattern": "^[^@]+@[^@]+\\.[^@]+$", "required": True},
|
|
{"field": "status", "type": "enum", "values": ["active", "inactive", "suspended"], "required": True},
|
|
{"field": "age", "type": "range", "min": 18, "max": 120}
|
|
],
|
|
"expected_result": False
|
|
}
|
|
]
|
|
|
|
|
|
import sqlite3
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List, Optional, Union, Callable
|
|
from unittest.mock import MagicMock, patch
|
|
import contextlib
|
|
import time
|
|
|
|
def create_test_database(
|
|
schema: Optional[str] = None,
|
|
data: Optional[List[Dict[str, Any]]] = None,
|
|
db_name: str = "test_db",
|
|
use_memory: bool = True
|
|
) -> Union[str, Path]:
|
|
"""
|
|
Create a test database with optional schema and data.
|
|
|
|
Args:
|
|
schema: SQL schema definition
|
|
data: List of data dictionaries to insert
|
|
db_name: Database name
|
|
use_memory: Use in-memory database if True
|
|
|
|
Returns:
|
|
Database path or connection string
|
|
"""
|
|
if use_memory:
|
|
conn = sqlite3.connect(":memory:")
|
|
return conn
|
|
else:
|
|
db_path = Path(tempfile.gettempdir()) / f"{db_name}.db"
|
|
conn = sqlite3.connect(str(db_path))
|
|
conn.close()
|
|
return str(db_path)
|
|
|
|
def setup_test_database_schema(
|
|
conn: sqlite3.Connection,
|
|
schema: str
|
|
) -> None:
|
|
"""
|
|
Set up database schema for testing.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
schema: SQL schema definition
|
|
"""
|
|
cursor = conn.cursor()
|
|
cursor.executescript(schema)
|
|
conn.commit()
|
|
|
|
def insert_test_data(
|
|
conn: sqlite3.Connection,
|
|
table: str,
|
|
data: List[Dict[str, Any]]
|
|
) -> None:
|
|
"""
|
|
Insert test data into a database table.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
table: Table name
|
|
data: List of data dictionaries
|
|
"""
|
|
if not data:
|
|
return
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Get column names from first data item
|
|
columns = list(data[0].keys())
|
|
placeholders = ", ".join(["?"] * len(columns))
|
|
columns_str = ", ".join(columns)
|
|
|
|
# Prepare and execute insert statements
|
|
for item in data:
|
|
values = [item[col] for col in columns]
|
|
cursor.execute(
|
|
f"INSERT INTO {table} ({columns_str}) VALUES ({placeholders})",
|
|
values
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
def verify_database_state(
|
|
conn: sqlite3.Connection,
|
|
expected_state: Dict[str, Any],
|
|
tolerance: float = 0.0
|
|
) -> bool:
|
|
"""
|
|
Verify database state matches expected state.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
expected_state: Expected database state
|
|
tolerance: Numeric tolerance for floating point comparisons
|
|
|
|
Returns:
|
|
True if state matches, False otherwise
|
|
"""
|
|
cursor = conn.cursor()
|
|
|
|
for table, expected_data in expected_state.items():
|
|
# Query all data from table
|
|
cursor.execute(f"SELECT * FROM {table}")
|
|
actual_data = cursor.fetchall()
|
|
|
|
# Get column names
|
|
cursor.execute(f"PRAGMA table_info({table})")
|
|
columns = [col[1] for col in cursor.fetchall()]
|
|
|
|
# Convert to list of dictionaries for comparison
|
|
actual_records = []
|
|
for row in actual_data:
|
|
record = dict(zip(columns, row))
|
|
actual_records.append(record)
|
|
|
|
# Compare with expected data
|
|
if len(actual_records) != len(expected_data):
|
|
return False
|
|
|
|
for actual, expected in zip(actual_records, expected_data):
|
|
for key, expected_value in expected.items():
|
|
actual_value = actual[key]
|
|
|
|
if isinstance(expected_value, (int, str, bool)):
|
|
if actual_value != expected_value:
|
|
return False
|
|
elif isinstance(expected_value, float):
|
|
if abs(actual_value - expected_value) > tolerance:
|
|
return False
|
|
else:
|
|
if actual_value != expected_value:
|
|
return False
|
|
|
|
return True
|
|
|
|
def create_database_mock() -> MagicMock:
|
|
"""
|
|
Create a mock database connection for testing.
|
|
|
|
Returns:
|
|
Mock database connection object
|
|
"""
|
|
mock_conn = MagicMock(spec=sqlite3.Connection)
|
|
mock_cursor = MagicMock()
|
|
|
|
# Set up mock behavior
|
|
mock_conn.cursor.return_value = mock_cursor
|
|
mock_cursor.fetchone.return_value = (1,)
|
|
mock_cursor.fetchall.return_value = [(1, "test"), (2, "data")]
|
|
mock_cursor.description = [("id",), ("name",)]
|
|
|
|
return mock_conn
|
|
|
|
def create_database_fixture(
|
|
schema: str,
|
|
initial_data: Optional[List[Dict[str, Any]]] = None
|
|
) -> Callable:
|
|
"""
|
|
Create a pytest fixture for database testing.
|
|
|
|
Args:
|
|
schema: Database schema
|
|
initial_data: Initial data to populate
|
|
|
|
Returns:
|
|
Fixture function
|
|
"""
|
|
def database_fixture():
|
|
"""Inner fixture function for database testing."""
|
|
# Create in-memory database
|
|
conn = sqlite3.connect(":memory:")
|
|
|
|
# Set up schema
|
|
setup_test_database_schema(conn, schema)
|
|
|
|
# Insert initial data if provided
|
|
if initial_data:
|
|
for table_data in initial_data:
|
|
table_name = list(table_data.keys())[0]
|
|
insert_test_data(conn, table_name, table_data[table_name])
|
|
|
|
yield conn
|
|
|
|
# Cleanup
|
|
conn.close()
|
|
|
|
return database_fixture
|
|
|
|
def simulate_database_errors(
|
|
error_type: str = "connection",
|
|
operation: str = "execute"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate database errors.
|
|
|
|
Args:
|
|
error_type: Type of error to simulate
|
|
operation: Database operation to fail
|
|
|
|
Returns:
|
|
Context manager for error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating database errors."""
|
|
error_map = {
|
|
"connection": sqlite3.OperationalError("Unable to connect"),
|
|
"integrity": sqlite3.IntegrityError("Constraint violation"),
|
|
"programming": sqlite3.ProgrammingError("SQL syntax error"),
|
|
"timeout": sqlite3.OperationalError("Database locked")
|
|
}
|
|
|
|
error = error_map.get(error_type, sqlite3.Error("Database error"))
|
|
|
|
with patch('sqlite3.Connection') as mock_conn_class:
|
|
mock_conn = MagicMock()
|
|
mock_cursor = MagicMock()
|
|
|
|
if operation == "execute":
|
|
mock_cursor.execute.side_effect = error
|
|
elif operation == "commit":
|
|
mock_conn.commit.side_effect = error
|
|
elif operation == "fetch":
|
|
mock_cursor.fetchall.side_effect = error
|
|
|
|
mock_conn.cursor.return_value = mock_cursor
|
|
mock_conn_class.return_value = mock_conn
|
|
|
|
yield mock_conn
|
|
|
|
return error_context
|
|
|
|
def benchmark_database_operations(
|
|
conn: sqlite3.Connection,
|
|
operations: List[Callable],
|
|
iterations: int = 100
|
|
) -> Dict[str, float]:
|
|
"""
|
|
Benchmark database operations performance.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
operations: List of operation functions
|
|
iterations: Number of iterations per operation
|
|
|
|
Returns:
|
|
Dictionary of operation timings
|
|
"""
|
|
results = {}
|
|
|
|
for i, operation in enumerate(operations):
|
|
start_time = time.time()
|
|
|
|
for _ in range(iterations):
|
|
operation(conn)
|
|
|
|
end_time = time.time()
|
|
avg_time = (end_time - start_time) / iterations
|
|
|
|
results[f"operation_{i}"] = avg_time
|
|
|
|
return results
|
|
|
|
def create_transaction_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create test scenarios for transaction testing.
|
|
|
|
Returns:
|
|
List of transaction test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "successful_transaction",
|
|
"operations": [
|
|
"BEGIN",
|
|
"INSERT INTO test VALUES (1, 'data')",
|
|
"COMMIT"
|
|
],
|
|
"expected_result": "success"
|
|
},
|
|
{
|
|
"name": "failed_transaction",
|
|
"operations": [
|
|
"BEGIN",
|
|
"INSERT INTO test VALUES (1, 'data')",
|
|
"ROLLBACK"
|
|
],
|
|
"expected_result": "rollback"
|
|
},
|
|
{
|
|
"name": "nested_transaction",
|
|
"operations": [
|
|
"BEGIN",
|
|
"SAVEPOINT sp1",
|
|
"INSERT INTO test VALUES (1, 'data')",
|
|
"RELEASE SAVEPOINT sp1",
|
|
"COMMIT"
|
|
],
|
|
"expected_result": "success"
|
|
}
|
|
]
|
|
|
|
def verify_database_performance(
|
|
conn: sqlite3.Connection,
|
|
query: str,
|
|
max_execution_time: float = 1.0,
|
|
iterations: int = 10
|
|
) -> bool:
|
|
"""
|
|
Verify database query performance meets requirements.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
query: SQL query to test
|
|
max_execution_time: Maximum allowed execution time
|
|
iterations: Number of test iterations
|
|
|
|
Returns:
|
|
True if performance is acceptable, False otherwise
|
|
"""
|
|
cursor = conn.cursor()
|
|
total_time = 0.0
|
|
|
|
for _ in range(iterations):
|
|
start_time = time.time()
|
|
cursor.execute(query)
|
|
cursor.fetchall()
|
|
end_time = time.time()
|
|
|
|
total_time += (end_time - start_time)
|
|
|
|
avg_time = total_time / iterations
|
|
return avg_time <= max_execution_time
|
|
|
|
def create_database_snapshot(
|
|
conn: sqlite3.Connection
|
|
) -> Dict[str, List[Dict[str, Any]]]:
|
|
"""
|
|
Create a snapshot of current database state.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
|
|
Returns:
|
|
Dictionary representing database state
|
|
"""
|
|
cursor = conn.cursor()
|
|
snapshot = {}
|
|
|
|
# Get all tables
|
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
tables = [table[0] for table in cursor.fetchall()]
|
|
|
|
for table in tables:
|
|
# Get table data
|
|
cursor.execute(f"SELECT * FROM {table}")
|
|
rows = cursor.fetchall()
|
|
|
|
# Get column names
|
|
cursor.execute(f"PRAGMA table_info({table})")
|
|
columns = [col[1] for col in cursor.fetchall()]
|
|
|
|
# Convert to list of dictionaries
|
|
table_data = []
|
|
for row in rows:
|
|
record = dict(zip(columns, row))
|
|
table_data.append(record)
|
|
|
|
snapshot[table] = table_data
|
|
|
|
return snapshot
|
|
|
|
def restore_database_snapshot(
|
|
conn: sqlite3.Connection,
|
|
snapshot: Dict[str, List[Dict[str, Any]]]
|
|
) -> None:
|
|
"""
|
|
Restore database to a previous snapshot state.
|
|
|
|
Args:
|
|
conn: Database connection
|
|
snapshot: Database snapshot to restore
|
|
"""
|
|
cursor = conn.cursor()
|
|
|
|
for table, data in snapshot.items():
|
|
# Clear existing data
|
|
cursor.execute(f"DELETE FROM {table}")
|
|
|
|
# Re-insert data
|
|
if data:
|
|
columns = list(data[0].keys())
|
|
placeholders = ", ".join(["?"] * len(columns))
|
|
columns_str = ", ".join(columns)
|
|
|
|
for item in data:
|
|
values = [item[col] for col in columns]
|
|
cursor.execute(
|
|
f"INSERT INTO {table} ({columns_str}) VALUES ({placeholders})",
|
|
values
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
|
|
import os
|
|
import stat
|
|
import hashlib
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List, Optional, Union
|
|
import tempfile
|
|
import shutil
|
|
from unittest.mock import MagicMock
|
|
|
|
def create_test_file_structure(
|
|
base_path: Path,
|
|
structure: Dict[str, Union[Dict, str, bytes]],
|
|
file_permissions: int = 0o644,
|
|
dir_permissions: int = 0o755
|
|
) -> Dict[str, Path]:
|
|
"""
|
|
Create a complex file structure for testing.
|
|
|
|
Args:
|
|
base_path: Base directory path
|
|
structure: Dictionary describing the file structure
|
|
file_permissions: Permissions for created files
|
|
dir_permissions: Permissions for created directories
|
|
|
|
Returns:
|
|
Dictionary mapping file names to their paths
|
|
"""
|
|
created_files = {}
|
|
|
|
for name, content in structure.items():
|
|
full_path = base_path / name
|
|
|
|
if isinstance(content, dict):
|
|
# It's a directory - create it and recurse
|
|
full_path.mkdir(exist_ok=True, mode=dir_permissions)
|
|
created_files.update(create_test_file_structure(
|
|
full_path, content, file_permissions, dir_permissions
|
|
))
|
|
else:
|
|
# It's a file - create it with content
|
|
if isinstance(content, str):
|
|
full_path.write_text(content)
|
|
else:
|
|
full_path.write_bytes(content)
|
|
|
|
# Set file permissions
|
|
full_path.chmod(file_permissions)
|
|
created_files[name] = full_path
|
|
|
|
return created_files
|
|
|
|
def create_duplicate_files(
|
|
base_path: Path,
|
|
original_content: str,
|
|
num_duplicates: int = 3,
|
|
file_size: int = 1024
|
|
) -> List[Path]:
|
|
"""
|
|
Create multiple duplicate files for testing.
|
|
|
|
Args:
|
|
base_path: Directory to create files in
|
|
original_content: Content for original file
|
|
num_duplicates: Number of duplicate files to create
|
|
file_size: Target file size
|
|
|
|
Returns:
|
|
List of created file paths
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
files = []
|
|
|
|
# Create original file
|
|
original_file = base_path / "original.txt"
|
|
if len(original_content) < file_size:
|
|
# Pad content to reach desired size
|
|
multiplier = (file_size // len(original_content)) + 1
|
|
padded_content = original_content * multiplier
|
|
original_file.write_text(padded_content[:file_size])
|
|
else:
|
|
original_file.write_text(original_content[:file_size])
|
|
|
|
files.append(original_file)
|
|
|
|
# Create duplicates
|
|
for i in range(1, num_duplicates + 1):
|
|
duplicate_file = base_path / f"duplicate_{i}.txt"
|
|
shutil.copy2(original_file, duplicate_file)
|
|
files.append(duplicate_file)
|
|
|
|
return files
|
|
|
|
def create_files_with_varying_sizes(
|
|
base_path: Path,
|
|
sizes: List[int],
|
|
content_pattern: str = "Test content "
|
|
) -> List[Path]:
|
|
"""
|
|
Create files with varying sizes for testing.
|
|
|
|
Args:
|
|
base_path: Directory to create files in
|
|
sizes: List of target file sizes in bytes
|
|
content_pattern: Pattern to use for file content
|
|
|
|
Returns:
|
|
List of created file paths
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
files = []
|
|
|
|
for i, size in enumerate(sizes):
|
|
file_path = base_path / f"file_{size}.txt"
|
|
|
|
# Create content that matches the desired size
|
|
content = (content_pattern * ((size // len(content_pattern)) + 1))[:size]
|
|
file_path.write_text(content)
|
|
|
|
files.append(file_path)
|
|
|
|
return files
|
|
|
|
def create_symlinks_and_hardlinks(
|
|
base_path: Path,
|
|
target_file: Path
|
|
) -> Dict[str, List[Path]]:
|
|
"""
|
|
Create symlinks and hardlinks for testing.
|
|
|
|
Args:
|
|
base_path: Directory to create links in
|
|
target_file: Target file for links
|
|
|
|
Returns:
|
|
Dictionary with 'symlinks' and 'hardlinks' lists
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
result = {
|
|
'symlinks': [],
|
|
'hardlinks': []
|
|
}
|
|
|
|
# Create symlinks
|
|
for i in range(3):
|
|
symlink = base_path / f"symlink_{i}.txt"
|
|
try:
|
|
symlink.symlink_to(target_file)
|
|
result['symlinks'].append(symlink)
|
|
except OSError:
|
|
# Symlinks not supported on this system
|
|
break
|
|
|
|
# Create hardlinks
|
|
for i in range(3):
|
|
hardlink = base_path / f"hardlink_{i}.txt"
|
|
try:
|
|
hardlink.link_to(target_file)
|
|
result['hardlinks'].append(hardlink)
|
|
except OSError:
|
|
# Hardlinks not supported for this file type
|
|
break
|
|
|
|
return result
|
|
|
|
def calculate_file_hash(file_path: Path, algorithm: str = "sha256") -> str:
|
|
"""
|
|
Calculate hash of a file using specified algorithm.
|
|
|
|
Args:
|
|
file_path: Path to file
|
|
algorithm: Hash algorithm to use
|
|
|
|
Returns:
|
|
Hexadecimal hash string
|
|
"""
|
|
hash_func = hashlib.new(algorithm)
|
|
|
|
with open(file_path, "rb") as f:
|
|
# Read file in chunks to handle large files
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_func.update(chunk)
|
|
|
|
return hash_func.hexdigest()
|
|
|
|
def compare_files(file1: Path, file2: Path) -> bool:
|
|
"""
|
|
Compare two files for identical content.
|
|
|
|
Args:
|
|
file1: First file path
|
|
file2: Second file path
|
|
|
|
Returns:
|
|
True if files have identical content, False otherwise
|
|
"""
|
|
if file1.stat().st_size != file2.stat().st_size:
|
|
return False
|
|
|
|
with open(file1, "rb") as f1, open(file2, "rb") as f2:
|
|
while True:
|
|
chunk1 = f1.read(4096)
|
|
chunk2 = f2.read(4096)
|
|
|
|
if chunk1 != chunk2:
|
|
return False
|
|
|
|
if not chunk1: # End of file
|
|
break
|
|
|
|
return True
|
|
|
|
def create_files_with_different_permissions(
|
|
base_path: Path,
|
|
permissions: List[int]
|
|
) -> List[Path]:
|
|
"""
|
|
Create files with different permissions for testing.
|
|
|
|
Args:
|
|
base_path: Directory to create files in
|
|
permissions: List of permission modes (e.g., 0o644, 0o755)
|
|
|
|
Returns:
|
|
List of created file paths
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
files = []
|
|
|
|
for i, perm in enumerate(permissions):
|
|
file_path = base_path / f"perm_{oct(perm)}.txt"
|
|
file_path.write_text(f"File with permissions {oct(perm)}")
|
|
file_path.chmod(perm)
|
|
files.append(file_path)
|
|
|
|
return files
|
|
|
|
def create_nested_directory_structure(
|
|
base_path: Path,
|
|
depth: int = 3,
|
|
files_per_dir: int = 2
|
|
) -> Dict[str, List[Path]]:
|
|
"""
|
|
Create a nested directory structure for testing.
|
|
|
|
Args:
|
|
base_path: Base directory path
|
|
depth: Depth of nesting
|
|
files_per_dir: Number of files per directory
|
|
|
|
Returns:
|
|
Dictionary mapping directory paths to their file lists
|
|
"""
|
|
structure = {}
|
|
|
|
def _create_nested(current_path: Path, current_depth: int):
|
|
"""Internal function to recursively create nested directory structure."""
|
|
if current_depth > depth:
|
|
return
|
|
|
|
current_files = []
|
|
for i in range(files_per_dir):
|
|
file_path = current_path / f"file_{current_depth}_{i}.txt"
|
|
file_path.write_text(f"Content at depth {current_depth}")
|
|
current_files.append(file_path)
|
|
|
|
structure[str(current_path)] = current_files
|
|
|
|
# Create subdirectories
|
|
for i in range(2): # Create 2 subdirectories per level
|
|
subdir = current_path / f"subdir_{i}"
|
|
subdir.mkdir(exist_ok=True)
|
|
_create_nested(subdir, current_depth + 1)
|
|
|
|
_create_nested(base_path, 0)
|
|
return structure
|
|
|
|
def create_files_with_timestamps(
|
|
base_path: Path,
|
|
timestamps: List[float]
|
|
) -> List[Path]:
|
|
"""
|
|
Create files with specific timestamps for testing.
|
|
|
|
Args:
|
|
base_path: Directory to create files in
|
|
timestamps: List of timestamps (seconds since epoch)
|
|
|
|
Returns:
|
|
List of created file paths
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
files = []
|
|
import time
|
|
|
|
for i, timestamp in enumerate(timestamps):
|
|
file_path = base_path / f"timestamp_{i}.txt"
|
|
file_path.write_text(f"File created at {timestamp}")
|
|
|
|
# Set file timestamps
|
|
os.utime(file_path, (timestamp, timestamp))
|
|
files.append(file_path)
|
|
|
|
return files
|
|
|
|
def verify_file_structure(
|
|
base_path: Path,
|
|
expected_structure: Dict[str, Union[Dict, str]]
|
|
) -> bool:
|
|
"""
|
|
Verify that a file structure matches expected structure.
|
|
|
|
Args:
|
|
base_path: Base directory path
|
|
expected_structure: Expected structure dictionary
|
|
|
|
Returns:
|
|
True if structure matches, False otherwise
|
|
"""
|
|
for name, expected in expected_structure.items():
|
|
full_path = base_path / name
|
|
|
|
if isinstance(expected, dict):
|
|
# Should be a directory
|
|
if not full_path.is_dir():
|
|
return False
|
|
|
|
if not verify_file_structure(full_path, expected):
|
|
return False
|
|
else:
|
|
# Should be a file
|
|
if not full_path.is_file():
|
|
return False
|
|
|
|
if isinstance(expected, str):
|
|
# Check text content
|
|
if full_path.read_text() != expected:
|
|
return False
|
|
else:
|
|
# Check binary content
|
|
if full_path.read_bytes() != expected:
|
|
return False
|
|
|
|
return True
|
|
|
|
def mock_file_operations() -> MagicMock:
|
|
"""
|
|
Create a mock for file system operations.
|
|
|
|
Returns:
|
|
Mock object for file operations
|
|
"""
|
|
mock = MagicMock()
|
|
|
|
# Mock common file operations
|
|
mock.exists.return_value = True
|
|
mock.is_file.return_value = True
|
|
mock.is_dir.return_value = False
|
|
mock.read_text.return_value = "Mock file content"
|
|
mock.read_bytes.return_value = b"Mock binary content"
|
|
mock.write_text.return_value = None
|
|
mock.write_bytes.return_value = None
|
|
mock.unlink.return_value = None
|
|
mock.rename.return_value = None
|
|
mock.stat.return_value = os.stat_result(
|
|
(0o100644, 0, 0, 0, 0, 0, 1024, 0, 0, 0)
|
|
)
|
|
|
|
return mock
|
|
|
|
def create_large_file(
|
|
base_path: Path,
|
|
size_mb: int = 10,
|
|
chunk_size: int = 1024 * 1024
|
|
) -> Path:
|
|
"""
|
|
Create a large file for performance testing.
|
|
|
|
Args:
|
|
base_path: Directory to create file in
|
|
size_mb: Size of file in megabytes
|
|
chunk_size: Chunk size for writing
|
|
|
|
Returns:
|
|
Path to created file
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
file_path = base_path / f"large_{size_mb}mb.dat"
|
|
total_size = size_mb * 1024 * 1024
|
|
|
|
with open(file_path, "wb") as f:
|
|
remaining = total_size
|
|
while remaining > 0:
|
|
write_size = min(chunk_size, remaining)
|
|
f.write(os.urandom(write_size))
|
|
remaining -= write_size
|
|
|
|
return file_path
|
|
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List, Optional, Union, Callable
|
|
from unittest.mock import MagicMock, patch, Mock
|
|
import importlib
|
|
import sys
|
|
from types import ModuleType
|
|
import contextlib
|
|
|
|
def create_mock_tool(
|
|
name: str = "test_tool",
|
|
functions: Optional[Dict[str, Callable]] = None,
|
|
metadata: Optional[Dict[str, Any]] = None
|
|
) -> Mock:
|
|
"""
|
|
Create a mock tool for testing.
|
|
|
|
Args:
|
|
name: Tool name
|
|
functions: Dictionary of tool functions
|
|
metadata: Tool metadata
|
|
|
|
Returns:
|
|
Mock tool object
|
|
"""
|
|
mock_tool = Mock()
|
|
mock_tool.name = name
|
|
|
|
# Set up tool metadata
|
|
if metadata is None:
|
|
metadata = {
|
|
"name": name,
|
|
"version": "1.0.0",
|
|
"author": "Test Author",
|
|
"description": "Test tool for testing"
|
|
}
|
|
|
|
mock_tool.metadata = metadata
|
|
|
|
# Set up tool functions
|
|
if functions is None:
|
|
functions = {
|
|
"initialize": lambda: True,
|
|
"execute": lambda *args, **kwargs: {"result": "success"},
|
|
"cleanup": lambda: None
|
|
}
|
|
|
|
for func_name, func_impl in functions.items():
|
|
setattr(mock_tool, func_name, func_impl)
|
|
|
|
return mock_tool
|
|
|
|
def create_tool_directory_structure(
|
|
base_path: Path,
|
|
tools: List[Dict[str, Any]]
|
|
) -> Dict[str, Path]:
|
|
"""
|
|
Create a tool directory structure for testing.
|
|
|
|
Args:
|
|
base_path: Base directory path
|
|
tools: List of tool definitions
|
|
|
|
Returns:
|
|
Dictionary mapping tool names to their paths
|
|
"""
|
|
if not base_path.exists():
|
|
base_path.mkdir(parents=True)
|
|
|
|
tool_paths = {}
|
|
|
|
for tool_def in tools:
|
|
tool_name = tool_def["name"]
|
|
tool_dir = base_path / tool_name
|
|
|
|
if not tool_dir.exists():
|
|
tool_dir.mkdir()
|
|
|
|
# Create __init__.py
|
|
init_file = tool_dir / "__init__.py"
|
|
init_file.write_text(f"# {tool_name} tool\n")
|
|
|
|
# Create main tool file
|
|
tool_file = tool_dir / f"{tool_name}.py"
|
|
tool_content = f"""
|
|
|
|
def initialize():
|
|
'''Initialize the tool'''
|
|
return True
|
|
|
|
def execute(*args, **kwargs):
|
|
'''Execute tool functionality'''
|
|
return {{"tool": "{tool_name}", "status": "success"}}
|
|
|
|
def cleanup():
|
|
'''Clean up tool resources'''
|
|
pass
|
|
|
|
metadata = {{
|
|
"name": "{tool_name}",
|
|
"version": "{tool_def.get("version", "1.0.0")}",
|
|
"author": "{tool_def.get("author", "Test Author")}",
|
|
"description": "{tool_def.get("description", "Test tool")}"
|
|
}}
|
|
"""
|
|
|
|
tool_file.write_text(tool_content.strip())
|
|
|
|
tool_paths[tool_name] = tool_dir
|
|
|
|
return tool_paths
|
|
|
|
def mock_tool_loader(
|
|
tools: Optional[List[Mock]] = None
|
|
) -> MagicMock:
|
|
"""
|
|
Create a mock tool loader for testing.
|
|
|
|
Args:
|
|
tools: List of mock tools to load
|
|
|
|
Returns:
|
|
Mock tool loader
|
|
"""
|
|
mock_loader = MagicMock()
|
|
|
|
if tools is None:
|
|
tools = [
|
|
create_mock_tool("tool1"),
|
|
create_mock_tool("tool2")
|
|
]
|
|
|
|
mock_loader.load_tools.return_value = tools
|
|
mock_loader.get_tool_by_name.side_effect = lambda name: next(
|
|
(p for p in tools if p.name == name), None
|
|
)
|
|
|
|
return mock_loader
|
|
|
|
def create_tool_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create test scenarios for tool testing.
|
|
|
|
Returns:
|
|
List of tool test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "successful_tool_loading",
|
|
"tools": [
|
|
{"name": "valid_tool1", "version": "1.0.0"},
|
|
{"name": "valid_tool2", "version": "2.0.0"}
|
|
],
|
|
"expected_result": "success"
|
|
},
|
|
{
|
|
"name": "tool_loading_failure",
|
|
"tools": [
|
|
{"name": "invalid_tool", "version": "1.0.0", "has_error": True}
|
|
],
|
|
"expected_result": "failure"
|
|
},
|
|
{
|
|
"name": "tool_compatibility_issue",
|
|
"tools": [
|
|
{"name": "old_tool", "version": "0.5.0"},
|
|
{"name": "new_tool", "version": "3.0.0"}
|
|
],
|
|
"expected_result": "compatibility_warning"
|
|
}
|
|
]
|
|
|
|
def simulate_tool_errors(
|
|
error_type: str = "loading",
|
|
tool_name: str = "test_tool"
|
|
) -> Callable:
|
|
"""
|
|
Create a context manager to simulate tool errors.
|
|
|
|
Args:
|
|
error_type: Type of error to simulate
|
|
tool_name: Name of tool to fail
|
|
|
|
Returns:
|
|
Context manager for error simulation
|
|
"""
|
|
@contextlib.contextmanager
|
|
def error_context():
|
|
"""Inner context manager for simulating tool errors."""
|
|
error_map = {
|
|
"loading": ImportError(f"Cannot load tool {tool_name}"),
|
|
"initialization": RuntimeError(f"Tool {tool_name} initialization failed"),
|
|
"execution": ValueError(f"Tool {tool_name} execution error"),
|
|
"compatibility": RuntimeError(f"Tool {tool_name} compatibility issue")
|
|
}
|
|
|
|
error = error_map.get(error_type, RuntimeError("Tool error"))
|
|
|
|
with patch('importlib.import_module') as mock_import:
|
|
if error_type == "loading":
|
|
mock_import.side_effect = error
|
|
else:
|
|
mock_tool = create_mock_tool(tool_name)
|
|
if error_type == "initialization":
|
|
mock_tool.initialize.side_effect = error
|
|
elif error_type == "execution":
|
|
mock_tool.execute.side_effect = error
|
|
|
|
mock_import.return_value = mock_tool
|
|
|
|
yield
|
|
|
|
return error_context
|
|
|
|
def verify_tool_functionality(
|
|
tool: Union[Mock, ModuleType],
|
|
test_cases: List[Dict[str, Any]]
|
|
) -> Dict[str, bool]:
|
|
"""
|
|
Verify tool functionality against test cases.
|
|
|
|
Args:
|
|
tool: Tool to test
|
|
test_cases: List of test cases
|
|
|
|
Returns:
|
|
Dictionary of test results
|
|
"""
|
|
results = {}
|
|
|
|
for test_case in test_cases:
|
|
test_name = test_case["name"]
|
|
try:
|
|
# Call the appropriate tool function
|
|
if test_case["function"] == "initialize":
|
|
result = tool.initialize()
|
|
elif test_case["function"] == "execute":
|
|
result = tool.execute(*test_case.get("args", []), **test_case.get("kwargs", {}))
|
|
elif test_case["function"] == "cleanup":
|
|
result = tool.cleanup()
|
|
else:
|
|
results[test_name] = False
|
|
continue
|
|
|
|
# Verify result
|
|
expected = test_case.get("expected", True)
|
|
if result == expected:
|
|
results[test_name] = True
|
|
else:
|
|
results[test_name] = False
|
|
|
|
except Exception:
|
|
results[test_name] = False
|
|
|
|
return results
|
|
|
|
def create_tool_dependency_graph(
|
|
tools: List[Dict[str, Any]]
|
|
) -> Dict[str, List[str]]:
|
|
"""
|
|
Create a tool dependency graph for testing.
|
|
|
|
Args:
|
|
tools: List of tool definitions with dependencies
|
|
|
|
Returns:
|
|
Dictionary representing dependency graph
|
|
"""
|
|
graph = {}
|
|
|
|
for tool in tools:
|
|
tool_name = tool["name"]
|
|
dependencies = tool.get("dependencies", [])
|
|
|
|
graph[tool_name] = dependencies
|
|
|
|
return graph
|
|
|
|
def test_tool_dependency_resolution(
|
|
dependency_graph: Dict[str, List[str]],
|
|
resolution_order: List[str]
|
|
) -> bool:
|
|
"""
|
|
Test tool dependency resolution.
|
|
|
|
Args:
|
|
dependency_graph: Tool dependency graph
|
|
resolution_order: Proposed resolution order
|
|
|
|
Returns:
|
|
True if dependencies are satisfied, False otherwise
|
|
"""
|
|
resolved = set()
|
|
|
|
for tool in resolution_order:
|
|
# Check if all dependencies are resolved
|
|
dependencies = dependency_graph.get(tool, [])
|
|
|
|
for dep in dependencies:
|
|
if dep not in resolved:
|
|
return False
|
|
|
|
resolved.add(tool)
|
|
|
|
return True
|
|
|
|
def create_tool_sandbox_environment() -> Dict[str, Any]:
|
|
"""
|
|
Create a sandbox environment for tool testing.
|
|
|
|
Returns:
|
|
Dictionary representing sandbox environment
|
|
"""
|
|
return {
|
|
"temp_dir": tempfile.mkdtemp(),
|
|
"allowed_modules": ["os", "sys", "pathlib", "json"],
|
|
"resource_limits": {
|
|
"memory": 1024 * 1024, # 1MB
|
|
"cpu": 1.0, # 1 CPU core
|
|
"timeout": 30 # 30 seconds
|
|
},
|
|
"permissions": {
|
|
"file_access": "read_only",
|
|
"network_access": False,
|
|
"process_creation": False
|
|
}
|
|
}
|
|
|
|
def mock_tool_registry(
|
|
tools: Optional[List[Mock]] = None
|
|
) -> MagicMock:
|
|
"""
|
|
Create a mock tool registry for testing.
|
|
|
|
Args:
|
|
tools: List of tools to register
|
|
|
|
Returns:
|
|
Mock tool registry
|
|
"""
|
|
mock_registry = MagicMock()
|
|
|
|
if tools is None:
|
|
tools = [
|
|
create_mock_tool("registered_tool1"),
|
|
create_mock_tool("registered_tool2")
|
|
]
|
|
|
|
# Mock registry methods
|
|
mock_registry.get_all_tools.return_value = tools
|
|
mock_registry.get_tool.return_value = tools[0] if tools else None
|
|
mock_registry.register_tool.return_value = True
|
|
mock_registry.unregister_tool.return_value = True
|
|
|
|
return mock_registry
|
|
|
|
def create_tool_lifecycle_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create test scenarios for tool lifecycle testing.
|
|
|
|
Returns:
|
|
List of tool lifecycle test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "normal_lifecycle",
|
|
"steps": [
|
|
{"action": "load", "expected": "success"},
|
|
{"action": "initialize", "expected": "success"},
|
|
{"action": "execute", "expected": "success"},
|
|
{"action": "cleanup", "expected": "success"},
|
|
{"action": "unload", "expected": "success"}
|
|
]
|
|
},
|
|
{
|
|
"name": "initialization_failure",
|
|
"steps": [
|
|
{"action": "load", "expected": "success"},
|
|
{"action": "initialize", "expected": "failure"},
|
|
{"action": "cleanup", "expected": "success"},
|
|
{"action": "unload", "expected": "success"}
|
|
]
|
|
},
|
|
{
|
|
"name": "execution_failure",
|
|
"steps": [
|
|
{"action": "load", "expected": "success"},
|
|
{"action": "initialize", "expected": "success"},
|
|
{"action": "execute", "expected": "failure"},
|
|
{"action": "cleanup", "expected": "success"},
|
|
{"action": "unload", "expected": "success"}
|
|
]
|
|
}
|
|
]
|
|
|
|
def benchmark_tool_performance(
|
|
tool: Union[Mock, ModuleType],
|
|
test_data: List[Dict[str, Any]],
|
|
iterations: int = 100
|
|
) -> Dict[str, float]:
|
|
"""
|
|
Benchmark tool performance.
|
|
|
|
Args:
|
|
tool: Tool to benchmark
|
|
test_data: List of test data inputs
|
|
iterations: Number of iterations
|
|
|
|
Returns:
|
|
Dictionary of performance metrics
|
|
"""
|
|
import time
|
|
|
|
results = {
|
|
"initialize": 0.0,
|
|
"execute": 0.0,
|
|
"cleanup": 0.0
|
|
}
|
|
|
|
# Benchmark initialize
|
|
start_time = time.time()
|
|
for _ in range(iterations):
|
|
tool.initialize()
|
|
end_time = time.time()
|
|
results["initialize"] = (end_time - start_time) / iterations
|
|
|
|
# Benchmark execute
|
|
start_time = time.time()
|
|
for _ in range(iterations):
|
|
for data in test_data:
|
|
tool.execute(**data)
|
|
end_time = time.time()
|
|
results["execute"] = (end_time - start_time) / (iterations * len(test_data))
|
|
|
|
# Benchmark cleanup
|
|
start_time = time.time()
|
|
for _ in range(iterations):
|
|
tool.cleanup()
|
|
end_time = time.time()
|
|
results["cleanup"] = (end_time - start_time) / iterations
|
|
|
|
return results
|
|
|
|
def create_tool_security_test_scenarios() -> List[Dict[str, Any]]:
|
|
"""
|
|
Create test scenarios for tool security testing.
|
|
|
|
Returns:
|
|
List of tool security test scenarios
|
|
"""
|
|
return [
|
|
{
|
|
"name": "safe_tool",
|
|
"tool_code": """
|
|
def execute():
|
|
return {"result": "success"}
|
|
""",
|
|
"expected_result": "allowed"
|
|
},
|
|
{
|
|
"name": "dangerous_tool",
|
|
"tool_code": """
|
|
import os
|
|
def execute():
|
|
os.system("rm -rf /")
|
|
return {"result": "success"}
|
|
""",
|
|
"expected_result": "blocked"
|
|
},
|
|
{
|
|
"name": "resource_intensive_tool",
|
|
"tool_code": """
|
|
def execute():
|
|
while True:
|
|
pass
|
|
return {"result": "success"}
|
|
""",
|
|
"expected_result": "timeout"
|
|
}
|
|
]
|