mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test: wait for complete shell fixture output (#1627)
Three daemon tests waited only for a shell fixture's output file to exist, then read it. A redirection or `writeFileSync` creates the file before writing it, so a read in between saw an empty file and failed intermittently. - **`tmux-agent-environment.test.ts`:** `waitFile` keeps its bound but returns only once the file ends in a newline, which all five writers now print last, and it throws on timeout. A fake-timer case checks that an empty or partial file doesn't resolve it. - **`claude-executable-foreground.test.ts`:** the recorder writes its JSON to a `.part` file and renames it into place. - **`transcript-seat-env-native.test.ts`:** the fixture writes its environment to a `.part` file and moves it into place. Test-only; no runtime file changes, and every payload assertion is unchanged.
This commit is contained in:
1 parent
a25cd1c341
commit
7296063795
3 files changed
+34
-8
No files matched your search
@@ -44,9 +44,10 @@ for (const shell of ["bash", "zsh", "dash", "ksh"]) {
|
||||
"const fs = require('node:fs');",
|
||||
"process.title = 'claude-fixture';",
|
||||
"process.on('SIGTERM', () => process.exit(0));",
|
||||
"fs.writeFileSync(process.env.MARKER, JSON.stringify({pid:process.pid,ppid:process.ppid,argv:process.argv.slice(2),",
|
||||
"fs.writeFileSync(process.env.MARKER + '.part', JSON.stringify({pid:process.pid,ppid:process.ppid,argv:process.argv.slice(2),",
|
||||
"node:process.env.OPENRIG_NODE_ID,home:process.env.OPENRIG_HOME,renderer:process.env.CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN,",
|
||||
"resume:process.env.OPENRIG_RESUME_LAUNCH,wrapper:process.env.FIXTURE_WRAPPER,config:process.env.CLAUDE_CONFIG_DIR}));",
|
||||
"fs.renameSync(process.env.MARKER + '.part', process.env.MARKER);",
|
||||
"setInterval(() => {}, 1000);",
|
||||
].join("\n"));
|
||||
fs.writeFileSync(fake, "#!/bin/sh\nexec " + quote(process.execPath) + " " + quote(recorder) + ' "$@"\n', { mode: 0o755 });
|
||||
|
||||
@@ -11,9 +11,34 @@ import { SeatLaunchEnvironment } from "../src/domain/seat-launch-environment.js"
|
||||
import { shellQuote } from "../src/adapters/shell-quote.js";
|
||||
const exec = promisify(execFile);
|
||||
const waitFile = async (file: string) => {
|
||||
for (let i=0; i<100 && !existsSync(file); i++) await new Promise(r=>setTimeout(r,25));
|
||||
return readFileSync(file,"utf8").trim().split("|");
|
||||
// Redirection creates the file before printf writes it; the final newline marks completion.
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const text = existsSync(file) ? readFileSync(file, "utf8") : "";
|
||||
if (text.endsWith("\n")) return text.trim().split("|");
|
||||
await new Promise(r => setTimeout(r, 25));
|
||||
}
|
||||
throw new Error(`Timed out waiting for complete environment result: ${file}`);
|
||||
};
|
||||
describe("environment result completion", () => {
|
||||
it("waits through an empty and partially written file", async () => {
|
||||
const home = mkdtempSync(path.join(tmpdir(), "tmux-result-"));
|
||||
const file = path.join(home, "result");
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
writeFileSync(file, "");
|
||||
let settled = false;
|
||||
const result = waitFile(file).then(value => { settled = true; return value; });
|
||||
await vi.advanceTimersByTimeAsync(25);
|
||||
expect(settled).toBe(false);
|
||||
writeFileSync(file, "first|");
|
||||
await vi.advanceTimersByTimeAsync(25);
|
||||
expect(settled).toBe(false);
|
||||
writeFileSync(file, "first|second\n");
|
||||
await vi.advanceTimersByTimeAsync(25);
|
||||
expect(await result).toEqual(["first", "second"]);
|
||||
} finally { vi.useRealTimers(); rmSync(home, { recursive: true, force: true }); }
|
||||
});
|
||||
});
|
||||
describe.skipIf(process.platform === "win32")("#1178 initial-shell environment policy (actual adapter)", () => {
|
||||
for (const clean of [false,true]) for (const successor of [false,true]) {
|
||||
it(`${clean ? "daemon baseline" : "stock inherited"}, ${successor ? "successor" : "fresh"}: shell startup and classic function remain usable`, async () => {
|
||||
@@ -25,7 +50,7 @@ describe.skipIf(process.platform === "win32")("#1178 initial-shell environment p
|
||||
const cli=path.join(home,"rig-fixture"), rc=path.join(home,"private.bashrc");
|
||||
writeFileSync(cli,"#!/bin/sh\nexit 0\n",{mode:0o755});
|
||||
// This deliberately custom default-command must still run; its function and override are intended setup.
|
||||
writeFileSync(rc, `unset HISTFILE\nprintf '%s|%s|%s|%s' "\${Q1178_SERVER-absent}" "\${Q1178_DAEMON-absent}" "\${Q1178_AUTH-absent}" "\${TMUX_PANE-absent}" > ${shellQuote(initial)}\nexport Q1178_STARTUP=startup Q1178_AUTH=startup\nclaude() { printf '%s|%s|%s|%s' "\${Q1178_SERVER-absent}" "\${Q1178_DAEMON-absent}" "$Q1178_AUTH" "$Q1178_STARTUP" > ${shellQuote(final)}; }\n`);
|
||||
writeFileSync(rc, `unset HISTFILE\nprintf '%s|%s|%s|%s\\n' "\${Q1178_SERVER-absent}" "\${Q1178_DAEMON-absent}" "\${Q1178_AUTH-absent}" "\${TMUX_PANE-absent}" > ${shellQuote(initial)}\nexport Q1178_STARTUP=startup Q1178_AUTH=startup\nclaude() { printf '%s|%s|%s|%s\\n' "\${Q1178_SERVER-absent}" "\${Q1178_DAEMON-absent}" "$Q1178_AUTH" "$Q1178_STARTUP" > ${shellQuote(final)}; }\n`);
|
||||
const custom=`exec /bin/bash --noprofile --rcfile ${shellQuote(rc)} -i`;
|
||||
const selected={HOME:home,PATH:process.env.PATH!,OPENRIG_HOME:home,OPENRIG_NODE_ID:"node",OPENRIG_SESSION_NAME:"seat",Q1178_AUTH:"daemon"};
|
||||
const baseline={...selected,Q1178_DAEMON:"daemon"};
|
||||
@@ -72,11 +97,11 @@ describe.skipIf(process.platform === "win32")("#1178 initial-shell environment p
|
||||
expect((await adapter.createSession("seat",home,{OPENRIG_NODE_ID:"node",OPENRIG_SESSION_NAME:"seat"})).ok).toBe(true);
|
||||
const pane=await tmux(["display-message","-p","-t","=seat:","#{pane_id}"]);
|
||||
const inspected=path.join(home,"shell-environment");
|
||||
await tmux(["send-keys","-t","=seat:","-l",`printf '%s|%s|%s' "$Q1178_DAEMON" "\${Q1178_SERVER-absent}" "$TMUX_PANE" > ${shellQuote(inspected)}`]);
|
||||
await tmux(["send-keys","-t","=seat:","-l",`printf '%s|%s|%s\\n' "$Q1178_DAEMON" "\${Q1178_SERVER-absent}" "$TMUX_PANE" > ${shellQuote(inspected)}`]);
|
||||
await tmux(["send-keys","-t","=seat:","Enter"]);
|
||||
expect(await waitFile(inspected)).toEqual(["daemon","absent",pane]);
|
||||
const mode=path.join(home,"login-mode");
|
||||
await tmux(["send-keys","-t","=seat:","-l",`if shopt -q login_shell; then printf login > ${shellQuote(mode)}; else printf nonlogin > ${shellQuote(mode)}; fi`]);
|
||||
await tmux(["send-keys","-t","=seat:","-l",`if shopt -q login_shell; then printf 'login\\n' > ${shellQuote(mode)}; else printf 'nonlogin\\n' > ${shellQuote(mode)}; fi`]);
|
||||
await tmux(["send-keys","-t","=seat:","Enter"]);
|
||||
expect(await waitFile(mode)).toEqual(["login"]);
|
||||
} finally { await tmux(["kill-server"]).catch(()=>{}); rmSync(home,{recursive:true,force:true,maxRetries:5,retryDelay:20}); }
|
||||
@@ -106,7 +131,7 @@ describe.skipIf(process.platform === "win32")("#1178 initial-shell environment p
|
||||
await tmux(["set-option","-g","update-environment","SSH_AUTH_SOCK SSH_ASKPASS Q1178_CUSTOM_UPDATED"]);
|
||||
await tmux(["set-option","-g","default-shell","/bin/bash"]);
|
||||
const script=path.join(home,"observe.sh");
|
||||
writeFileSync(script,`export HISTFILE=/dev/null\nprintf '%s|%s|%s' "$SSH_AUTH_SOCK" "\${SSH_ASKPASS-absent}" "$Q1178_CUSTOM_UPDATED" > ${shellQuote(observed)}\nexec /bin/bash --noprofile --norc\n`);
|
||||
writeFileSync(script,`export HISTFILE=/dev/null\nprintf '%s|%s|%s\\n' "$SSH_AUTH_SOCK" "\${SSH_ASKPASS-absent}" "$Q1178_CUSTOM_UPDATED" > ${shellQuote(observed)}\nexec /bin/bash --noprofile --norc\n`);
|
||||
await tmux(["set-option","-g","default-command",`exec /bin/bash ${shellQuote(script)}`]);
|
||||
expect((await adapter.createSession("seat",home,{OPENRIG_NODE_ID:"owned-node"})).ok).toBe(true);
|
||||
expect(await waitFile(observed)).toEqual(["/synthetic/daemon-agent.sock","absent","daemon-custom"]);
|
||||
|
||||
@@ -28,7 +28,7 @@ describe.skipIf(process.platform === "win32")("native transcript seat environmen
|
||||
const adapter = new TmuxAdapter(async (cmd) => (await run(`tmux -S ${shellQuote(socket)} ${cmd.slice(5)}`, { env })).stdout,
|
||||
undefined, argv ? async (args) => (await runFile(args[0]!, ["-S", socket, ...args.slice(1)], { env })).stdout : undefined);
|
||||
expect(await adapter.createSession("fixture", temp)).toEqual({ ok: true });
|
||||
await runFile("tmux", ["-S", socket, "respawn-pane", "-k", "-t", "=fixture:", `/usr/bin/env > ${shellQuote(captured)}; sleep 60`], { env });
|
||||
await runFile("tmux", ["-S", socket, "respawn-pane", "-k", "-t", "=fixture:", `/usr/bin/env > ${shellQuote(captured + ".part")} && mv ${shellQuote(captured + ".part")} ${shellQuote(captured)}; sleep 60`], { env });
|
||||
const deadline = Date.now() + 2000;
|
||||
while (!fs.existsSync(captured) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
const seatEnv = Object.fromEntries(fs.readFileSync(captured, "utf8").split("\n").filter((line) => line.includes("=")).map((line) => {
|
||||
|
||||
Reference in new issue
Block a user