-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathstate.test.ts
More file actions
99 lines (82 loc) · 3.46 KB
/
Copy pathstate.test.ts
File metadata and controls
99 lines (82 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { afterAll, afterEach, beforeEach, expect, mock, test } from "bun:test";
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
// Bun mutates the imported namespace object in place when a module is
// mocked, so the capture is shallow-copied immediately -- holding onto the
// live namespace would turn into the mocked exports as soon as mock.module
// below runs, making a later restore a no-op.
const realFs = { ...(await import("node:fs/promises")) };
// Simulates the straggler write's real await point (e.g. cycleRecorder.dispose
// during the terminal path) landing its writeFile after a later-issued
// terminal write's writeFile, so rename-order alone would let it win.
const realWriteFile = realFs.writeFile;
let delayNextWrite = false;
mock.module("node:fs/promises", () => ({
...realFs,
writeFile: async (path: string, data: string) => {
if (delayNextWrite) {
delayNextWrite = false;
await new Promise((resolve) => setTimeout(resolve, 30));
}
return realWriteFile(path, data);
},
}));
afterAll(() => {
mock.module("node:fs/promises", () => realFs);
});
const { finalizeRunState, loadState, saveState } = await import("./state.js");
const { getActiveRun, setActiveRun } = await import("./active-run.js");
type RunState = Awaited<ReturnType<typeof loadState>>;
let cwd = "";
let home = "";
beforeEach(async () => {
const stamp = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
cwd = join(tmpdir(), `corbits-state-${stamp}`);
home = join(tmpdir(), `corbits-state-home-${stamp}`);
await mkdir(cwd, { recursive: true });
await mkdir(home, { recursive: true });
});
afterEach(async () => {
await rm(cwd, { recursive: true, force: true });
await rm(home, { recursive: true, force: true });
});
function state(overrides: Partial<NonNullable<RunState>>): NonNullable<RunState> {
return {
status: "running",
turnsUsed: 0,
task: "task",
startedAt: 1,
...overrides,
};
}
test("a straggler snapshot started before a terminal write does not overwrite it", async () => {
const sessionId = "sess-race";
delayNextWrite = true;
const straggler = saveState(cwd, sessionId, state({ status: "running" }), home);
const terminal = saveState(cwd, sessionId, state({ status: "done", finishedAt: 999 }), home);
await Promise.all([straggler, terminal]);
const final = await loadState(cwd, sessionId, home);
expect(final?.status).toBe("done");
expect(final?.finishedAt).toBe(999);
});
test("a persisted terminal status agrees with the active-run handle without a second call site", async () => {
const sessionId = "sess-terminal";
setActiveRun({ sessionId, cwd, task: "task", startedAt: 1 });
await finalizeRunState(cwd, sessionId, state({ status: "done", finishedAt: 1000 }), home);
const persisted = await loadState(cwd, sessionId, home);
expect(persisted?.status).toBe("done");
// The only liveness representation left is presence in the active-run
// slot -- a terminal RunState.status must leave nothing there to read.
expect(getActiveRun()).toBeNull();
});
test("saveState calls for different sessions do not block each other", async () => {
await Promise.all([
saveState(cwd, "session-a", state({ task: "a" }), home),
saveState(cwd, "session-b", state({ task: "b" }), home),
]);
const a = await loadState(cwd, "session-a", home);
const b = await loadState(cwd, "session-b", home);
expect(a?.task).toBe("a");
expect(b?.task).toBe("b");
});