883 lines
No EOL
31 KiB
Python
883 lines
No EOL
31 KiB
Python
"""
|
|
Unit tests for session cache management.
|
|
"""
|
|
import pytest
|
|
import time
|
|
import uuid
|
|
from unittest.mock import Mock, patch
|
|
from datetime import datetime, timedelta
|
|
from collections import defaultdict
|
|
|
|
from detector_worker.storage.session_cache import (
|
|
SessionCache,
|
|
SessionCacheManager,
|
|
SessionData,
|
|
CacheConfig,
|
|
CacheEntry,
|
|
CacheStats,
|
|
SessionError,
|
|
CacheError
|
|
)
|
|
from detector_worker.detection.detection_result import DetectionResult, BoundingBox
|
|
|
|
|
|
class TestCacheConfig:
|
|
"""Test cache configuration."""
|
|
|
|
def test_creation_default(self):
|
|
"""Test creating cache config with default values."""
|
|
config = CacheConfig()
|
|
|
|
assert config.max_size == 1000
|
|
assert config.ttl_seconds == 3600 # 1 hour
|
|
assert config.cleanup_interval == 300 # 5 minutes
|
|
assert config.eviction_policy == "lru"
|
|
assert config.enable_persistence is False
|
|
|
|
def test_creation_custom(self):
|
|
"""Test creating cache config with custom values."""
|
|
config = CacheConfig(
|
|
max_size=5000,
|
|
ttl_seconds=7200,
|
|
cleanup_interval=600,
|
|
eviction_policy="lfu",
|
|
enable_persistence=True,
|
|
persistence_path="/tmp/cache"
|
|
)
|
|
|
|
assert config.max_size == 5000
|
|
assert config.ttl_seconds == 7200
|
|
assert config.cleanup_interval == 600
|
|
assert config.eviction_policy == "lfu"
|
|
assert config.enable_persistence is True
|
|
assert config.persistence_path == "/tmp/cache"
|
|
|
|
def test_from_dict(self):
|
|
"""Test creating config from dictionary."""
|
|
config_dict = {
|
|
"max_size": 2000,
|
|
"ttl_seconds": 1800,
|
|
"eviction_policy": "fifo",
|
|
"enable_persistence": True,
|
|
"unknown_field": "ignored"
|
|
}
|
|
|
|
config = CacheConfig.from_dict(config_dict)
|
|
|
|
assert config.max_size == 2000
|
|
assert config.ttl_seconds == 1800
|
|
assert config.eviction_policy == "fifo"
|
|
assert config.enable_persistence is True
|
|
|
|
|
|
class TestCacheEntry:
|
|
"""Test cache entry data structure."""
|
|
|
|
def test_creation(self):
|
|
"""Test cache entry creation."""
|
|
data = {"key": "value", "number": 42}
|
|
entry = CacheEntry(data, ttl_seconds=600)
|
|
|
|
assert entry.data == data
|
|
assert entry.ttl_seconds == 600
|
|
assert entry.created_at <= time.time()
|
|
assert entry.last_accessed <= time.time()
|
|
assert entry.access_count == 1
|
|
assert entry.size > 0
|
|
|
|
def test_is_expired(self):
|
|
"""Test expiration checking."""
|
|
# Non-expired entry
|
|
entry = CacheEntry({"data": "test"}, ttl_seconds=600)
|
|
assert entry.is_expired() is False
|
|
|
|
# Expired entry (simulate by setting old creation time)
|
|
entry.created_at = time.time() - 700 # Created 700 seconds ago
|
|
assert entry.is_expired() is True
|
|
|
|
# Entry without expiration
|
|
entry_no_ttl = CacheEntry({"data": "test"})
|
|
assert entry_no_ttl.is_expired() is False
|
|
|
|
def test_touch(self):
|
|
"""Test updating access time and count."""
|
|
entry = CacheEntry({"data": "test"})
|
|
|
|
original_access_time = entry.last_accessed
|
|
original_access_count = entry.access_count
|
|
|
|
time.sleep(0.01) # Small delay
|
|
entry.touch()
|
|
|
|
assert entry.last_accessed > original_access_time
|
|
assert entry.access_count == original_access_count + 1
|
|
|
|
def test_age(self):
|
|
"""Test age calculation."""
|
|
entry = CacheEntry({"data": "test"})
|
|
|
|
time.sleep(0.01) # Small delay
|
|
age = entry.age()
|
|
|
|
assert age > 0
|
|
assert age < 1 # Should be less than 1 second
|
|
|
|
def test_size_estimation(self):
|
|
"""Test size estimation."""
|
|
small_entry = CacheEntry({"key": "value"})
|
|
large_entry = CacheEntry({"key": "x" * 1000, "data": list(range(100))})
|
|
|
|
assert large_entry.size > small_entry.size
|
|
|
|
|
|
class TestSessionData:
|
|
"""Test session data structure."""
|
|
|
|
def test_creation(self):
|
|
"""Test session data creation."""
|
|
session_data = SessionData(
|
|
session_id="session_123",
|
|
camera_id="camera_001",
|
|
display_id="display_001"
|
|
)
|
|
|
|
assert session_data.session_id == "session_123"
|
|
assert session_data.camera_id == "camera_001"
|
|
assert session_data.display_id == "display_001"
|
|
assert session_data.created_at <= time.time()
|
|
assert session_data.last_activity <= time.time()
|
|
assert session_data.detection_data == {}
|
|
assert session_data.metadata == {}
|
|
|
|
def test_update_activity(self):
|
|
"""Test updating last activity."""
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
|
|
original_activity = session_data.last_activity
|
|
time.sleep(0.01)
|
|
session_data.update_activity()
|
|
|
|
assert session_data.last_activity > original_activity
|
|
|
|
def test_add_detection_data(self):
|
|
"""Test adding detection data."""
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
|
|
detection_data = {
|
|
"class": "car",
|
|
"confidence": 0.95,
|
|
"bbox": [100, 200, 300, 400]
|
|
}
|
|
|
|
session_data.add_detection_data("main_detection", detection_data)
|
|
|
|
assert "main_detection" in session_data.detection_data
|
|
assert session_data.detection_data["main_detection"] == detection_data
|
|
|
|
def test_add_metadata(self):
|
|
"""Test adding metadata."""
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
|
|
session_data.add_metadata("model_version", "v2.1")
|
|
session_data.add_metadata("inference_time", 0.15)
|
|
|
|
assert session_data.metadata["model_version"] == "v2.1"
|
|
assert session_data.metadata["inference_time"] == 0.15
|
|
|
|
def test_is_expired(self):
|
|
"""Test session expiration."""
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
|
|
# Not expired with default timeout
|
|
assert session_data.is_expired() is False
|
|
|
|
# Expired with short timeout
|
|
assert session_data.is_expired(timeout_seconds=0.001) is True
|
|
|
|
def test_to_dict(self):
|
|
"""Test converting session to dictionary."""
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
session_data.add_detection_data("detection", {"class": "car", "confidence": 0.9})
|
|
session_data.add_metadata("model_id", "yolo_v8")
|
|
|
|
data_dict = session_data.to_dict()
|
|
|
|
assert data_dict["session_id"] == "session_123"
|
|
assert data_dict["camera_id"] == "camera_001"
|
|
assert data_dict["detection_data"]["detection"]["class"] == "car"
|
|
assert data_dict["metadata"]["model_id"] == "yolo_v8"
|
|
assert "created_at" in data_dict
|
|
assert "last_activity" in data_dict
|
|
|
|
|
|
class TestCacheStats:
|
|
"""Test cache statistics."""
|
|
|
|
def test_creation(self):
|
|
"""Test cache stats creation."""
|
|
stats = CacheStats()
|
|
|
|
assert stats.hits == 0
|
|
assert stats.misses == 0
|
|
assert stats.evictions == 0
|
|
assert stats.size == 0
|
|
assert stats.memory_usage == 0
|
|
|
|
def test_hit_rate_calculation(self):
|
|
"""Test hit rate calculation."""
|
|
stats = CacheStats()
|
|
|
|
# No requests yet
|
|
assert stats.hit_rate() == 0.0
|
|
|
|
# Some hits and misses
|
|
stats.hits = 8
|
|
stats.misses = 2
|
|
|
|
assert stats.hit_rate() == 0.8 # 8 / (8 + 2)
|
|
|
|
def test_total_requests(self):
|
|
"""Test total requests calculation."""
|
|
stats = CacheStats()
|
|
|
|
stats.hits = 15
|
|
stats.misses = 5
|
|
|
|
assert stats.total_requests() == 20
|
|
|
|
|
|
class TestSessionCache:
|
|
"""Test session cache functionality."""
|
|
|
|
def test_creation(self):
|
|
"""Test session cache creation."""
|
|
config = CacheConfig(max_size=100, ttl_seconds=300)
|
|
cache = SessionCache(config)
|
|
|
|
assert cache.config == config
|
|
assert cache.max_size == 100
|
|
assert cache.ttl_seconds == 300
|
|
assert len(cache._cache) == 0
|
|
assert len(cache._access_order) == 0
|
|
|
|
def test_put_and_get_session(self):
|
|
"""Test putting and getting session data."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
session_data.add_detection_data("main", {"class": "car", "confidence": 0.9})
|
|
|
|
# Put session
|
|
cache.put("session_123", session_data)
|
|
|
|
# Get session
|
|
retrieved_data = cache.get("session_123")
|
|
|
|
assert retrieved_data is not None
|
|
assert retrieved_data.session_id == "session_123"
|
|
assert retrieved_data.camera_id == "camera_001"
|
|
assert "main" in retrieved_data.detection_data
|
|
|
|
def test_get_nonexistent_session(self):
|
|
"""Test getting non-existent session."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
result = cache.get("nonexistent_session")
|
|
|
|
assert result is None
|
|
|
|
def test_contains_check(self):
|
|
"""Test checking if session exists."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
cache.put("session_123", session_data)
|
|
|
|
assert cache.contains("session_123") is True
|
|
assert cache.contains("nonexistent_session") is False
|
|
|
|
def test_remove_session(self):
|
|
"""Test removing session."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
cache.put("session_123", session_data)
|
|
|
|
assert cache.contains("session_123") is True
|
|
|
|
removed_data = cache.remove("session_123")
|
|
|
|
assert removed_data is not None
|
|
assert removed_data.session_id == "session_123"
|
|
assert cache.contains("session_123") is False
|
|
|
|
def test_size_tracking(self):
|
|
"""Test cache size tracking."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
assert cache.size() == 0
|
|
assert cache.is_empty() is True
|
|
|
|
# Add sessions
|
|
for i in range(3):
|
|
session_data = SessionData(f"session_{i}", "camera_001", "display_001")
|
|
cache.put(f"session_{i}", session_data)
|
|
|
|
assert cache.size() == 3
|
|
assert cache.is_empty() is False
|
|
|
|
def test_lru_eviction(self):
|
|
"""Test LRU eviction policy."""
|
|
cache = SessionCache(CacheConfig(max_size=3, eviction_policy="lru"))
|
|
|
|
# Fill cache to capacity
|
|
for i in range(3):
|
|
session_data = SessionData(f"session_{i}", "camera_001", "display_001")
|
|
cache.put(f"session_{i}", session_data)
|
|
|
|
# Access session_1 to make it recently used
|
|
cache.get("session_1")
|
|
|
|
# Add another session (should evict session_0, the least recently used)
|
|
new_session = SessionData("session_3", "camera_001", "display_001")
|
|
cache.put("session_3", new_session)
|
|
|
|
assert cache.size() == 3
|
|
assert cache.contains("session_0") is False # Evicted
|
|
assert cache.contains("session_1") is True # Recently accessed
|
|
assert cache.contains("session_2") is True
|
|
assert cache.contains("session_3") is True # Newly added
|
|
|
|
def test_ttl_expiration(self):
|
|
"""Test TTL-based expiration."""
|
|
cache = SessionCache(CacheConfig(max_size=10, ttl_seconds=0.1)) # 100ms TTL
|
|
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
cache.put("session_123", session_data)
|
|
|
|
# Should exist immediately
|
|
assert cache.contains("session_123") is True
|
|
|
|
# Wait for expiration
|
|
time.sleep(0.2)
|
|
|
|
# Should be expired (but might still be in cache until cleanup)
|
|
entry = cache._cache.get("session_123")
|
|
if entry:
|
|
assert entry.is_expired() is True
|
|
|
|
# Getting expired entry should return None and clean it up
|
|
retrieved = cache.get("session_123")
|
|
assert retrieved is None
|
|
assert cache.contains("session_123") is False
|
|
|
|
def test_cleanup_expired_entries(self):
|
|
"""Test cleanup of expired entries."""
|
|
cache = SessionCache(CacheConfig(max_size=10, ttl_seconds=0.1))
|
|
|
|
# Add multiple sessions
|
|
for i in range(3):
|
|
session_data = SessionData(f"session_{i}", "camera_001", "display_001")
|
|
cache.put(f"session_{i}", session_data)
|
|
|
|
assert cache.size() == 3
|
|
|
|
# Wait for expiration
|
|
time.sleep(0.2)
|
|
|
|
# Cleanup expired entries
|
|
cleaned_count = cache.cleanup_expired()
|
|
|
|
assert cleaned_count == 3
|
|
assert cache.size() == 0
|
|
|
|
def test_clear_cache(self):
|
|
"""Test clearing entire cache."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
# Add sessions
|
|
for i in range(5):
|
|
session_data = SessionData(f"session_{i}", "camera_001", "display_001")
|
|
cache.put(f"session_{i}", session_data)
|
|
|
|
assert cache.size() == 5
|
|
|
|
cache.clear()
|
|
|
|
assert cache.size() == 0
|
|
assert cache.is_empty() is True
|
|
|
|
def test_get_all_sessions(self):
|
|
"""Test getting all sessions."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
sessions = []
|
|
for i in range(3):
|
|
session_data = SessionData(f"session_{i}", f"camera_{i}", "display_001")
|
|
cache.put(f"session_{i}", session_data)
|
|
sessions.append(session_data)
|
|
|
|
all_sessions = cache.get_all()
|
|
|
|
assert len(all_sessions) == 3
|
|
for session_id, session_data in all_sessions.items():
|
|
assert session_id.startswith("session_")
|
|
assert session_data.session_id == session_id
|
|
|
|
def test_get_sessions_by_camera(self):
|
|
"""Test getting sessions by camera ID."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
# Add sessions for different cameras
|
|
for i in range(2):
|
|
session_data1 = SessionData(f"session_cam1_{i}", "camera_001", "display_001")
|
|
session_data2 = SessionData(f"session_cam2_{i}", "camera_002", "display_001")
|
|
cache.put(f"session_cam1_{i}", session_data1)
|
|
cache.put(f"session_cam2_{i}", session_data2)
|
|
|
|
camera1_sessions = cache.get_by_camera("camera_001")
|
|
camera2_sessions = cache.get_by_camera("camera_002")
|
|
|
|
assert len(camera1_sessions) == 2
|
|
assert len(camera2_sessions) == 2
|
|
|
|
for session_data in camera1_sessions:
|
|
assert session_data.camera_id == "camera_001"
|
|
|
|
for session_data in camera2_sessions:
|
|
assert session_data.camera_id == "camera_002"
|
|
|
|
def test_statistics_tracking(self):
|
|
"""Test cache statistics tracking."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
cache.put("session_123", session_data)
|
|
|
|
# Cache miss
|
|
cache.get("nonexistent_session")
|
|
|
|
# Cache hit
|
|
cache.get("session_123")
|
|
cache.get("session_123") # Another hit
|
|
|
|
stats = cache.get_stats()
|
|
|
|
assert stats.hits == 2
|
|
assert stats.misses == 1
|
|
assert stats.size == 1
|
|
assert stats.hit_rate() == 2/3 # 2 hits out of 3 total requests
|
|
|
|
def test_memory_usage_estimation(self):
|
|
"""Test memory usage estimation."""
|
|
cache = SessionCache(CacheConfig(max_size=10))
|
|
|
|
initial_memory = cache.get_memory_usage()
|
|
|
|
# Add large session
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
session_data.add_detection_data("large_data", {"data": "x" * 1000})
|
|
cache.put("session_123", session_data)
|
|
|
|
after_memory = cache.get_memory_usage()
|
|
|
|
assert after_memory > initial_memory
|
|
|
|
|
|
class TestSessionCacheManager:
|
|
"""Test session cache manager."""
|
|
|
|
def test_singleton_behavior(self):
|
|
"""Test that SessionCacheManager is a singleton."""
|
|
manager1 = SessionCacheManager()
|
|
manager2 = SessionCacheManager()
|
|
|
|
assert manager1 is manager2
|
|
|
|
def test_initialization(self):
|
|
"""Test session cache manager initialization."""
|
|
manager = SessionCacheManager()
|
|
|
|
assert manager.detection_cache is not None
|
|
assert manager.pipeline_cache is not None
|
|
assert manager.session_cache is not None
|
|
assert isinstance(manager.detection_cache, SessionCache)
|
|
assert isinstance(manager.pipeline_cache, SessionCache)
|
|
assert isinstance(manager.session_cache, SessionCache)
|
|
|
|
def test_cache_detection_result(self):
|
|
"""Test caching detection results."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all() # Start fresh
|
|
|
|
detection_data = {
|
|
"class": "car",
|
|
"confidence": 0.95,
|
|
"bbox": [100, 200, 300, 400],
|
|
"track_id": 1001
|
|
}
|
|
|
|
manager.cache_detection("camera_001", detection_data)
|
|
|
|
cached_detection = manager.get_cached_detection("camera_001")
|
|
|
|
assert cached_detection is not None
|
|
assert cached_detection["class"] == "car"
|
|
assert cached_detection["confidence"] == 0.95
|
|
assert cached_detection["track_id"] == 1001
|
|
|
|
def test_cache_pipeline_result(self):
|
|
"""Test caching pipeline results."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
pipeline_result = {
|
|
"status": "success",
|
|
"detections": [{"class": "car", "confidence": 0.9}],
|
|
"execution_time": 0.15,
|
|
"model_id": "yolo_v8"
|
|
}
|
|
|
|
manager.cache_pipeline_result("camera_001", pipeline_result)
|
|
|
|
cached_result = manager.get_cached_pipeline_result("camera_001")
|
|
|
|
assert cached_result is not None
|
|
assert cached_result["status"] == "success"
|
|
assert cached_result["execution_time"] == 0.15
|
|
assert len(cached_result["detections"]) == 1
|
|
|
|
def test_manage_session_data(self):
|
|
"""Test session data management."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
session_id = str(uuid.uuid4())
|
|
|
|
# Create session
|
|
manager.create_session(session_id, "camera_001", {"initial": "data"})
|
|
|
|
# Update session
|
|
manager.update_session_detection(session_id, {"car_brand": "Toyota"})
|
|
|
|
# Get session
|
|
session_data = manager.get_session_detection(session_id)
|
|
|
|
assert session_data is not None
|
|
assert "initial" in session_data
|
|
assert session_data["car_brand"] == "Toyota"
|
|
|
|
def test_set_latest_frame(self):
|
|
"""Test setting and getting latest frame."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
frame_data = b"fake_frame_data"
|
|
|
|
manager.set_latest_frame("camera_001", frame_data)
|
|
|
|
retrieved_frame = manager.get_latest_frame("camera_001")
|
|
|
|
assert retrieved_frame == frame_data
|
|
|
|
def test_frame_skip_flag_management(self):
|
|
"""Test frame skip flag management."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
# Initially should be False
|
|
assert manager.get_frame_skip_flag("camera_001") is False
|
|
|
|
# Set to True
|
|
manager.set_frame_skip_flag("camera_001", True)
|
|
assert manager.get_frame_skip_flag("camera_001") is True
|
|
|
|
# Set back to False
|
|
manager.set_frame_skip_flag("camera_001", False)
|
|
assert manager.get_frame_skip_flag("camera_001") is False
|
|
|
|
def test_cleanup_expired_sessions(self):
|
|
"""Test cleanup of expired sessions."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
# Create sessions with short TTL
|
|
manager.session_cache = SessionCache(CacheConfig(max_size=10, ttl_seconds=0.1))
|
|
|
|
# Add sessions
|
|
for i in range(3):
|
|
session_id = f"session_{i}"
|
|
manager.create_session(session_id, "camera_001", {"test": "data"})
|
|
|
|
assert manager.session_cache.size() == 3
|
|
|
|
# Wait for expiration
|
|
time.sleep(0.2)
|
|
|
|
# Cleanup
|
|
expired_count = manager.cleanup_expired_sessions()
|
|
|
|
assert expired_count == 3
|
|
assert manager.session_cache.size() == 0
|
|
|
|
def test_clear_camera_cache(self):
|
|
"""Test clearing cache for specific camera."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
# Add data for multiple cameras
|
|
manager.cache_detection("camera_001", {"class": "car"})
|
|
manager.cache_detection("camera_002", {"class": "truck"})
|
|
manager.cache_pipeline_result("camera_001", {"status": "success"})
|
|
manager.set_latest_frame("camera_001", b"frame1")
|
|
manager.set_latest_frame("camera_002", b"frame2")
|
|
|
|
# Clear camera_001 cache
|
|
manager.clear_camera_cache("camera_001")
|
|
|
|
# camera_001 data should be gone
|
|
assert manager.get_cached_detection("camera_001") is None
|
|
assert manager.get_cached_pipeline_result("camera_001") is None
|
|
assert manager.get_latest_frame("camera_001") is None
|
|
|
|
# camera_002 data should remain
|
|
assert manager.get_cached_detection("camera_002") is not None
|
|
assert manager.get_latest_frame("camera_002") is not None
|
|
|
|
def test_get_cache_statistics(self):
|
|
"""Test getting cache statistics."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
# Add some data to generate statistics
|
|
manager.cache_detection("camera_001", {"class": "car"})
|
|
manager.cache_pipeline_result("camera_001", {"status": "success"})
|
|
manager.create_session("session_123", "camera_001", {"initial": "data"})
|
|
|
|
# Access data to generate hits/misses
|
|
manager.get_cached_detection("camera_001") # Hit
|
|
manager.get_cached_detection("camera_002") # Miss
|
|
|
|
stats = manager.get_cache_statistics()
|
|
|
|
assert "detection_cache" in stats
|
|
assert "pipeline_cache" in stats
|
|
assert "session_cache" in stats
|
|
assert "total_memory_usage" in stats
|
|
|
|
detection_stats = stats["detection_cache"]
|
|
assert detection_stats["size"] >= 1
|
|
assert detection_stats["hits"] >= 1
|
|
assert detection_stats["misses"] >= 1
|
|
|
|
def test_memory_pressure_handling(self):
|
|
"""Test handling memory pressure."""
|
|
# Create manager with small cache sizes
|
|
config = CacheConfig(max_size=3)
|
|
manager = SessionCacheManager()
|
|
manager.detection_cache = SessionCache(config)
|
|
manager.pipeline_cache = SessionCache(config)
|
|
manager.session_cache = SessionCache(config)
|
|
|
|
# Fill caches beyond capacity
|
|
for i in range(5):
|
|
manager.cache_detection(f"camera_{i}", {"class": "car", "data": "x" * 100})
|
|
manager.cache_pipeline_result(f"camera_{i}", {"status": "success", "data": "y" * 100})
|
|
manager.create_session(f"session_{i}", f"camera_{i}", {"data": "z" * 100})
|
|
|
|
# Caches should not exceed max size due to eviction
|
|
assert manager.detection_cache.size() <= 3
|
|
assert manager.pipeline_cache.size() <= 3
|
|
assert manager.session_cache.size() <= 3
|
|
|
|
def test_concurrent_access_thread_safety(self):
|
|
"""Test thread safety of concurrent cache access."""
|
|
import threading
|
|
import concurrent.futures
|
|
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
results = []
|
|
errors = []
|
|
|
|
def cache_operation(thread_id):
|
|
try:
|
|
# Each thread performs multiple cache operations
|
|
for i in range(10):
|
|
session_id = f"thread_{thread_id}_session_{i}"
|
|
|
|
# Create session
|
|
manager.create_session(session_id, f"camera_{thread_id}", {"thread": thread_id, "index": i})
|
|
|
|
# Update session
|
|
manager.update_session_detection(session_id, {"updated": True})
|
|
|
|
# Read session
|
|
data = manager.get_session_detection(session_id)
|
|
if data and data.get("thread") == thread_id:
|
|
results.append((thread_id, i))
|
|
|
|
except Exception as e:
|
|
errors.append((thread_id, str(e)))
|
|
|
|
# Run operations concurrently
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
|
|
futures = [executor.submit(cache_operation, i) for i in range(5)]
|
|
concurrent.futures.wait(futures)
|
|
|
|
# Should have no errors and successful operations
|
|
assert len(errors) == 0
|
|
assert len(results) >= 25 # At least some operations should succeed
|
|
|
|
|
|
class TestSessionCacheIntegration:
|
|
"""Integration tests for session cache."""
|
|
|
|
def test_complete_detection_workflow(self):
|
|
"""Test complete detection workflow with caching."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
camera_id = "camera_001"
|
|
session_id = str(uuid.uuid4())
|
|
|
|
# 1. Cache initial detection
|
|
detection_data = {
|
|
"class": "car",
|
|
"confidence": 0.92,
|
|
"bbox": [100, 200, 300, 400],
|
|
"track_id": 1001,
|
|
"timestamp": int(time.time() * 1000)
|
|
}
|
|
|
|
manager.cache_detection(camera_id, detection_data)
|
|
|
|
# 2. Create session for tracking
|
|
initial_session_data = {
|
|
"detection_class": detection_data["class"],
|
|
"confidence": detection_data["confidence"],
|
|
"track_id": detection_data["track_id"]
|
|
}
|
|
|
|
manager.create_session(session_id, camera_id, initial_session_data)
|
|
|
|
# 3. Cache pipeline processing result
|
|
pipeline_result = {
|
|
"status": "processing",
|
|
"stage": "classification",
|
|
"detections": [detection_data],
|
|
"branches_completed": [],
|
|
"branches_pending": ["car_brand_cls", "car_bodytype_cls"]
|
|
}
|
|
|
|
manager.cache_pipeline_result(camera_id, pipeline_result)
|
|
|
|
# 4. Update session with classification results
|
|
classification_updates = [
|
|
{"car_brand": "Toyota", "brand_confidence": 0.87},
|
|
{"car_body_type": "Sedan", "bodytype_confidence": 0.82}
|
|
]
|
|
|
|
for update in classification_updates:
|
|
manager.update_session_detection(session_id, update)
|
|
|
|
# 5. Update pipeline result to completed
|
|
final_pipeline_result = {
|
|
"status": "completed",
|
|
"stage": "finished",
|
|
"detections": [detection_data],
|
|
"branches_completed": ["car_brand_cls", "car_bodytype_cls"],
|
|
"branches_pending": [],
|
|
"execution_time": 0.25
|
|
}
|
|
|
|
manager.cache_pipeline_result(camera_id, final_pipeline_result)
|
|
|
|
# 6. Verify all cached data
|
|
cached_detection = manager.get_cached_detection(camera_id)
|
|
cached_pipeline = manager.get_cached_pipeline_result(camera_id)
|
|
cached_session = manager.get_session_detection(session_id)
|
|
|
|
# Assertions
|
|
assert cached_detection["class"] == "car"
|
|
assert cached_detection["track_id"] == 1001
|
|
|
|
assert cached_pipeline["status"] == "completed"
|
|
assert len(cached_pipeline["branches_completed"]) == 2
|
|
|
|
assert cached_session["detection_class"] == "car"
|
|
assert cached_session["car_brand"] == "Toyota"
|
|
assert cached_session["car_body_type"] == "Sedan"
|
|
assert cached_session["brand_confidence"] == 0.87
|
|
|
|
def test_cache_performance_under_load(self):
|
|
"""Test cache performance under load."""
|
|
manager = SessionCacheManager()
|
|
manager.clear_all()
|
|
|
|
import time
|
|
|
|
# Measure performance of cache operations
|
|
start_time = time.time()
|
|
|
|
# Perform many cache operations
|
|
for i in range(1000):
|
|
camera_id = f"camera_{i % 10}" # 10 different cameras
|
|
session_id = f"session_{i}"
|
|
|
|
# Cache detection
|
|
detection_data = {
|
|
"class": "car",
|
|
"confidence": 0.9 + (i % 10) * 0.01,
|
|
"track_id": i,
|
|
"bbox": [i % 100, i % 100, (i % 100) + 200, (i % 100) + 200]
|
|
}
|
|
manager.cache_detection(camera_id, detection_data)
|
|
|
|
# Create session
|
|
manager.create_session(session_id, camera_id, {"index": i})
|
|
|
|
# Read back (every 10th operation)
|
|
if i % 10 == 0:
|
|
manager.get_cached_detection(camera_id)
|
|
manager.get_session_detection(session_id)
|
|
|
|
end_time = time.time()
|
|
total_time = end_time - start_time
|
|
|
|
# Should complete in reasonable time (less than 1 second)
|
|
assert total_time < 1.0
|
|
|
|
# Verify cache statistics
|
|
stats = manager.get_cache_statistics()
|
|
assert stats["detection_cache"]["size"] > 0
|
|
assert stats["session_cache"]["size"] > 0
|
|
assert stats["detection_cache"]["hits"] > 0
|
|
|
|
def test_cache_persistence_and_recovery(self):
|
|
"""Test cache persistence and recovery (if enabled)."""
|
|
# This test would be more meaningful with actual persistence
|
|
# For now, test the configuration and structure
|
|
|
|
persistence_config = CacheConfig(
|
|
max_size=100,
|
|
enable_persistence=True,
|
|
persistence_path="/tmp/detector_cache_test"
|
|
)
|
|
|
|
cache = SessionCache(persistence_config)
|
|
|
|
# Add some data
|
|
session_data = SessionData("session_123", "camera_001", "display_001")
|
|
session_data.add_detection_data("main", {"class": "car", "confidence": 0.95})
|
|
|
|
cache.put("session_123", session_data)
|
|
|
|
# Verify data exists
|
|
assert cache.contains("session_123") is True
|
|
|
|
# In a real implementation, this would test:
|
|
# 1. Saving cache to disk
|
|
# 2. Loading cache from disk
|
|
# 3. Verifying data integrity after reload |