diff --git a/src/openhuman/composio/ops_test.rs b/src/openhuman/composio/ops_test.rs index f148b8857..f82ba7ffa 100644 --- a/src/openhuman/composio/ops_test.rs +++ b/src/openhuman/composio/ops_test.rs @@ -728,6 +728,9 @@ async fn composio_execute_via_mock_propagates_backend_error() { #[tokio::test] async fn composio_sync_gmail_via_mock_archives_raw_email_and_updates_outcome() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; use crate::openhuman::config::TEST_ENV_LOCK; use crate::openhuman::memory_store::content::raw::{raw_rel_path, RawKind}; use crate::openhuman::memory_tree::tree::rpc::{list_chunks_rpc, ListChunksRequest}; diff --git a/src/openhuman/memory/ops/documents.rs b/src/openhuman/memory/ops/documents.rs index 8bd99e2de..d11f8b82a 100644 --- a/src/openhuman/memory/ops/documents.rs +++ b/src/openhuman/memory/ops/documents.rs @@ -535,6 +535,9 @@ mod tests { #[tokio::test] async fn direct_document_handlers_roundtrip_through_namespace() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let namespace = unique_namespace("memory-docs-direct"); let key = format!( @@ -613,6 +616,9 @@ mod tests { #[tokio::test] async fn envelope_memory_handlers_report_counts_and_statuses() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let namespace = unique_namespace("memory-docs-envelope"); let key = format!("env{}", &uuid::Uuid::new_v4().as_simple().to_string()[..12]); diff --git a/src/openhuman/memory/ops/kv_graph.rs b/src/openhuman/memory/ops/kv_graph.rs index cc5c9d89d..2405460fb 100644 --- a/src/openhuman/memory/ops/kv_graph.rs +++ b/src/openhuman/memory/ops/kv_graph.rs @@ -163,6 +163,9 @@ mod tests { #[tokio::test] async fn kv_handlers_roundtrip_scoped_values() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let namespace = unique_namespace("kv-graph-kv"); let key = format!( @@ -216,6 +219,9 @@ mod tests { #[tokio::test] async fn graph_handlers_roundtrip_relation_rows() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let namespace = unique_namespace("kv-graph-rel"); let subject = format!( diff --git a/src/openhuman/memory/ops/learn.rs b/src/openhuman/memory/ops/learn.rs index b4bcc8307..d3f3ca6fd 100644 --- a/src/openhuman/memory/ops/learn.rs +++ b/src/openhuman/memory/ops/learn.rs @@ -245,6 +245,9 @@ mod tests { #[tokio::test] async fn memory_learn_all_is_noop_for_explicit_empty_namespace_list() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let outcome = memory_learn_all(LearnAllParams { namespaces: Some(vec![]), @@ -258,6 +261,9 @@ mod tests { #[tokio::test] async fn memory_learn_all_is_noop_when_requested_namespaces_do_not_exist() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let missing = format!( "missing{}", @@ -274,6 +280,9 @@ mod tests { #[tokio::test] async fn memory_learn_all_filters_missing_namespaces_and_dedupes_requested_order() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; let namespace_a = seed_namespace("memory-learn-a").await; let namespace_b = seed_namespace("memory-learn-b").await; let missing = format!( @@ -304,6 +313,9 @@ mod tests { #[tokio::test] async fn memory_learn_all_requires_local_ai_once_existing_namespace_is_selected() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; let namespace = seed_namespace("memory-learn-runtime").await; let tmp = TempDir::new().expect("tempdir"); let _workspace = write_config_with_runtime_enabled(tmp.path(), false).await; @@ -319,6 +331,9 @@ mod tests { #[tokio::test] async fn memory_learn_all_uses_all_namespaces_when_none_is_requested() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; let namespace_a = seed_namespace("memory-learn-all-a").await; let namespace_b = seed_namespace("memory-learn-all-b").await; let tmp = TempDir::new().expect("tempdir"); diff --git a/src/openhuman/memory/ops/mod.rs b/src/openhuman/memory/ops/mod.rs index 3676e4107..d7b2c737e 100644 --- a/src/openhuman/memory/ops/mod.rs +++ b/src/openhuman/memory/ops/mod.rs @@ -70,6 +70,15 @@ pub(crate) use helpers::{ relation_metadata, timestamp_to_rfc3339, validate_memory_relative_path, }; +/// Serializes the tests that drive the process-global memory client +/// (`memory::global`). Each re-points that singleton at its own workspace, so +/// running them concurrently races on one client + SQLite connection +/// (`SQLITE_IOERR` during schema init + cross-test data bleed). Async tests +/// hold this for their whole body; serial execution is their proven-safe mode. +#[cfg(test)] +pub(crate) static GLOBAL_MEMORY_TEST_LOCK: tokio::sync::Mutex<()> = + tokio::sync::Mutex::const_new(()); + #[cfg(test)] #[path = "../ops_tests.rs"] mod tests; diff --git a/src/openhuman/memory/ops/sync.rs b/src/openhuman/memory/ops/sync.rs index 2fa1320ef..b0a5c77df 100644 --- a/src/openhuman/memory/ops/sync.rs +++ b/src/openhuman/memory/ops/sync.rs @@ -313,6 +313,9 @@ mod tests { #[tokio::test] async fn memory_sync_channel_publishes_targeted_event() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; let _guard = test_mutex() .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -338,6 +341,9 @@ mod tests { #[tokio::test] async fn memory_sync_all_publishes_broadcast_event() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; let _guard = test_mutex() .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); @@ -361,6 +367,9 @@ mod tests { #[tokio::test] async fn memory_ingestion_status_reflects_initialized_client_snapshot() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; let _guard = test_mutex() .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); diff --git a/src/openhuman/memory/ops/tool_memory.rs b/src/openhuman/memory/ops/tool_memory.rs index ea60613fb..ccc310b78 100644 --- a/src/openhuman/memory/ops/tool_memory.rs +++ b/src/openhuman/memory/ops/tool_memory.rs @@ -202,6 +202,9 @@ mod tests { #[tokio::test] async fn tool_rule_put_get_list_and_delete_roundtrip() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let tool_name = unique_tool_name(); @@ -268,6 +271,9 @@ mod tests { #[tokio::test] async fn tool_rules_for_prompt_sorts_by_priority_and_tool_name() { + let _serial = crate::openhuman::memory::ops::GLOBAL_MEMORY_TEST_LOCK + .lock() + .await; ensure_memory_client(); let primary_tool = unique_tool_name(); let secondary_tool = unique_tool_name();