mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
fix(test): preserve private temporary fixture ownership (#154759)
This commit is contained in:
parent
659b4e8c71
commit
33ecdbb346
3 changed files with 99 additions and 2 deletions
|
|
@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { execNodeEvalSync } from "../test-utils/node-process.js";
|
||||
import { withTempDirSync, withTestDir } from "./temp-dir.js";
|
||||
|
||||
const parentRoots: string[] = [];
|
||||
|
|
@ -74,3 +75,50 @@ describe("withTestDir", () => {
|
|||
await expect(fs.readdir(parentDir)).resolves.toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(process.platform === "win32")("private temporary case directories", () => {
|
||||
it.each(["async", "sync", "suite"])(
|
||||
"admits a private workspace under a group-writable umask (%s)",
|
||||
async (kind) => {
|
||||
const parentDir = await makeParentRoot();
|
||||
// umask is process-wide and cannot be changed in a Vitest worker thread.
|
||||
const stdout = execNodeEvalSync(
|
||||
`import fs from "node:fs";
|
||||
import { tempWorkspaceSync } from "@openclaw/fs-safe/temp";
|
||||
import { withTestDir, withTempDirSync, createSuiteTempRootTracker } from ${JSON.stringify(new URL("./temp-dir.ts", import.meta.url).href)};
|
||||
process.umask(0o002);
|
||||
const options = { prefix: "private-case-", parentDir: ${JSON.stringify(parentDir)} };
|
||||
function exercise(dir) {
|
||||
const workspace = tempWorkspaceSync({ rootDir: dir, prefix: "private-workspace-" });
|
||||
try {
|
||||
workspace.write("marker", "private");
|
||||
return workspace.read("marker").toString();
|
||||
} finally {
|
||||
workspace.cleanup();
|
||||
}
|
||||
}
|
||||
let result;
|
||||
if (${JSON.stringify(kind)} === "async") {
|
||||
result = await withTestDir({ ...options, subdir: "nested/leaf" }, async (dir) => exercise(dir));
|
||||
} else if (${JSON.stringify(kind)} === "sync") {
|
||||
result = withTempDirSync({ ...options, subdir: "nested/leaf" }, exercise);
|
||||
} else {
|
||||
const tracker = createSuiteTempRootTracker(options);
|
||||
await tracker.setup();
|
||||
try {
|
||||
result = exercise(await tracker.make("nested/case"));
|
||||
} finally {
|
||||
await tracker.cleanup();
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify({ result, remaining: fs.readdirSync(options.parentDir) }));`,
|
||||
{
|
||||
imports: [new URL("../../scripts/tsx.mjs", import.meta.url).href],
|
||||
timeout: 10_000,
|
||||
killSignal: "SIGKILL",
|
||||
},
|
||||
);
|
||||
expect(JSON.parse(stdout)).toEqual({ result: "private", remaining: [] });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ import { createDeferred } from "../../test/helpers/promise.js";
|
|||
import { runExclusiveSessionStoreWrite } from "../config/sessions/store-writer.js";
|
||||
import { resolveEffectiveHomeDir } from "../infra/home-dir.js";
|
||||
import { withTempHomeCore } from "../plugin-sdk/test-helpers/temp-home.js";
|
||||
import {
|
||||
captureOpenClawStateDatabaseReadAdmission,
|
||||
closeOpenClawStateDatabaseByPathAsync,
|
||||
registerOpenClawStateDatabaseAsyncResource,
|
||||
} from "../state/openclaw-state-db-cache.js";
|
||||
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
|
||||
import { captureEnv, captureFullEnv, withEnvAsync } from "./env.js";
|
||||
import { createTempHomeEnv } from "./temp-home.js";
|
||||
|
||||
|
|
@ -21,6 +27,46 @@ async function expectPathMissing(targetPath: string): Promise<void> {
|
|||
}
|
||||
|
||||
describe("createTempHomeEnv", () => {
|
||||
it("restores the environment and retains home after resource drainage fails", async () => {
|
||||
const envKeys = [
|
||||
"HOME",
|
||||
"USERPROFILE",
|
||||
"HOMEDRIVE",
|
||||
"HOMEPATH",
|
||||
"OPENCLAW_HOME",
|
||||
"OPENCLAW_STATE_DIR",
|
||||
];
|
||||
const environment = captureEnv(envKeys);
|
||||
const previous = Object.fromEntries(envKeys.map((key) => [key, process.env[key]]));
|
||||
const temporary = await createTempHomeEnv("openclaw-temp-home-drain-");
|
||||
const stateDir = path.join(temporary.home, ".openclaw");
|
||||
const databasePath = resolveOpenClawStateSqlitePath({ OPENCLAW_STATE_DIR: stateDir });
|
||||
const admission = captureOpenClawStateDatabaseReadAdmission(databasePath);
|
||||
const failure = new Error("fixture resource still owns its state");
|
||||
let rejectClose = true;
|
||||
const unregister = registerOpenClawStateDatabaseAsyncResource({
|
||||
close: async (identity) => {
|
||||
if (identity?.key === admission.identity.key && rejectClose) {
|
||||
throw failure;
|
||||
}
|
||||
},
|
||||
});
|
||||
const marker = path.join(temporary.home, "owned.txt");
|
||||
try {
|
||||
await fs.writeFile(marker, "retained fixture");
|
||||
await expect(temporary.restore()).rejects.toBe(failure);
|
||||
expect(Object.fromEntries(envKeys.map((key) => [key, process.env[key]]))).toEqual(previous);
|
||||
expect(await fs.readFile(marker, "utf8")).toBe("retained fixture");
|
||||
} finally {
|
||||
// Release the deliberately retained owner before disposing its fixture.
|
||||
rejectClose = false;
|
||||
await closeOpenClawStateDatabaseByPathAsync(databasePath);
|
||||
unregister();
|
||||
environment.restore();
|
||||
await fs.rm(temporary.home, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["directory", "environment"])(
|
||||
"rolls back failed %s acquisition without removing a sibling home",
|
||||
async (stage) => {
|
||||
|
|
|
|||
|
|
@ -77,8 +77,11 @@ export async function createTempHomeEnv(prefix: string): Promise<TempHomeEnv> {
|
|||
return {
|
||||
home,
|
||||
restore: async () => {
|
||||
await cleanupSessionStateForTest({ stateDir }).catch(() => undefined);
|
||||
snapshot.restore();
|
||||
try {
|
||||
await cleanupSessionStateForTest({ stateDir });
|
||||
} finally {
|
||||
snapshot.restore();
|
||||
}
|
||||
await fs.rm(home, { recursive: true, force: true });
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue