fix(memory): isolate test workspace cleanup

This commit is contained in:
Ralph Chang
2026-04-28 14:50:30 +08:00
parent 8e07bfe3c1
commit c0a083ddaf
14 changed files with 692 additions and 70 deletions
+21
View File
@@ -0,0 +1,21 @@
import { after } from "node:test";
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
const previousXdgDataHome = process.env.XDG_DATA_HOME;
const previousTestFlag = process.env.OPENCODE_WORKING_MEMORY_TEST;
const testDataHome = await mkdtemp(join(tmpdir(), "opencode-working-memory-test-xdg-"));
process.env.XDG_DATA_HOME = testDataHome;
process.env.OPENCODE_WORKING_MEMORY_TEST = "1";
after(async () => {
if (previousXdgDataHome === undefined) delete process.env.XDG_DATA_HOME;
else process.env.XDG_DATA_HOME = previousXdgDataHome;
if (previousTestFlag === undefined) delete process.env.OPENCODE_WORKING_MEMORY_TEST;
else process.env.OPENCODE_WORKING_MEMORY_TEST = previousTestFlag;
await rm(testDataHome, { recursive: true, force: true });
});
+171
View File
@@ -0,0 +1,171 @@
import test from "node:test";
import assert from "node:assert/strict";
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
classifyWorkspaceDir,
cleanupWorkspaceResidues,
scanWorkspaceResidues,
} from "../src/workspace-cleanup.ts";
async function writeWorkspaceStore(dataHome: string, key: string, root: string): Promise<string> {
const workspaceDir = join(dataHome, "opencode-working-memory", "workspaces", key);
await mkdir(workspaceDir, { recursive: true });
await writeFile(
join(workspaceDir, "workspace-memory.json"),
JSON.stringify({
version: 1,
workspace: { root, key },
limits: { maxRenderedChars: 5200, maxEntries: 28 },
entries: [],
updatedAt: "2026-04-28T00:00:00.000Z",
}, null, 2),
"utf8",
);
return workspaceDir;
}
test("workspace cleanup classifies missing temp test roots as definite residue", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
try {
const missingTempRoot = join(tmpdir(), "memory-plugin-test-missing-root");
await rm(missingTempRoot, { recursive: true, force: true });
const workspaceDir = await writeWorkspaceStore(dataHome, "definite", missingTempRoot);
const result = await classifyWorkspaceDir(workspaceDir);
assert.equal(result.classification, "test_temp_definite");
assert.equal(result.rootExists, false);
assert.ok(result.reasons.includes("root_missing"));
assert.ok(result.reasons.some(reason => reason.startsWith("root_under_temp")));
assert.ok(result.reasons.includes("test_prefix_memory-plugin-test"));
} finally {
await rm(dataHome, { recursive: true, force: true });
}
});
test("workspace cleanup keeps existing temp roots live", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
const liveRoot = await mkdtemp(join(tmpdir(), "wm-quality-live-root-"));
try {
const workspaceDir = await writeWorkspaceStore(dataHome, "live", liveRoot);
const result = await classifyWorkspaceDir(workspaceDir);
assert.equal(result.classification, "live_or_existing");
assert.equal(result.rootExists, true);
} finally {
await rm(dataHome, { recursive: true, force: true });
await rm(liveRoot, { recursive: true, force: true });
}
});
test("workspace cleanup reports missing non-temp roots as unknown orphans", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
try {
const missingNonTempRoot = `/definitely-not-temp-opencode-working-memory-test-${Date.now()}`;
const workspaceDir = await writeWorkspaceStore(dataHome, "orphan", missingNonTempRoot);
const result = await classifyWorkspaceDir(workspaceDir);
assert.equal(result.classification, "orphan_unknown");
assert.equal(result.rootExists, false);
assert.ok(result.reasons.includes("root_missing"));
} finally {
await rm(dataHome, { recursive: true, force: true });
}
});
test("workspace cleanup reports invalid stores without moving them", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
try {
const workspaceDir = join(dataHome, "opencode-working-memory", "workspaces", "invalid");
await mkdir(workspaceDir, { recursive: true });
await writeFile(join(workspaceDir, "workspace-memory.json"), "{ invalid", "utf8");
const result = await classifyWorkspaceDir(workspaceDir);
assert.equal(result.classification, "invalid_or_unreadable");
assert.ok(result.reasons.includes("invalid_json"));
const cleanup = await cleanupWorkspaceResidues({ dataHome, mode: "quarantine" });
assert.equal(cleanup.quarantined.length, 0);
assert.equal(existsSync(workspaceDir), true);
} finally {
await rm(dataHome, { recursive: true, force: true });
}
});
test("workspace cleanup dry-run scans definite residue without moving it", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
try {
const missingTempRoot = join(tmpdir(), "wm-accounting-missing-root");
await rm(missingTempRoot, { recursive: true, force: true });
const workspaceDir = await writeWorkspaceStore(dataHome, "dryrun", missingTempRoot);
const result = await cleanupWorkspaceResidues({ dataHome, minAgeMs: 0 });
assert.equal(result.mode, "dry-run");
assert.equal(result.candidates.length, 1);
assert.equal(result.quarantined.length, 0);
assert.equal(existsSync(workspaceDir), true);
} finally {
await rm(dataHome, { recursive: true, force: true });
}
});
test("workspace cleanup quarantine moves definite residue and writes manifest", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
try {
const missingTempRoot = join(tmpdir(), "wm-redact-missing-root");
await rm(missingTempRoot, { recursive: true, force: true });
const definiteDir = await writeWorkspaceStore(dataHome, "definite", missingTempRoot);
const orphanDir = await writeWorkspaceStore(dataHome, "orphan", `/definitely-not-temp-opencode-working-memory-test-${Date.now()}`);
const result = await cleanupWorkspaceResidues({
dataHome,
mode: "quarantine",
minAgeMs: 0,
now: new Date("2026-04-28T12:00:00.000Z"),
});
assert.equal(result.quarantined.length, 1);
assert.equal(result.quarantined[0]?.workspaceKey, "definite");
assert.equal(existsSync(definiteDir), false);
assert.equal(existsSync(orphanDir), true);
assert.ok(result.quarantineDir);
assert.equal(existsSync(join(result.quarantineDir!, "workspaces", "definite", "workspace-memory.json")), true);
const manifest = await readFile(join(result.quarantineDir!, "manifest.jsonl"), "utf8");
const event = JSON.parse(manifest.trim());
assert.equal(event.workspaceKey, "definite");
assert.equal(event.classification, "test_temp_definite");
assert.equal(event.root, missingTempRoot);
} finally {
await rm(dataHome, { recursive: true, force: true });
}
});
test("workspace cleanup skips recently updated definite residue", async () => {
const dataHome = await mkdtemp(join(tmpdir(), "wm-cleanup-data-"));
try {
const missingTempRoot = join(tmpdir(), "wm-extraction-missing-root");
await rm(missingTempRoot, { recursive: true, force: true });
const workspaceDir = await writeWorkspaceStore(dataHome, "recent", missingTempRoot);
const stats = await stat(workspaceDir);
const result = await scanWorkspaceResidues({
dataHome,
nowMs: stats.mtimeMs + 1_000,
minAgeMs: 10 * 60 * 1_000,
});
assert.equal(result.candidates.length, 0);
assert.equal(result.results.find(item => item.workspaceKey === "recent")?.classification, "test_temp_definite");
assert.ok(result.results.find(item => item.workspaceKey === "recent")?.reasons.includes("recent_workspace_dir"));
} finally {
await rm(dataHome, { recursive: true, force: true });
}
});
+1 -1
View File
@@ -14,13 +14,13 @@ import {
normalizeWorkspaceMemoryWithAccounting,
workspaceMemoryExactKey,
workspaceMemoryIdentityKey,
redactCredentials,
runMigrationP0Cleanup,
runMigrationQualityCleanup,
loadWorkspaceMemory,
saveWorkspaceMemory,
updateWorkspaceMemoryWithAccounting,
} from "../src/workspace-memory.ts";
import { redactCredentials } from "../src/redaction.ts";
import { assessMemoryQuality, isHardQualityReason, isProgressSnapshotViolation } from "../src/memory-quality.ts";
import { reviewerCurrent28Fixture } from "./fixtures/memory-quality-current-28.ts";
import { REAL_WORKSPACE_FIXTURES } from "./fixtures/real-workspaces-snapshot.ts";