""" Integration test suite for HCFS components. Tests covering: - Full system integration - End-to-end workflows - Cross-component functionality - Performance under load - Real-world usage scenarios """ import pytest import tempfile import shutil import time import asyncio from pathlib import Path import concurrent.futures import threading import sys sys.path.insert(0, str(Path(__file__).parent.parent)) from hcfs.core.context_db import Context from hcfs.core.context_db_optimized_fixed import OptimizedContextDatabase from hcfs.core.embeddings_optimized import OptimizedEmbeddingManager from hcfs.core.context_versioning import VersioningSystem from hcfs.core.context_db_trio import TrioContextDatabase class TestFullSystemIntegration: """Test full HCFS system integration.""" @pytest.fixture def integrated_system(self): """Create fully integrated HCFS system.""" temp_dir = Path(tempfile.mkdtemp()) db_path = temp_dir / "integration_test.db" vector_db_path = temp_dir / "integration_vectors.db" # Initialize all components context_db = OptimizedContextDatabase(str(db_path)) embedding_manager = OptimizedEmbeddingManager( context_db, model_name="mini", vector_db_path=str(vector_db_path), cache_size=200 ) versioning_system = VersioningSystem(str(db_path)) yield context_db, embedding_manager, versioning_system shutil.rmtree(temp_dir) def test_complete_context_lifecycle(self, integrated_system): """Test complete context lifecycle with all features.""" context_db, embedding_manager, versioning_system = integrated_system # 1. Create initial context context = Context( None, "/project/hcfs", "HCFS is a context-aware hierarchical filesystem for AI agents", "HCFS project description", "developer", 1 ) context_id = context_db.store_context(context) assert context_id is not None # 2. Generate and store embedding embedding = embedding_manager.generate_embedding(context.content) embedding_manager.store_embedding(context_id, embedding) # 3. Create version initial_version = versioning_system.create_version( context_id, "developer", "Initial project description" ) assert initial_version is not None # 4. Update context updated_content = "HCFS is an advanced context-aware hierarchical filesystem with ML-powered semantic search" context_db.update_context(context_id, content=updated_content) # 5. Update embedding new_embedding = embedding_manager.generate_embedding(updated_content) embedding_manager.store_embedding(context_id, new_embedding) # 6. Create new version updated_version = versioning_system.create_version( context_id, "developer", "Added ML and semantic search details" ) # 7. Test search functionality search_results = embedding_manager.semantic_search_optimized( "machine learning filesystem", top_k=5, include_contexts=True ) assert len(search_results) > 0 found_context = any(result.context_id == context_id for result in search_results) assert found_context, "Should find the updated context in search results" # 8. Test version history history = versioning_system.get_version_history(context_id) assert len(history) == 2 assert history[0].message == "Added ML and semantic search details" assert history[1].message == "Initial project description" # 9. Test rollback rollback_version = versioning_system.rollback_to_version( context_id, initial_version.version_number, "developer", "Testing rollback" ) # Verify rollback worked current_context = context_db.get_context(context_id) assert "HCFS is a context-aware hierarchical filesystem for AI agents" in current_context.content def test_hierarchical_context_inheritance(self, integrated_system): """Test hierarchical context relationships.""" context_db, embedding_manager, _ = integrated_system # Create hierarchical contexts contexts = [ Context(None, "/", "Root directory context", "Root summary", "user", 1), Context(None, "/projects", "Projects directory", "Projects summary", "user", 1), Context(None, "/projects/hcfs", "HCFS project", "HCFS summary", "user", 1), Context(None, "/projects/hcfs/core", "HCFS core modules", "Core summary", "user", 1), ] context_ids = [] for context in contexts: context_id = context_db.store_context(context) context_ids.append(context_id) # Build embeddings for all contexts embedding_manager.build_embeddings_index() # Test hierarchical search results = embedding_manager.semantic_search_optimized( "HCFS development", path_prefix="/projects", top_k=10, include_contexts=True ) # Should find HCFS-related contexts under /projects assert len(results) >= 2 hcfs_results = [r for r in results if "hcfs" in r.context.path.lower()] assert len(hcfs_results) >= 2 def test_multi_user_collaboration(self, integrated_system): """Test multi-user collaboration features.""" context_db, embedding_manager, versioning_system = integrated_system # Create shared context shared_context = Context( None, "/shared/document", "Shared collaborative document", "Team collaboration", "user1", 1 ) context_id = context_db.store_context(shared_context) # User 1 creates initial version v1 = versioning_system.create_version(context_id, "user1", "Initial draft") # User 2 makes changes context_db.update_context( context_id, content="Shared collaborative document with user2 contributions", author="user2" ) v2 = versioning_system.create_version(context_id, "user2", "Added contributions") # User 3 makes changes context_db.update_context( context_id, content="Shared collaborative document with user2 and user3 contributions", author="user3" ) v3 = versioning_system.create_version(context_id, "user3", "Final review") # Test version history shows all contributors history = versioning_system.get_version_history(context_id) authors = {version.author for version in history} assert authors == {"user1", "user2", "user3"} # Test rollback to previous version rollback = versioning_system.rollback_to_version( context_id, v2.version_number, "user1", "Reverting to user2 version" ) current = context_db.get_context(context_id) assert "user2 contributions" in current.content assert "user3 contributions" not in current.content class TestPerformanceIntegration: """Test system performance under integrated load.""" @pytest.fixture def performance_system(self): """Create system for performance testing.""" temp_dir = Path(tempfile.mkdtemp()) db_path = temp_dir / "performance_test.db" vector_db_path = temp_dir / "performance_vectors.db" context_db = OptimizedContextDatabase(str(db_path), cache_size=500) embedding_manager = OptimizedEmbeddingManager( context_db, model_name="mini", vector_db_path=str(vector_db_path), cache_size=300, batch_size=16 ) versioning_system = VersioningSystem(str(db_path)) yield context_db, embedding_manager, versioning_system shutil.rmtree(temp_dir) def test_large_scale_context_management(self, performance_system): """Test managing large numbers of contexts.""" context_db, embedding_manager, versioning_system = performance_system # Create large number of contexts num_contexts = 100 contexts = [] start_time = time.time() for i in range(num_contexts): context = Context( None, f"/large_scale/context_{i}", f"Large scale test context {i} with detailed content about topic {i % 10}", f"Summary for context {i}", f"user_{i % 5}", 1 ) contexts.append(context) # Batch store contexts context_ids = context_db.store_contexts_batch(contexts) storage_time = time.time() - start_time assert len(context_ids) == num_contexts print(f"Stored {num_contexts} contexts in {storage_time:.3f}s ({num_contexts/storage_time:.1f} contexts/sec)") # Build embeddings index start_time = time.time() index_stats = embedding_manager.build_embeddings_index(batch_size=20) index_time = time.time() - start_time assert index_stats["total_processed"] == num_contexts print(f"Built embeddings for {num_contexts} contexts in {index_time:.3f}s") # Test search performance search_queries = [ "detailed content about topic", "large scale test", "context management", "topic 5 information", "user collaboration" ] total_search_time = 0 for query in search_queries: start_time = time.time() results = embedding_manager.semantic_search_optimized(query, top_k=10) search_time = time.time() - start_time total_search_time += search_time assert len(results) > 0 avg_search_time = total_search_time / len(search_queries) print(f"Average search time: {avg_search_time:.4f}s") assert avg_search_time < 0.1 # Should be under 100ms def test_concurrent_system_load(self, performance_system): """Test system under concurrent load.""" context_db, embedding_manager, versioning_system = performance_system # Pre-populate with some data base_contexts = [ Context(None, f"/concurrent/{i}", f"Base context {i}", f"Summary {i}", "base_user", 1) for i in range(20) ] for context in base_contexts: context_db.store_context(context) embedding_manager.build_embeddings_index() def concurrent_worker(worker_id): results = [] # Each worker performs mixed operations for i in range(5): operation_type = i % 4 if operation_type == 0: # Create context context = Context( None, f"/worker{worker_id}/context_{i}", f"Worker {worker_id} context {i} with specific content", f"Worker {worker_id} summary {i}", f"worker{worker_id}", 1 ) context_id = context_db.store_context(context) results.append(("create", context_id)) elif operation_type == 1: # Search search_results = embedding_manager.semantic_search_optimized( f"worker {worker_id} content", top_k=5 ) results.append(("search", len(search_results))) elif operation_type == 2: # Update context if results: # Only if we have created contexts created_contexts = [r for r in results if r[0] == "create"] if created_contexts: context_id = created_contexts[-1][1] try: context_db.update_context( context_id, content=f"Updated by worker {worker_id} iteration {i}" ) results.append(("update", context_id)) except: pass # Context might not exist due to concurrency elif operation_type == 3: # Hybrid search hybrid_results = embedding_manager.hybrid_search_optimized( f"context {worker_id}", top_k=3 ) results.append(("hybrid_search", len(hybrid_results))) return results # Run concurrent workers num_workers = 5 start_time = time.time() with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: futures = [executor.submit(concurrent_worker, i) for i in range(num_workers)] all_results = [future.result() for future in futures] total_time = time.time() - start_time # Verify all workers completed successfully assert len(all_results) == num_workers for worker_results in all_results: assert len(worker_results) >= 3 # Should have completed most operations # Calculate operation statistics total_operations = sum(len(worker_results) for worker_results in all_results) operations_per_second = total_operations / total_time print(f"Completed {total_operations} operations in {total_time:.3f}s ({operations_per_second:.1f} ops/sec)") assert operations_per_second > 10 # Should handle at least 10 operations per second def test_memory_usage_under_load(self, performance_system): """Test memory usage under sustained load.""" context_db, embedding_manager, _ = performance_system import psutil import os process = psutil.Process(os.getpid()) initial_memory = process.memory_info().rss / 1024 / 1024 # MB # Create contexts in batches and monitor memory batch_size = 50 num_batches = 5 for batch_num in range(num_batches): # Create batch of contexts contexts = [ Context( None, f"/memory_test/batch_{batch_num}/context_{i}", f"Memory test context {batch_num}-{i} " + "x" * 100, # Larger content f"Memory summary {batch_num}-{i}", f"memory_user_{batch_num}", 1 ) for i in range(batch_size) ] # Store contexts and build embeddings context_ids = context_db.store_contexts_batch(contexts) # Generate embeddings in batch contents = [context.content for context in contexts] embeddings = embedding_manager.generate_embeddings_batch(contents) # Store embeddings embedding_data = list(zip(context_ids, embeddings)) embedding_manager.store_embeddings_batch(embedding_data) # Check memory usage current_memory = process.memory_info().rss / 1024 / 1024 memory_increase = current_memory - initial_memory print(f"Batch {batch_num + 1}: Memory usage: {current_memory:.1f} MB (+{memory_increase:.1f} MB)") # Perform some searches to exercise the system for query in [f"memory test batch {batch_num}", "context content"]: results = embedding_manager.semantic_search_optimized(query, top_k=5) assert len(results) >= 0 final_memory = process.memory_info().rss / 1024 / 1024 total_increase = final_memory - initial_memory # Memory increase should be reasonable (less than 200MB for this test) print(f"Total memory increase: {total_increase:.1f} MB") assert total_increase < 200, f"Memory usage increased by {total_increase:.1f} MB, which is too much" class TestAsyncIntegration: """Test async/Trio integration.""" @pytest.fixture def async_system(self): """Create system for async testing.""" temp_dir = Path(tempfile.mkdtemp()) db_path = temp_dir / "async_test.db" # Create async-compatible system context_db = OptimizedContextDatabase(str(db_path)) trio_db = TrioContextDatabase(context_db) yield trio_db shutil.rmtree(temp_dir) def test_trio_database_operations(self, async_system): """Test Trio async database operations.""" import trio async def async_test(): trio_db = async_system # Test async context storage context = Context( None, "/async/test", "Async test content", "Async summary", "async_user", 1 ) context_id = await trio_db.store_context(context) assert context_id is not None # Test async retrieval retrieved = await trio_db.get_context(context_id) assert retrieved is not None assert retrieved.content == context.content # Test async search results = await trio_db.search_contexts("async test") assert len(results) > 0 # Test async update await trio_db.update_context(context_id, content="Updated async content") updated = await trio_db.get_context(context_id) assert updated.content == "Updated async content" return "Success" # Run async test result = trio.run(async_test) assert result == "Success" def test_concurrent_async_operations(self, async_system): """Test concurrent async operations.""" import trio async def async_concurrent_test(): trio_db = async_system async def async_worker(worker_id): results = [] for i in range(3): context = Context( None, f"/async_concurrent/{worker_id}/{i}", f"Async worker {worker_id} content {i}", f"Async summary {worker_id}-{i}", f"async_worker_{worker_id}", 1 ) context_id = await trio_db.store_context(context) results.append(context_id) return results # Run multiple async workers concurrently async with trio.open_nursery() as nursery: results = [] for worker_id in range(3): nursery.start_soon(async_worker, worker_id) return "Concurrent async operations completed" result = trio.run(async_concurrent_test) assert "completed" in result class TestErrorHandlingIntegration: """Test error handling across integrated components.""" @pytest.fixture def error_test_system(self): """Create system for error testing.""" temp_dir = Path(tempfile.mkdtemp()) db_path = temp_dir / "error_test.db" vector_db_path = temp_dir / "error_vectors.db" context_db = OptimizedContextDatabase(str(db_path)) embedding_manager = OptimizedEmbeddingManager( context_db, model_name="mini", vector_db_path=str(vector_db_path) ) versioning_system = VersioningSystem(str(db_path)) yield context_db, embedding_manager, versioning_system shutil.rmtree(temp_dir) def test_database_corruption_recovery(self, error_test_system): """Test recovery from database issues.""" context_db, embedding_manager, versioning_system = error_test_system # Create some valid data first context = Context( None, "/error_test/valid", "Valid test content", "Valid summary", "test_user", 1 ) context_id = context_db.store_context(context) assert context_id is not None # Test handling of invalid operations with pytest.raises((ValueError, AttributeError, TypeError)): # Try to store invalid context invalid_context = None context_db.store_context(invalid_context) # Verify original data is still intact retrieved = context_db.get_context(context_id) assert retrieved is not None assert retrieved.content == "Valid test content" def test_embedding_generation_errors(self, error_test_system): """Test embedding generation error handling.""" _, embedding_manager, _ = error_test_system # Test with empty content try: embedding = embedding_manager.generate_embedding("") # Empty string should still generate an embedding assert embedding is not None except Exception as e: # If it fails, it should fail gracefully assert isinstance(e, (ValueError, RuntimeError)) # Test with very long content very_long_text = "x" * 10000 embedding = embedding_manager.generate_embedding(very_long_text) assert embedding is not None assert embedding.shape == (384,) def test_concurrent_error_isolation(self, error_test_system): """Test that errors in one thread don't affect others.""" context_db, embedding_manager, _ = error_test_system def worker_with_error(worker_id): try: if worker_id == 1: # One worker will fail # Try invalid operation context_db.get_context(-1) # Invalid ID return "error_worker_failed" else: # Other workers do valid operations context = Context( None, f"/error_isolation/{worker_id}", f"Valid content {worker_id}", f"Summary {worker_id}", f"user{worker_id}", 1 ) context_id = context_db.store_context(context) return f"success_{context_id}" except Exception as e: return f"error_{type(e).__name__}" # Run workers concurrently with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: futures = [executor.submit(worker_with_error, i) for i in range(3)] results = [future.result() for future in futures] # Check that some workers succeeded despite one failing success_count = sum(1 for r in results if r.startswith("success_")) error_count = sum(1 for r in results if r.startswith("error_")) assert success_count >= 1, "At least one worker should have succeeded" assert error_count >= 1, "At least one worker should have failed" def run_integration_tests(): """Run all integration tests.""" import subprocess import sys try: # Run pytest on this module result = subprocess.run([ sys.executable, "-m", "pytest", __file__, "-v", "--tb=short", "-x" ], capture_output=True, text=True, cwd=Path(__file__).parent.parent) print("INTEGRATION TEST RESULTS") print("=" * 50) print(result.stdout) if result.stderr: print("ERRORS:") print(result.stderr) return result.returncode == 0 except Exception as e: print(f"Failed to run tests: {e}") return False if __name__ == "__main__": success = run_integration_tests() exit(0 if success else 1)