test(memory-core): release cached databases before removing fixture state dirs (#128310)

* test(memory-core): release cached state handles before removing fixture dirs

Six memory-core fixtures remove their temporary state directory while the
per-agent or shared SQLite handles opened underneath it are still cached, so
Windows fails the removal with EBUSY while Linux unlinks the open files and
stays green.

Release both handles before each removal, in the ordering merged for this
class: the agent close releases its leases through shared state and reopens
it, so the shared store has to be released after it.

* test(memory-core): release cached handles in the shared fixture harness and cli teardown
This commit is contained in:
Yiğit ERDOĞAN
2026-08-23 23:55:24 +03:00
committed by GitHub
parent d1177a7882
commit 3b1c8f229d
8 changed files with 45 additions and 1 deletions
+5
View File
@@ -5,6 +5,7 @@ import path from "node:path";
import { Command } from "commander";
import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import { resolveSessionTranscriptsDirForAgent as resolveTestSessionTranscriptsDirForAgent } from "openclaw/plugin-sdk/memory-core-host-runtime-core";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime";
import { resolveOpenClawAgentSqlitePath } from "openclaw/plugin-sdk/sqlite-runtime";
@@ -161,6 +162,10 @@ afterAll(async () => {
if (!fixtureRoot) {
return;
}
// The agent close releases its leases through shared state and reopens it, so the
// shared handle is released second; otherwise Windows fails the removal with EBUSY.
closeOpenClawAgentDatabasesForTest();
resetPluginStateStoreForTests();
await fs.rm(fixtureRoot, { recursive: true, force: true });
resetMemoryCoreDreamingStateForTests();
});
@@ -3,6 +3,7 @@ import os from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { getSessionEntry, upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
import {
appendSqliteSessionTranscriptEventForTest,
@@ -30,6 +31,7 @@ afterEach(async () => {
vi.useRealTimers();
vi.unstubAllEnvs();
vi.restoreAllMocks();
resetPluginStateStoreForTests();
await fs.rm(stateDir, { recursive: true, force: true });
});
@@ -19,6 +19,7 @@ import {
type MemorySyncParams,
type MemorySyncProgressUpdate,
} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import {
clearConfigCache,
clearRuntimeConfigSnapshot,
@@ -413,6 +414,10 @@ describe("session startup catch-up", () => {
}
startupHarnessDatabases.clear();
closeOpenClawAgentDatabasesForTest();
// Closing the agent databases releases their leases through shared state, which
// reopens it, so the shared handle has to be released after that and before the
// removal or Windows fails the unlink with EBUSY.
resetPluginStateStoreForTests();
await fs.rm(stateDir, { recursive: true, force: true });
});
@@ -4,7 +4,9 @@ import os from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { resolveOpenClawAgentSqlitePath } from "openclaw/plugin-sdk/sqlite-runtime";
import { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { closeAllMemorySearchManagers, getMemorySearchManager } from "./index.js";
import type { MemoryIndexMeta } from "./manager-reindex-state.js";
@@ -107,6 +109,10 @@ describe("memory manager FTS-only reindex", () => {
afterAll(async () => {
await closeAllMemorySearchManagers();
// The agent close releases its leases through shared state and reopens it, so the
// shared handle is released second; otherwise Windows fails the removal with EBUSY.
closeOpenClawAgentDatabasesForTest();
resetPluginStateStoreForTests();
if (fixtureRoot) {
await fs.rm(fixtureRoot, { recursive: true, force: true });
}
@@ -4,7 +4,9 @@ import os from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { resolveOpenClawAgentSqlitePath } from "openclaw/plugin-sdk/sqlite-runtime";
import { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resetEmbeddingMocks } from "./embedding.test-mocks.js";
import { acquireMemoryReindexLock } from "./manager-reindex-lock.js";
@@ -52,6 +54,10 @@ describe("memory manager reindex recovery", () => {
}
const { closeAllMemorySearchManagers } = await import("./index.js");
await closeAllMemorySearchManagers();
// The agent close releases its leases through shared state and reopens it, so the
// shared handle is released second; otherwise Windows fails the removal with EBUSY.
closeOpenClawAgentDatabasesForTest();
resetPluginStateStoreForTests();
await fs.rm(fixtureRoot, { recursive: true, force: true });
});
@@ -3,7 +3,9 @@ import os from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { resolveOpenClawAgentSqlitePath } from "openclaw/plugin-sdk/sqlite-runtime";
import { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { closeAllMemorySearchManagers, getMemorySearchManager } from "./index.js";
import type { MemoryIndexManager } from "./manager.js";
@@ -78,6 +80,10 @@ describe("memory manager self-heal missing identity with FTS-only chunks", () =>
afterAll(async () => {
await closeAllMemorySearchManagers();
// The agent close releases its leases through shared state and reopens it, so the
// shared handle is released second; otherwise Windows fails the removal with EBUSY.
closeOpenClawAgentDatabasesForTest();
resetPluginStateStoreForTests();
if (fixtureRoot) {
await fs.rm(fixtureRoot, { recursive: true, force: true });
}
@@ -7,6 +7,8 @@ import type {
MemorySearchConfig,
OpenClawConfig,
} from "openclaw/plugin-sdk/memory-core-host-engine-foundation";
import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
type WatchIgnoredFn = (watchPath: string, stats?: { isDirectory?: () => boolean }) => boolean;
@@ -208,6 +210,10 @@ describe("memory watcher config", () => {
await closeAllMemorySearchManagers();
clearRegistry();
restoreWatcherStateDir();
// The agent close releases its leases through shared state and reopens it, so the
// shared handle is released second; otherwise Windows fails the removal with EBUSY.
closeOpenClawAgentDatabasesForTest();
resetPluginStateStoreForTests();
if (workspaceDir) {
await fs.rm(workspaceDir, { recursive: true, force: true });
workspaceDir = "";
+9 -1
View File
@@ -2,7 +2,11 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { OpenKeyedStoreOptions } from "openclaw/plugin-sdk/plugin-state-runtime";
import { createPluginStateKeyedStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime";
import {
createPluginStateKeyedStoreForTests,
resetPluginStateStoreForTests,
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing";
import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import { afterAll, beforeAll } from "vitest";
@@ -210,6 +214,10 @@ export function createMemoryCoreTestHarness() {
if (!fixtureRoot) {
return;
}
// The agent close releases its leases through shared state and reopens it, so the
// shared handle is released second; otherwise Windows fails the removal with EBUSY.
closeOpenClawAgentDatabasesForTest();
resetPluginStateStoreForTests();
await fs.rm(fixtureRoot, { recursive: true, force: true });
resetMemoryCoreDreamingStateForTests();
});