Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion backend/cli/test/global/data-dir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ async function root() {
return value
}

function rewriteRacingFile(filepath: string, content: string) {
try {
fsSync.writeFileSync(filepath, content)
return true
} catch (error) {
const code = (error as NodeJS.ErrnoException).code
// This fixture deliberately writes while migration reads the same file.
// Windows may transiently deny that competing open; skipping one timer
// tick preserves the race without turning a sharing violation into an
// unhandled test-runner error and cleanup cascade.
if (process.platform === "win32" && (code === "EBUSY" || code === "EACCES" || code === "EPERM")) return false
throw error
}
}

async function artifact(root: string, key: string) {
const dir = path.join(root, "artifact-store")
await fs.mkdir(path.join(dir, "blobs"), { recursive: true })
Expand Down Expand Up @@ -245,10 +260,14 @@ describe("OpenScience data directory", () => {
// verification. Only that file may be dropped.
const torn = path.join(legacy, "storage", "session", "ses_b.json")
const original = fsSync.readFileSync(torn)
const watcher = setInterval(() => fsSync.writeFileSync(torn, `{"id":"b","n":${Math.random()}}`), 1)
let rewrites = 0
const watcher = setInterval(() => {
if (rewriteRacingFile(torn, `{"id":"b","n":${Math.random()}}`)) rewrites++
}, 1)
const result = await resolveDataDirectory({ home, legacy }).finally(() => clearInterval(watcher))

expect(result.path).toBe(target)
expect(rewrites).toBeGreaterThan(0)
expect(await fs.readFile(path.join(target, "openscience-session.json"), "utf8")).toContain("kept")
expect(JSON.parse(await fs.readFile(path.join(target, "auth.json"), "utf8"))["openai-codex"].access).toBe("kept")
for (const id of ["a", "c"])
Expand Down
21 changes: 13 additions & 8 deletions backend/cli/test/project/authority-process-ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,19 @@ async function scenario(kind: "pty" | "biology", action: "trust" | "filesystem"

owner.kill("SIGKILL")
await owner.exited
// Both fixtures ignore terminal hangup so the independently sandboxed
// leader and its escaped descendant genuinely outlive the killed server.
await Bun.sleep(100)
const survivedOwner = await AuthorityProcessLedger.owns(entry.pid, entry.identity)
// macOS responsibility supervision and Linux bubblewrap's parent-death PID
// namespace both tear down immediately. The durable ledger remains so a
// fresh server can verify the dead tree and clear ownership atomically.
expect(survivedOwner).toBe(process.platform !== "darwin" && !(process.platform === "linux" && entry.sandboxed))
// Both fixtures ignore terminal hangup, so any death below must come from
// the platform containment rather than directly from the killed server.
const contained = process.platform === "darwin" || (process.platform === "linux" && entry.sandboxed)
if (contained) {
// The macOS responsibility supervisor and Linux bubblewrap namespace
// observe owner death asynchronously. Prove their causal teardown to a
// bounded deadline instead of treating 100 ms as a lifecycle contract.
expect(await gone(entry)).toBe(true)
expect(await gone(entry.descendant)).toBe(true)
} else {
await Bun.sleep(100)
expect(await AuthorityProcessLedger.owns(entry.pid, entry.identity)).toBe(true)
}

await run(
root,
Expand Down