test: remove more avoidable waits (#105816)

This commit is contained in:
Peter Steinberger
2026-07-12 18:24:13 -07:00
committed by GitHub
parent f1a64741b5
commit a63aec8bf8
3 changed files with 95 additions and 34 deletions

View File

@@ -22,17 +22,30 @@ function isProcessRunning(pid: number): boolean {
}
}
async function waitForFile(pathToCheck: string, timeoutMs: number): Promise<void> {
async function waitForPidFile(pathToCheck: string, timeoutMs: number): Promise<number> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try {
await readFile(pathToCheck, "utf8");
return;
} catch {
await sleep(25);
}
const value = (await readFile(pathToCheck, "utf8")).trim();
const pid = Number(value);
if (/^[1-9]\d*$/u.test(value) && Number.isSafeInteger(pid)) {
return pid;
}
} catch {}
await sleep(25);
}
throw new Error(`Timed out waiting for ${pathToCheck}`);
throw new Error(`Timed out waiting for a PID in ${pathToCheck}`);
}
async function waitForProcessExit(pid: number, timeoutMs: number): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (!isProcessRunning(pid)) {
return;
}
await sleep(25);
}
throw new Error(`Timed out waiting for process ${pid} to exit`);
}
describe("Matrix QA CLI runtime", () => {
@@ -235,15 +248,9 @@ describe("Matrix QA CLI runtime", () => {
env: process.env,
timeoutMs: 250,
}),
).rejects.toThrow(/stdout:\nwaiting for verification/);
await expect(
runMatrixQaOpenClawCli({
args: ["matrix", "verify", "self"],
cwd: root,
env: process.env,
timeoutMs: 250,
}),
).rejects.toThrow(/stderr:\nmatrix sdk still syncing/);
).rejects.toThrow(
/stderr:\nmatrix sdk still syncing[\s\S]*stdout:\nwaiting for verification/u,
);
} finally {
await rm(root, { force: true, recursive: true });
}
@@ -312,12 +319,12 @@ describe("Matrix QA CLI runtime", () => {
env: process.env,
timeoutMs: 500,
});
await sleep(850);
childPid = await waitForPidFile(pidPath, 2_000);
await waitForProcessExit(childPid, 2_000);
await expect(session.wait()).rejects.toThrow(/timed out after 500ms/u);
await expect(session.wait()).rejects.toThrow(/late wait timeout marker/u);
childPid = Number(await readFile(pidPath, "utf8"));
expect(isProcessRunning(childPid)).toBe(false);
} finally {
if (childPid && isProcessRunning(childPid)) {
@@ -409,12 +416,11 @@ describe("Matrix QA CLI runtime", () => {
env: process.env,
timeoutMs: 500,
});
await waitForFile(grandchildPidPath, 2_000);
grandchildPid = await waitForPidFile(grandchildPidPath, 2_000);
await expect(run).rejects.toThrow(/timed out after 500ms/u);
childPid = Number(await readFile(childPidPath, "utf8"));
grandchildPid = Number(await readFile(grandchildPidPath, "utf8"));
childPid = await waitForPidFile(childPidPath, 2_000);
expect(isProcessRunning(childPid)).toBe(false);
expect(isProcessRunning(grandchildPid)).toBe(false);
} finally {
@@ -460,14 +466,17 @@ describe("Matrix QA CLI runtime", () => {
env: process.env,
timeoutMs: 10_000,
});
await waitForFile(grandchildPidPath, 2_000);
await sleep(300);
childPid = await waitForPidFile(childPidPath, 2_000);
grandchildPid = await waitForPidFile(grandchildPidPath, 2_000);
const sessionExit = session.wait().catch(() => undefined);
session.kill();
await sleep(500);
childPid = Number(await readFile(childPidPath, "utf8"));
grandchildPid = Number(await readFile(grandchildPidPath, "utf8"));
await Promise.all([
waitForProcessExit(childPid, 2_000),
waitForProcessExit(grandchildPid, 2_000),
]);
await sessionExit;
expect(isProcessRunning(childPid)).toBe(false);
expect(isProcessRunning(grandchildPid)).toBe(false);
} finally {

View File

@@ -17,6 +17,7 @@ describe("telegram transport cache eviction over real sockets", () => {
let sendMessageCalls = 0;
let slowMode = false;
let slowRequestReceived: () => void = () => {};
let releaseSlowResponse: (() => void) | undefined;
beforeAll(async () => {
server = createServer((req, res) => {
@@ -34,7 +35,9 @@ describe("telegram transport cache eviction over real sockets", () => {
sendMessageCalls += 1;
if (slowMode) {
slowRequestReceived();
setTimeout(() => respond({ message_id: sendMessageCalls, chat: { id: 123 } }), 800);
releaseSlowResponse = () => {
respond({ message_id: sendMessageCalls, chat: { id: 123 } });
};
return;
}
respond({ message_id: sendMessageCalls, chat: { id: 123 } });
@@ -114,12 +117,24 @@ describe("telegram transport cache eviction over real sockets", () => {
await inFlight;
slowMode = false;
// New cache key -> evicts acct-0 while its send holds the lease.
const evictor = await sendMessageTelegram("123", "evictor", { cfg, accountId: "acct-64" });
expect(evictor.messageId).toBeTruthy();
// Deferred close: the evicted transport must NOT be closed mid-request.
expect(sockets.closed).toBe(0);
const releaseResponse = releaseSlowResponse;
if (!releaseResponse) {
throw new Error("slow Telegram response was not captured");
}
try {
// New cache key -> evicts acct-0 while its send holds the lease.
const evictor = await sendMessageTelegram("123", "evictor", {
cfg,
accountId: "acct-64",
});
expect(evictor.messageId).toBeTruthy();
// Deferred close: the evicted transport must NOT be closed mid-request.
expect(sockets.closed).toBe(0);
} finally {
releaseSlowResponse = undefined;
releaseResponse();
await slowSend.catch(() => undefined);
}
const slow = await slowSend;
expect(slow.messageId).toBeTruthy();
// Lease released -> the retired acct-0 transport closes its real socket.

View File

@@ -208,6 +208,43 @@ async function waitForExit(child: ChildProcess): Promise<void> {
});
}
async function waitForStderr(
child: ChildProcess,
expected: string,
timeoutMs: number,
): Promise<string> {
const stderr = child.stderr;
if (!stderr) {
throw new Error("child stderr is not piped");
}
stderr.setEncoding("utf8");
let output = "";
return await new Promise<string>((resolve, reject) => {
const cleanup = () => {
clearTimeout(timeout);
stderr.off("data", onData);
child.off("exit", onExit);
};
const onData = (chunk: string) => {
output += chunk;
if (output.includes(expected)) {
cleanup();
resolve(output);
}
};
const onExit = () => {
cleanup();
reject(new Error(`child exited before writing ${JSON.stringify(expected)}: ${output}`));
};
const timeout = setTimeout(() => {
cleanup();
reject(new Error(`timed out waiting for ${JSON.stringify(expected)}: ${output}`));
}, timeoutMs);
stderr.on("data", onData);
child.once("exit", onExit);
});
}
afterEach(async () => {
for (const child of children.splice(0)) {
child.kill("SIGKILL");
@@ -759,7 +796,7 @@ describe("pr-gates-lock helper", () => {
const second = spawnGateLockHolder(repoDir, secondStatus, {
OPENCLAW_HEAVY_CHECK_LOCK_POLL_MS: "50",
});
await new Promise((resolve) => setTimeout(resolve, 400));
await waitForStderr(second, "queued behind the local heavy-check lock", 5_000);
expect(existsSync(secondStatus)).toBe(false);
first.kill("SIGTERM");