mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
test(tailscale): await worker readiness events (#125396)
* test(tailscale): await worker readiness events Replace readiness polling in the shared worker fixture with an opt-in IPC waiter that settles on startup failure, exit, or test cancellation. Preserve compiled worker execution, immediate pre-readiness IPC loss, and existing worker exit and foreground-claim cleanup assertions. * test(tailscale): distinguish cancellation and exit signals Name the test-owned AbortSignal binding separately from the OS signal returned by child exit events so the readiness fixture passes the no-shadow lint rule. * test(tailscale): retain failure status in readiness errors Keep the foreground exit code or terminating signal in readiness errors, including silent failures, while retaining diagnostic output.
This commit is contained in:
@@ -10,7 +10,8 @@ import {
|
||||
} from "./tailscale-route-owner-protocol.js";
|
||||
import { runTailscaleRouteOwner } from "./tailscale-route-owner.worker.js";
|
||||
|
||||
function spawnRouteOwnerFixture() {
|
||||
function spawnRouteOwnerFixture(waitForReady: boolean, abortSignal: AbortSignal) {
|
||||
abortSignal.throwIfAborted();
|
||||
const workerUrl = resolveRuntimeWorkerUrl(runtimeProcessEntrypoints.tailscaleRouteOwner);
|
||||
const workerPath = fileURLToPath(workerUrl);
|
||||
const fixturePath = fileURLToPath(
|
||||
@@ -29,7 +30,47 @@ function spawnRouteOwnerFixture() {
|
||||
);
|
||||
const messages: TailscaleRouteOwnerMessage[] = [];
|
||||
worker.on("message", (message: TailscaleRouteOwnerMessage) => messages.push(message));
|
||||
return { messages, worker };
|
||||
const ready = waitForReady
|
||||
? new Promise<void>((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
worker.off("message", onMessage);
|
||||
worker.off("error", onError);
|
||||
worker.off("exit", onExit);
|
||||
abortSignal.removeEventListener("abort", onAbort);
|
||||
};
|
||||
const onMessage = (message: TailscaleRouteOwnerMessage) => {
|
||||
if (message.type === "ready") {
|
||||
cleanup();
|
||||
resolve();
|
||||
} else if (message.type === "failed") {
|
||||
const exitStatus = message.signal
|
||||
? `signal ${message.signal}`
|
||||
: `code ${message.code ?? "unknown"}`;
|
||||
const output = message.stderr || message.stdout;
|
||||
onError(new Error(`route owner failed (${exitStatus})${output ? `: ${output}` : ""}`));
|
||||
}
|
||||
};
|
||||
const onError = (error: Error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
};
|
||||
const onAbort = () => {
|
||||
onError(new Error("route owner readiness canceled", { cause: abortSignal.reason }));
|
||||
};
|
||||
const onExit = (code: number | null, signal: NodeJS.Signals | null) => {
|
||||
onError(
|
||||
new Error(
|
||||
`route owner exited before readiness (${signal ? `signal ${signal}` : `code ${code ?? "unknown"}`})`,
|
||||
),
|
||||
);
|
||||
};
|
||||
worker.on("message", onMessage);
|
||||
worker.once("error", onError);
|
||||
worker.once("exit", onExit);
|
||||
abortSignal.addEventListener("abort", onAbort, { once: true });
|
||||
})
|
||||
: undefined;
|
||||
return { messages, ready, worker };
|
||||
}
|
||||
|
||||
describe("Tailscale route owner", () => {
|
||||
@@ -70,15 +111,13 @@ describe("Tailscale route owner", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32").each([false, true])(
|
||||
it.runIf(process.platform !== "win32").for([false, true])(
|
||||
"terminates the claim when the Gateway IPC owner disappears (ready=%s)",
|
||||
async (waitForReady) => {
|
||||
const { messages, worker } = spawnRouteOwnerFixture();
|
||||
async (waitForReady, { signal: abortSignal }) => {
|
||||
const { ready, worker } = spawnRouteOwnerFixture(waitForReady, abortSignal);
|
||||
try {
|
||||
if (waitForReady) {
|
||||
await vi.waitFor(() => {
|
||||
expect(messages).toContainEqual({ type: "ready" });
|
||||
});
|
||||
await ready;
|
||||
}
|
||||
const exit = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
|
||||
(resolve) => {
|
||||
@@ -98,13 +137,11 @@ describe("Tailscale route owner", () => {
|
||||
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"terminates the claim before exiting on an interactive interrupt",
|
||||
async () => {
|
||||
const { messages, worker } = spawnRouteOwnerFixture();
|
||||
async ({ signal: abortSignal }) => {
|
||||
const { messages, ready, worker } = spawnRouteOwnerFixture(true, abortSignal);
|
||||
let routePid: number | undefined;
|
||||
try {
|
||||
await vi.waitFor(() => {
|
||||
expect(messages).toContainEqual({ type: "ready" });
|
||||
});
|
||||
await ready;
|
||||
const spawned = messages.find((message) => message.type === "spawned");
|
||||
if (!spawned) {
|
||||
throw new Error("route owner did not report its claim process");
|
||||
|
||||
Reference in New Issue
Block a user