From 40c88ebefdcef206578533299769705ab23d795d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fit=20ERDO=C4=9EAN?= Date: Sun, 23 Aug 2026 23:36:49 +0300 Subject: [PATCH] test(qa-lab): release cached databases before removing fixture temp dirs (#128335) Five qa-lab fixtures point a state directory at a temporary workspace and then remove it while the shared and per-agent SQLite handles opened underneath are still cached, so Windows fails the removal with EBUSY while Linux unlinks the open files and stays green. Release both 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 is released second. --- .../src/runtime-parity-cache-diagnostics.test.ts | 11 ++++++++++- .../src/runtime-parity-session-selection.test.ts | 7 +++++++ extensions/qa-lab/src/runtime-parity.test.ts | 7 +++++++ extensions/qa-lab/src/runtime-tool-fixture.test.ts | 6 ++++++ .../qa-lab/src/suite-runtime-agent-session.test.ts | 11 ++++++++++- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/extensions/qa-lab/src/runtime-parity-cache-diagnostics.test.ts b/extensions/qa-lab/src/runtime-parity-cache-diagnostics.test.ts index c3092eed9511..97c72be0ff2d 100644 --- a/extensions/qa-lab/src/runtime-parity-cache-diagnostics.test.ts +++ b/extensions/qa-lab/src/runtime-parity-cache-diagnostics.test.ts @@ -1,7 +1,11 @@ import path from "node:path"; +import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime"; import { resolveStorePath, upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime"; import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime"; -import { formatSqliteSessionFileMarker } from "openclaw/plugin-sdk/sqlite-runtime-testing"; +import { + closeOpenClawAgentDatabasesForTest, + formatSqliteSessionFileMarker, +} from "openclaw/plugin-sdk/sqlite-runtime-testing"; import { afterEach, describe, expect, it } from "vitest"; import { buildRuntimeParityCacheDiagnostics } from "./runtime-parity-cache-diagnostics.js"; import { captureRuntimeParityCell, type RuntimeParityUsage } from "./runtime-parity.js"; @@ -10,6 +14,11 @@ import { createTempDirHarness } from "./temp-dir.test-helper.js"; const tempDirs = createTempDirHarness(); afterEach(async () => { + // Fixtures point a state dir at these temp workspaces, so the shared and per-agent + // SQLite handles stay cached and Windows fails the removal with EBUSY. The agent close + // releases its leases through shared state and reopens it, so the store is released second. + closeOpenClawAgentDatabasesForTest(); + resetPluginStateStoreForTests(); await tempDirs.cleanup(); }); diff --git a/extensions/qa-lab/src/runtime-parity-session-selection.test.ts b/extensions/qa-lab/src/runtime-parity-session-selection.test.ts index 833e5a45d037..92b6e85aaae1 100644 --- a/extensions/qa-lab/src/runtime-parity-session-selection.test.ts +++ b/extensions/qa-lab/src/runtime-parity-session-selection.test.ts @@ -1,8 +1,10 @@ import path from "node:path"; +import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime"; import { resolveStorePath, upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime"; import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime"; import { appendSqliteTrajectoryRuntimeEvents, + closeOpenClawAgentDatabasesForTest, formatSqliteSessionFileMarker, } from "openclaw/plugin-sdk/sqlite-runtime-testing"; import { afterEach, describe, expect, it } from "vitest"; @@ -12,6 +14,11 @@ import { createTempDirHarness } from "./temp-dir.test-helper.js"; const tempDirs = createTempDirHarness(); afterEach(async () => { + // Fixtures point a state dir at these temp workspaces, so the shared and per-agent + // SQLite handles stay cached and Windows fails the removal with EBUSY. The agent close + // releases its leases through shared state and reopens it, so the store is released second. + closeOpenClawAgentDatabasesForTest(); + resetPluginStateStoreForTests(); await tempDirs.cleanup(); }); diff --git a/extensions/qa-lab/src/runtime-parity.test.ts b/extensions/qa-lab/src/runtime-parity.test.ts index f8257d0d61f3..3ae9c198e132 100644 --- a/extensions/qa-lab/src/runtime-parity.test.ts +++ b/extensions/qa-lab/src/runtime-parity.test.ts @@ -2,10 +2,12 @@ import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; import path from "node:path"; +import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime"; import { resolveStorePath, upsertSessionEntry } from "openclaw/plugin-sdk/session-store-runtime"; import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime"; import { appendSqliteTrajectoryRuntimeEvents, + closeOpenClawAgentDatabasesForTest, formatSqliteSessionFileMarker, } from "openclaw/plugin-sdk/sqlite-runtime-testing"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -25,6 +27,11 @@ const tempDirs = createTempDirHarness(); afterEach(async () => { vi.unstubAllGlobals(); + // Fixtures point a state dir at these temp workspaces, so the shared and per-agent + // SQLite handles stay cached and Windows fails the removal with EBUSY. The agent close + // releases its leases through shared state and reopens it, so the store is released second. + closeOpenClawAgentDatabasesForTest(); + resetPluginStateStoreForTests(); await tempDirs.cleanup(); }); diff --git a/extensions/qa-lab/src/runtime-tool-fixture.test.ts b/extensions/qa-lab/src/runtime-tool-fixture.test.ts index 4a8c1ad9502e..fc32e153569a 100644 --- a/extensions/qa-lab/src/runtime-tool-fixture.test.ts +++ b/extensions/qa-lab/src/runtime-tool-fixture.test.ts @@ -2,8 +2,10 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +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 { closeOpenClawAgentDatabasesForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing"; import { afterEach, describe, expect, it, vi } from "vitest"; import { QaSuiteInfraError } from "./errors.js"; import { runRuntimeToolFixture } from "./runtime-tool-fixture.js"; @@ -437,6 +439,10 @@ async function runMockRuntimeToolFixtureWithOutputs(params: { } afterEach(async () => { + // The session store keeps the state database open under the temporary root, so + // Windows fails the removal with EBUSY unless the cached handle is released first. + closeOpenClawAgentDatabasesForTest(); + resetPluginStateStoreForTests(); await Promise.all( tempRoots.splice(0).map((tempRoot) => fs.rm(tempRoot, { recursive: true, force: true })), ); diff --git a/extensions/qa-lab/src/suite-runtime-agent-session.test.ts b/extensions/qa-lab/src/suite-runtime-agent-session.test.ts index 04d7740541ac..dd75bc7b1380 100644 --- a/extensions/qa-lab/src/suite-runtime-agent-session.test.ts +++ b/extensions/qa-lab/src/suite-runtime-agent-session.test.ts @@ -1,12 +1,16 @@ // Qa Lab tests cover suite runtime agent session plugin behavior. import fs from "node:fs/promises"; import path from "node:path"; +import { resetPluginStateStoreForTests } from "openclaw/plugin-sdk/plugin-state-test-runtime"; import { loadTranscriptEventsSync, upsertSessionEntry, } from "openclaw/plugin-sdk/session-store-runtime"; import { appendSessionTranscriptMessageByIdentity } from "openclaw/plugin-sdk/session-transcript-runtime"; -import { appendSqliteSessionTranscriptEventForTest } from "openclaw/plugin-sdk/sqlite-runtime-testing"; +import { + closeOpenClawAgentDatabasesForTest, + appendSqliteSessionTranscriptEventForTest, +} from "openclaw/plugin-sdk/sqlite-runtime-testing"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createSession, @@ -22,6 +26,11 @@ const { cleanup, makeTempDir } = createTempDirHarness(); afterEach(async () => { vi.useRealTimers(); + // Fixtures point a state dir at these temp workspaces, so the shared and per-agent + // SQLite handles stay cached and Windows fails the removal with EBUSY. The agent close + // releases its leases through shared state and reopens it, so the store is released second. + closeOpenClawAgentDatabasesForTest(); + resetPluginStateStoreForTests(); await cleanup(); });