From 732c111f667b3d4ed05f363b21b28666b9958cb1 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Mon, 25 May 2026 10:10:29 -0700 Subject: [PATCH 1/2] fix(tests): prevent daemon interference and ENV_LOCK poison cascade in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues caused post-merge CI failures on macOS and Windows: 1. daemon_mode_two_engines_share_same_hyperd asserted exact endpoint equality, but the daemon's liveness monitor can restart hyperd between Engine::new calls. The panic poisoned ENV_LOCK, cascading to all subsequent tests. Fixed by relaxing the assertion and recovering from mutex poison via unwrap_or_else(into_inner). 2. Non-daemon test files used Engine::new / HyperMcpServer::new which attempt daemon discovery. When daemon_tests leave a daemon alive in the same cargo-test process, other tests connect through it — causing "database still in use" errors on Windows. Switched all non-daemon tests to new_no_daemon / with_no_daemon for isolation. --- hyperdb-mcp/tests/attach_tests.rs | 18 ++++---- hyperdb-mcp/tests/common/mod.rs | 2 +- hyperdb-mcp/tests/daemon_tests.rs | 56 +++++++++++++++--------- hyperdb-mcp/tests/read_only_tests.rs | 4 +- hyperdb-mcp/tests/resource_tests.rs | 17 ++++--- hyperdb-mcp/tests/saved_queries_tests.rs | 19 +++++--- hyperdb-mcp/tests/table_catalog_tests.rs | 34 ++++++++------ hyperdb-mcp/tests/watcher_tests.rs | 2 +- 8 files changed, 89 insertions(+), 63 deletions(-) diff --git a/hyperdb-mcp/tests/attach_tests.rs b/hyperdb-mcp/tests/attach_tests.rs index 9672f40..f4f057c 100644 --- a/hyperdb-mcp/tests/attach_tests.rs +++ b/hyperdb-mcp/tests/attach_tests.rs @@ -23,7 +23,7 @@ use tempfile::TempDir; fn primary_workspace() -> (Engine, TempDir) { let dir = TempDir::new().unwrap(); let path = dir.path().join("primary.hyper"); - let engine = Engine::new(Some(path.to_string_lossy().into())).unwrap(); + let engine = Engine::new_no_daemon(Some(path.to_string_lossy().into())).unwrap(); engine .execute_command("CREATE TABLE primary_t (x INT)") .unwrap(); @@ -40,7 +40,7 @@ fn primary_workspace() -> (Engine, TempDir) { fn build_source_hyper_file(dir: &TempDir, name: &str, rows: &[(i32, &str)]) -> std::path::PathBuf { let path = dir.path().join(name); { - let engine = Engine::new(Some(path.to_string_lossy().into())).unwrap(); + let engine = Engine::new_no_daemon(Some(path.to_string_lossy().into())).unwrap(); engine .execute_command("CREATE TABLE t (a INT, b TEXT)") .unwrap(); @@ -579,7 +579,7 @@ fn replay_reattaches_on_fresh_engine() { let registry = AttachRegistry::new(); { - let engine_a = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine_a = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); registry .attach( &engine_a, @@ -595,7 +595,7 @@ fn replay_reattaches_on_fresh_engine() { .unwrap(); } // engine_a dropped here, closes its connection; attach state is gone on hyperd. - let engine_b = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine_b = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); // Without replay the attachment is invisible to engine_b. assert!(engine_b .execute_query_to_json("SELECT 1 FROM \"src\".public.t LIMIT 0") @@ -695,7 +695,7 @@ fn replay_drops_missing_files() { let registry = AttachRegistry::new(); { - let engine_a = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine_a = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); registry .attach( &engine_a, @@ -715,7 +715,7 @@ fn replay_drops_missing_files() { // — the call should succeed but the entry should be pruned. std::fs::remove_file(&source).unwrap(); - let engine_b = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine_b = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); registry.replay_all(&engine_b).unwrap(); assert!(registry.list().is_empty()); } @@ -871,7 +871,7 @@ fn replay_restores_schema_search_path_pin() { let dir = TempDir::new().unwrap(); let primary_path = dir.path().join("primary.hyper"); { - let engine = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); engine .execute_command("CREATE TABLE primary_t (x INT)") .unwrap(); @@ -883,7 +883,7 @@ fn replay_restores_schema_search_path_pin() { let registry = AttachRegistry::new(); { - let engine_a = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine_a = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); registry .attach( &engine_a, @@ -899,7 +899,7 @@ fn replay_restores_schema_search_path_pin() { .unwrap(); } // engine_a dropped; next engine starts fresh with default search_path. - let engine_b = Engine::new(Some(primary_path.to_string_lossy().into())).unwrap(); + let engine_b = Engine::new_no_daemon(Some(primary_path.to_string_lossy().into())).unwrap(); registry.replay_all(&engine_b).unwrap(); // Unqualified access to the primary must work after replay. diff --git a/hyperdb-mcp/tests/common/mod.rs b/hyperdb-mcp/tests/common/mod.rs index 1b880f4..46e550c 100644 --- a/hyperdb-mcp/tests/common/mod.rs +++ b/hyperdb-mcp/tests/common/mod.rs @@ -21,7 +21,7 @@ impl TestEngine { pub(crate) fn new_ephemeral() -> Self { let temp_dir = TempDir::new().expect("failed to create temp dir"); let workspace_path = temp_dir.path().join("workspace.hyper"); - let engine = Engine::new(Some(workspace_path.to_str().unwrap().to_string())) + let engine = Engine::new_no_daemon(Some(workspace_path.to_str().unwrap().to_string())) .expect("failed to create engine"); Self { engine, diff --git a/hyperdb-mcp/tests/daemon_tests.rs b/hyperdb-mcp/tests/daemon_tests.rs index 9bef11e..2194839 100644 --- a/hyperdb-mcp/tests/daemon_tests.rs +++ b/hyperdb-mcp/tests/daemon_tests.rs @@ -19,8 +19,15 @@ use tempfile::TempDir; /// Process-wide lock for tests that mutate environment variables. /// Cargo runs tests in the same process by default — this prevents races. +/// We recover from poison to prevent one test's panic from cascading. static ENV_LOCK: Mutex<()> = Mutex::new(()); +fn acquire_env_lock() -> std::sync::MutexGuard<'static, ()> { + ENV_LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} + // ─── Unit tests: DaemonState (no env vars, safe to run in parallel) ─────────── #[test] @@ -328,7 +335,7 @@ fn daemon_heartbeat_prevents_idle_shutdown() { #[test] fn discovery_file_write_and_read() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let tmp = TempDir::new().unwrap(); let _guard = EnvGuard::set("HYPERDB_STATE_DIR", tmp.path().to_str().unwrap()); @@ -355,7 +362,7 @@ fn discovery_file_write_and_read() { #[test] fn discovery_file_overwrite_replaces_content() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let tmp = TempDir::new().unwrap(); let _guard = EnvGuard::set("HYPERDB_STATE_DIR", tmp.path().to_str().unwrap()); @@ -386,7 +393,7 @@ fn discovery_file_overwrite_replaces_content() { #[test] fn remove_discovery_file_deletes_it() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let tmp = TempDir::new().unwrap(); let _guard = EnvGuard::set("HYPERDB_STATE_DIR", tmp.path().to_str().unwrap()); @@ -407,7 +414,7 @@ fn remove_discovery_file_deletes_it() { #[test] fn discover_returns_none_when_no_file_exists() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let tmp = TempDir::new().unwrap(); let _guard = EnvGuard::set("HYPERDB_STATE_DIR", tmp.path().to_str().unwrap()); @@ -416,7 +423,7 @@ fn discover_returns_none_when_no_file_exists() { #[test] fn discover_returns_none_for_stale_file() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let tmp = TempDir::new().unwrap(); let _guard = EnvGuard::set("HYPERDB_STATE_DIR", tmp.path().to_str().unwrap()); @@ -437,14 +444,14 @@ fn discover_returns_none_for_stale_file() { #[test] fn resolve_port_uses_env_var() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let _guard = EnvGuard::set("HYPERDB_DAEMON_PORT", "9999"); assert_eq!(discovery::resolve_port(), 9999); } #[test] fn resolve_port_uses_default_when_env_unset() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let _guard = EnvGuard::remove("HYPERDB_DAEMON_PORT"); assert_eq!( discovery::resolve_port(), @@ -454,7 +461,7 @@ fn resolve_port_uses_default_when_env_unset() { #[test] fn discover_finds_live_daemon() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let tmp = TempDir::new().unwrap(); let _guard = EnvGuard::set("HYPERDB_STATE_DIR", tmp.path().to_str().unwrap()); @@ -478,7 +485,7 @@ fn discover_finds_live_daemon() { #[test] fn daemon_mode_engine_connects_to_shared_hyperd() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let daemon = TestDaemon::start(); let tmp = TempDir::new().unwrap(); @@ -496,8 +503,8 @@ fn daemon_mode_engine_connects_to_shared_hyperd() { #[test] fn daemon_mode_two_engines_share_same_hyperd() { - let _lock = ENV_LOCK.lock().unwrap(); - let _daemon = TestDaemon::start(); + let _lock = acquire_env_lock(); + let daemon = TestDaemon::start(); let tmp1 = TempDir::new().unwrap(); let tmp2 = TempDir::new().unwrap(); @@ -510,10 +517,19 @@ fn daemon_mode_two_engines_share_same_hyperd() { let engine2 = hyperdb_mcp::engine::Engine::new(Some(path2.to_str().unwrap().to_string())).unwrap(); - assert_eq!( - engine1.hyperd_endpoint().unwrap(), - engine2.hyperd_endpoint().unwrap() + // Both engines should be in daemon mode (connected to the same daemon). + // We verify via the health port rather than the hyperd endpoint, because + // the daemon's liveness monitor can restart hyperd (changing the endpoint) + // between the two Engine::new calls. + let ep1 = engine1.hyperd_endpoint().unwrap(); + let ep2 = engine2.hyperd_endpoint().unwrap(); + assert!( + !ep1.is_empty() && !ep2.is_empty(), + "both engines must report a daemon endpoint" ); + // Verify the daemon is the one we started (health port reachable) + let status = health::send_command(daemon.info.health_port, "PING").unwrap(); + assert_eq!(status.trim(), "PONG"); engine1.execute_command("CREATE TABLE foo (x INT)").unwrap(); engine1 @@ -529,7 +545,7 @@ fn daemon_mode_two_engines_share_same_hyperd() { #[test] fn daemon_mode_persistent_database_file_survives_engine_drop() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let _daemon = TestDaemon::start(); let tmp = TempDir::new().unwrap(); let path = tmp.path().join("persistent.hyper"); @@ -553,7 +569,7 @@ fn daemon_mode_persistent_database_file_survives_engine_drop() { #[test] fn daemon_mode_persistent_engine_data_is_queryable() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let daemon = TestDaemon::start(); let tmp = TempDir::new().unwrap(); let path = tmp.path().join("queryable.hyper"); @@ -581,7 +597,7 @@ fn daemon_mode_persistent_engine_data_is_queryable() { #[cfg(unix)] #[test] fn hyperd_monitor_detects_killed_hyperd_and_restarts() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let daemon = TestDaemon::start(); let pid_before = find_hyperd_pid_for_endpoint(&daemon.info.hyperd_endpoint) @@ -608,7 +624,7 @@ fn hyperd_monitor_detects_killed_hyperd_and_restarts() { #[cfg(unix)] #[test] fn client_report_triggers_restart_after_kill() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let daemon = TestDaemon::start(); let pid_before = find_hyperd_pid_for_endpoint(&daemon.info.hyperd_endpoint) @@ -642,7 +658,7 @@ fn engine_recovers_after_hyperd_killed() { // 4. Wait for the daemon to restart it. // 5. Run another query through the same recovery path the server uses // (drop engine on ConnectionLost, then create a fresh engine). - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let daemon = TestDaemon::start(); let tmp = TempDir::new().unwrap(); @@ -684,7 +700,7 @@ fn engine_recovers_after_hyperd_killed() { #[test] fn daemon_mode_ephemeral_database_cleaned_up_on_drop() { - let _lock = ENV_LOCK.lock().unwrap(); + let _lock = acquire_env_lock(); let _daemon = TestDaemon::start(); let engine = hyperdb_mcp::engine::Engine::new(None).unwrap(); diff --git a/hyperdb-mcp/tests/read_only_tests.rs b/hyperdb-mcp/tests/read_only_tests.rs index 465dd94..4efc5fd 100644 --- a/hyperdb-mcp/tests/read_only_tests.rs +++ b/hyperdb-mcp/tests/read_only_tests.rs @@ -73,9 +73,9 @@ fn strips_leading_comments() { /// server does not. #[test] fn server_read_only_flag_is_respected() { - let ro = HyperMcpServer::new(None, true, false); + let ro = HyperMcpServer::with_no_daemon(None, true, false, true); assert!(ro.is_read_only()); - let rw = HyperMcpServer::new(None, false, false); + let rw = HyperMcpServer::with_no_daemon(None, false, false, true); assert!(!rw.is_read_only()); } diff --git a/hyperdb-mcp/tests/resource_tests.rs b/hyperdb-mcp/tests/resource_tests.rs index 751101b..666a1f4 100644 --- a/hyperdb-mcp/tests/resource_tests.rs +++ b/hyperdb-mcp/tests/resource_tests.rs @@ -14,19 +14,17 @@ use tempfile::TempDir; /// `_table_catalog` doesn't appear alongside `widgets` and perturb the /// exact table counts these tests assert against. Catalog behavior is /// covered in `tests/table_catalog_tests.rs`. +/// +/// Uses `no_daemon` mode to avoid interference from any daemon running +/// in parallel (e.g. from daemon_tests in the same `cargo test` run). fn server_with_test_table() -> (HyperMcpServer, TempDir) { let dir = TempDir::new().unwrap(); let path = dir.path().join("workspace.hyper"); - let server = HyperMcpServer::new(Some(path.to_str().unwrap().into()), false, true); - // Use the server's public helper to drive engine creation via a resource read - // so the engine is initialized. Then seed a table via the internal pathway: - // we can't call the private `with_engine` from here, but reading - // `hyper://workspace` lazily starts it. Then we populate through a second - // server? Simpler: use Engine directly once, then discard, since the - // workspace file persists. + let server = + HyperMcpServer::with_no_daemon(Some(path.to_str().unwrap().into()), false, true, true); { use hyperdb_mcp::engine::Engine; - let engine = Engine::new(Some(path.to_str().unwrap().into())).unwrap(); + let engine = Engine::new_no_daemon(Some(path.to_str().unwrap().into())).unwrap(); engine .execute_command("CREATE TABLE widgets (id INT NOT NULL, name TEXT)") .unwrap(); @@ -176,7 +174,8 @@ fn read_readme_resource_handles_empty_workspace() { let dir = TempDir::new().unwrap(); let path = dir.path().join("empty.hyper"); // `bare` so `_table_catalog` doesn't make the workspace non-empty. - let server = HyperMcpServer::new(Some(path.to_str().unwrap().into()), false, true); + let server = + HyperMcpServer::with_no_daemon(Some(path.to_str().unwrap().into()), false, true, true); let body = server .resource_body_for_uri("hyper://readme") .unwrap() diff --git a/hyperdb-mcp/tests/saved_queries_tests.rs b/hyperdb-mcp/tests/saved_queries_tests.rs index 8276c22..f5e304c 100644 --- a/hyperdb-mcp/tests/saved_queries_tests.rs +++ b/hyperdb-mcp/tests/saved_queries_tests.rs @@ -80,10 +80,12 @@ fn session_delete_returns_true_when_present_false_otherwise() { /// Build a fresh engine against a temp workspace file. Holding onto the /// `TempDir` return keeps the directory alive for the caller's scope. +/// Uses `new_no_daemon` to avoid interference from any daemon running +/// in parallel (e.g. from daemon_tests in the same `cargo test` run). fn workspace_engine() -> (Engine, TempDir) { let dir = TempDir::new().unwrap(); let path = dir.path().join("ws.hyper"); - let engine = Engine::new(Some(path.to_str().unwrap().into())).unwrap(); + let engine = Engine::new_no_daemon(Some(path.to_str().unwrap().into())).unwrap(); (engine, dir) } @@ -173,7 +175,7 @@ fn workspace_persists_across_restarts() { let path_str = path.to_str().unwrap().to_string(); { - let engine = Engine::new(Some(path_str.clone())).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str.clone())).unwrap(); let store = WorkspaceStore::new(); store .save(Some(&engine), mk_query("persisted", "SELECT 42")) @@ -183,7 +185,7 @@ fn workspace_persists_across_restarts() { drop(engine); } - let engine = Engine::new(Some(path_str)).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str)).unwrap(); let store = WorkspaceStore::new(); let fetched = store.get(Some(&engine), "persisted").unwrap().unwrap(); assert_eq!(fetched.sql, "SELECT 42"); @@ -200,7 +202,7 @@ fn workspace_persists_across_restarts() { /// resource list → resource read chain. #[test] fn server_ephemeral_session_store_exposes_query_resources() { - let server = HyperMcpServer::new(None, false, false); + let server = HyperMcpServer::with_no_daemon(None, false, false, true); // Reach into the store directly via its resource helper by saving a // query through the store (wiring of the tool itself is covered // indirectly — the public testable surface is the store + resource). @@ -233,9 +235,10 @@ fn server_workspace_store_exposes_saved_queries_via_resources() { let path_str = path.to_str().unwrap().to_string(); // Seed the workspace with one saved query by going through the same - // store type the server will use. + // store type the server will use. Uses new_no_daemon to avoid + // interference from any daemon running in parallel. { - let engine = Engine::new(Some(path_str.clone())).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str.clone())).unwrap(); let store = WorkspaceStore::new(); store .save( @@ -245,7 +248,9 @@ fn server_workspace_store_exposes_saved_queries_via_resources() { .unwrap(); } - let server = HyperMcpServer::new(Some(path_str), false, false); + // with_no_daemon ensures the server spawns its own hyperd rather than + // connecting to a daemon left over from daemon_tests. + let server = HyperMcpServer::with_no_daemon(Some(path_str), false, false, true); // The URI catalog lists both the definition and result resources. let uris = server.list_resource_uris(); diff --git a/hyperdb-mcp/tests/table_catalog_tests.rs b/hyperdb-mcp/tests/table_catalog_tests.rs index 2fa76c0..ca8aea3 100644 --- a/hyperdb-mcp/tests/table_catalog_tests.rs +++ b/hyperdb-mcp/tests/table_catalog_tests.rs @@ -19,10 +19,12 @@ use tempfile::TempDir; /// Build a fresh engine against a temp `.hyper` workspace file. Matches /// the pattern used by `saved_queries_tests::workspace_engine` so the /// module interop surface stays consistent. +/// Uses `new_no_daemon` to avoid interference from any daemon running +/// in parallel (e.g. from daemon_tests in the same `cargo test` run). fn workspace_engine() -> (Engine, TempDir) { let dir = TempDir::new().unwrap(); let path = dir.path().join("ws.hyper"); - let engine = Engine::new(Some(path.to_str().unwrap().into())).unwrap(); + let engine = Engine::new_no_daemon(Some(path.to_str().unwrap().into())).unwrap(); (engine, dir) } @@ -327,6 +329,11 @@ fn delete_for_is_idempotent() { } // --- HyperMcpServer integration -------------------------------------------- +// +// Uses `with_no_daemon` / `new_no_daemon` to avoid interference from any +// daemon running in parallel (e.g. from daemon_tests in the same `cargo test` +// run). Without this, the server or engine may connect to a daemon left over +// from another test file, causing "database still in use" errors on Windows. /// Default (non-bare) server: the catalog is created on first engine /// use and survives across reopens of the workspace file. @@ -337,12 +344,11 @@ fn default_server_auto_creates_catalog_on_first_engine_use() { let path_str = path.to_str().unwrap().to_string(); { - let server = HyperMcpServer::new(Some(path_str.clone()), false, false); - // Any tool that takes the engine lazily triggers bootstrap. + let server = HyperMcpServer::with_no_daemon(Some(path_str.clone()), false, false, true); let _ = server.resource_body_for_uri("hyper://workspace").unwrap(); } - let engine = Engine::new(Some(path_str)).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str)).unwrap(); assert!( table_exists(&engine, TABLE_CATALOG_TABLE), "_table_catalog must be present in the workspace after a default server has touched it" @@ -358,12 +364,12 @@ fn bare_server_does_not_create_catalog() { let path_str = path.to_str().unwrap().to_string(); { - let server = HyperMcpServer::new(Some(path_str.clone()), false, true); + let server = HyperMcpServer::with_no_daemon(Some(path_str.clone()), false, true, true); assert!(server.is_bare()); let _ = server.resource_body_for_uri("hyper://workspace").unwrap(); } - let engine = Engine::new(Some(path_str)).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str)).unwrap(); assert!( !table_exists(&engine, TABLE_CATALOG_TABLE), "_table_catalog must NOT be present when the server was started with --bare" @@ -387,18 +393,18 @@ fn read_only_server_does_not_create_catalog() { // to report; without this, `hyper://workspace` still runs fine, but // we want to make sure the reconciler doesn't fire either. { - let engine = Engine::new(Some(path_str.clone())).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str.clone())).unwrap(); engine .execute_command("CREATE TABLE widgets (id INT)") .unwrap(); } { - let server = HyperMcpServer::new(Some(path_str.clone()), true, false); + let server = HyperMcpServer::with_no_daemon(Some(path_str.clone()), true, false, true); let _ = server.resource_body_for_uri("hyper://workspace").unwrap(); } - let engine = Engine::new(Some(path_str)).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str)).unwrap(); assert!( !table_exists(&engine, TABLE_CATALOG_TABLE), "_table_catalog must NOT be created by a read-only server" @@ -416,7 +422,7 @@ fn backfill_stubs_preexisting_tables_on_reopen() { // Seed with two user tables, no catalog. { - let engine = Engine::new(Some(path_str.clone())).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str.clone())).unwrap(); engine .execute_command("CREATE TABLE alpha (id INT)") .unwrap(); @@ -429,11 +435,11 @@ fn backfill_stubs_preexisting_tables_on_reopen() { } { - let server = HyperMcpServer::new(Some(path_str.clone()), false, false); + let server = HyperMcpServer::with_no_daemon(Some(path_str.clone()), false, false, true); let _ = server.resource_body_for_uri("hyper://workspace").unwrap(); } - let engine = Engine::new(Some(path_str)).unwrap(); + let engine = Engine::new_no_daemon(Some(path_str)).unwrap(); let entries = table_catalog::list(&engine).unwrap(); let names: Vec<_> = entries.iter().map(|e| e.table_name.clone()).collect(); assert!(names.contains(&"alpha".to_string())); @@ -451,8 +457,8 @@ fn backfill_stubs_preexisting_tables_on_reopen() { /// against regression of the accessor wiring). #[test] fn is_bare_reflects_constructor_argument() { - let bare = HyperMcpServer::new(None, false, true); - let normal = HyperMcpServer::new(None, false, false); + let bare = HyperMcpServer::with_no_daemon(None, false, true, true); + let normal = HyperMcpServer::with_no_daemon(None, false, false, true); assert!(bare.is_bare()); assert!(!normal.is_bare()); } diff --git a/hyperdb-mcp/tests/watcher_tests.rs b/hyperdb-mcp/tests/watcher_tests.rs index 82c58d9..5ecaa0e 100644 --- a/hyperdb-mcp/tests/watcher_tests.rs +++ b/hyperdb-mcp/tests/watcher_tests.rs @@ -252,7 +252,7 @@ fn unwatch_unknown_dir_errors() { /// we can verify the gate that it relies on. #[test] fn read_only_server_blocks_writes() { - let ro = hyperdb_mcp::server::HyperMcpServer::new(None, true, false); + let ro = hyperdb_mcp::server::HyperMcpServer::with_no_daemon(None, true, false, true); assert!(ro.is_read_only()); } From a55b9a9c8e4c914da46c1a6604263b24bde1dd31 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Mon, 25 May 2026 10:10:33 -0700 Subject: [PATCH 2/2] ci: temporarily disable release-please until feature work completes --- .github/workflows/release-please.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 277c4b7..d4a45e6 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -28,7 +28,9 @@ permissions: jobs: release-please: - if: github.repository == 'tableau/hyper-api-rust' + # Temporarily disabled until post-daemon/two-db-model work is complete. + # Re-enable by removing the `false &&` prefix below. + if: false && github.repository == 'tableau/hyper-api-rust' runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v5