| """
|
| Tests for async lock initialization patterns.
|
|
|
| This module verifies that asyncio.Lock, asyncio.Semaphore, and asyncio.Event
|
| are properly initialized in async initialize() methods rather than in __init__.
|
|
|
| This prevents RuntimeError when objects are instantiated outside of an
|
| async context (e.g., during import or synchronous instantiation).
|
| """
|
|
|
| import asyncio
|
| import os
|
| import pytest
|
| import pytest_asyncio
|
| from pathlib import Path
|
| from unittest.mock import MagicMock, AsyncMock, patch
|
|
|
|
|
| class TestHAIMEngineAsyncLock:
|
| """Tests for HAIMEngine async lock initialization."""
|
|
|
| def test_engine_sync_instantiation_no_runtime_error(self, tmp_path):
|
| """
|
| Verify that HAIMEngine can be instantiated synchronously without
|
| raising RuntimeError about no running event loop.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.engine import HAIMEngine
|
|
|
|
|
| engine = HAIMEngine(dimension=1024)
|
|
|
|
|
| assert isinstance(engine.synapse_lock, asyncio.Lock)
|
| assert isinstance(engine._write_lock, asyncio.Lock)
|
| assert isinstance(engine._dream_sem, asyncio.Semaphore)
|
| assert engine._initialized is False
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_engine_async_initialization(self, tmp_path):
|
| """
|
| Verify that HAIMEngine.initialize() properly initializes all
|
| asyncio primitives with a running event loop.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.engine import HAIMEngine
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| engine = HAIMEngine(dimension=1024, tier_manager=tier_manager)
|
| await engine.initialize()
|
|
|
|
|
| assert engine.synapse_lock is not None
|
| assert isinstance(engine.synapse_lock, asyncio.Lock)
|
| assert engine._write_lock is not None
|
| assert isinstance(engine._write_lock, asyncio.Lock)
|
| assert engine._dream_sem is not None
|
| assert isinstance(engine._dream_sem, asyncio.Semaphore)
|
| assert engine._initialized is True
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_engine_initialize_is_idempotent(self, tmp_path):
|
| """
|
| Verify that calling initialize() multiple times is safe and
|
| does not recreate locks.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.engine import HAIMEngine
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| engine = HAIMEngine(dimension=1024, tier_manager=tier_manager)
|
| await engine.initialize()
|
|
|
|
|
| first_lock = engine.synapse_lock
|
| first_write_lock = engine._write_lock
|
| first_sem = engine._dream_sem
|
|
|
|
|
| await engine.initialize()
|
|
|
|
|
| assert engine.synapse_lock is first_lock
|
| assert engine._write_lock is first_write_lock
|
| assert engine._dream_sem is first_sem
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_engine_locks_functional(self, tmp_path):
|
| """
|
| Verify that the initialized locks can actually be used.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.engine import HAIMEngine
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| engine = HAIMEngine(dimension=1024, tier_manager=tier_manager)
|
| await engine.initialize()
|
|
|
|
|
| async with engine.synapse_lock:
|
| pass
|
|
|
| async with engine._write_lock:
|
| pass
|
|
|
| async with engine._dream_sem:
|
| pass
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
|
|
| class TestTierManagerAsyncLock:
|
| """Tests for TierManager async lock initialization."""
|
|
|
| def test_tier_manager_sync_instantiation_no_runtime_error(self, tmp_path):
|
| """
|
| Verify that TierManager can be instantiated synchronously without
|
| raising RuntimeError about no running event loop.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
|
|
| with patch("qdrant_client.QdrantClient", side_effect=Exception("Qdrant Mock Fail")):
|
|
|
| tier_manager = TierManager()
|
|
|
|
|
| assert isinstance(tier_manager.lock, asyncio.Lock)
|
| assert tier_manager._initialized is False
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_tier_manager_async_initialization(self, tmp_path):
|
| """
|
| Verify that TierManager.initialize() properly initializes the
|
| asyncio.Lock with a running event loop.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| await tier_manager.initialize()
|
|
|
|
|
| assert tier_manager.lock is not None
|
| assert isinstance(tier_manager.lock, asyncio.Lock)
|
| assert tier_manager._initialized is True
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_tier_manager_initialize_is_idempotent(self, tmp_path):
|
| """
|
| Verify that calling initialize() multiple times is safe and
|
| does not recreate locks.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| await tier_manager.initialize()
|
|
|
|
|
| first_lock = tier_manager.lock
|
|
|
|
|
| await tier_manager.initialize()
|
|
|
|
|
| assert tier_manager.lock is first_lock
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_tier_manager_lock_functional(self, tmp_path):
|
| """
|
| Verify that the initialized lock can actually be used.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| await tier_manager.initialize()
|
|
|
|
|
| async with tier_manager.lock:
|
| pass
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
|
|
| class TestAsyncLockPatternIntegration:
|
| """Integration tests for async lock patterns across the codebase."""
|
|
|
| @pytest.mark.asyncio
|
| async def test_full_engine_workflow(self, tmp_path):
|
| """
|
| Test a complete workflow: instantiate engine, initialize, use locks.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.engine import HAIMEngine
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
| engine = HAIMEngine(dimension=1024, tier_manager=tier_manager)
|
|
|
|
|
| await engine.initialize()
|
|
|
|
|
|
|
| await engine.bind_memories("test_id_a", "test_id_b", success=True)
|
|
|
|
|
| assert engine.synapse_lock.locked() is False
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_concurrent_initialize_calls(self, tmp_path):
|
| """
|
| Test that concurrent initialize() calls are safe due to idempotency.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.engine import HAIMEngine
|
| from mnemocore.core.tier_manager import TierManager
|
|
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| engine = HAIMEngine(dimension=1024, tier_manager=tier_manager)
|
|
|
|
|
| await asyncio.gather(
|
| engine.initialize(),
|
| engine.initialize(),
|
| engine.initialize(),
|
| )
|
|
|
|
|
| assert engine._initialized is True
|
| assert engine.synapse_lock is not None
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| @pytest.mark.asyncio
|
| async def test_tier_manager_concurrent_access(self, tmp_path):
|
| """
|
| Test that TierManager lock protects concurrent access properly.
|
| """
|
|
|
| os.environ["HAIM_DATA_DIR"] = str(tmp_path / "data")
|
| os.environ["HAIM_WARM_MMAP_DIR"] = str(tmp_path / "warm")
|
| os.environ["HAIM_COLD_ARCHIVE_DIR"] = str(tmp_path / "cold")
|
|
|
| try:
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
| from mnemocore.core.tier_manager import TierManager
|
| from mnemocore.core.node import MemoryNode
|
| from mnemocore.core.binary_hdv import BinaryHDV
|
|
|
| tier_manager = TierManager()
|
| tier_manager.use_qdrant = False
|
| if not tier_manager.warm_path:
|
| tier_manager.warm_path = Path(tmp_path / "warm")
|
| tier_manager.warm_path.mkdir(parents=True, exist_ok=True)
|
|
|
| await tier_manager.initialize()
|
|
|
|
|
| nodes = []
|
| for i in range(10):
|
| hdv = BinaryHDV.random(1024)
|
| node = MemoryNode(
|
| id=f"test_node_{i}",
|
| hdv=hdv,
|
| content=f"Test content {i}",
|
| metadata={}
|
| )
|
| nodes.append(node)
|
|
|
|
|
| async def add_node(node):
|
| await tier_manager.add_memory(node)
|
|
|
| await asyncio.gather(*[add_node(n) for n in nodes])
|
|
|
|
|
| assert len(tier_manager.hot) >= 10
|
| finally:
|
|
|
| for key in ["HAIM_DATA_DIR", "HAIM_WARM_MMAP_DIR", "HAIM_COLD_ARCHIVE_DIR"]:
|
| os.environ.pop(key, None)
|
| from mnemocore.core.config import reset_config
|
| reset_config()
|
|
|
|
|