mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:30:44 +00:00
QA Matrix: capture full runner output
This commit is contained in:
@@ -50,6 +50,34 @@ function createExitedProcess(code: number | null, signal: string | null = null)
|
||||
};
|
||||
}
|
||||
|
||||
function createPipedExitedProcess(params: {
|
||||
code?: number | null;
|
||||
signal?: string | null;
|
||||
stderr?: string;
|
||||
stdout?: string;
|
||||
}) {
|
||||
const stdout = new EventEmitter();
|
||||
const stderr = new EventEmitter();
|
||||
return {
|
||||
stdout,
|
||||
stderr,
|
||||
on: (event: string, cb: (code: number | null, signal: string | null) => void) => {
|
||||
if (event === "exit") {
|
||||
queueMicrotask(() => {
|
||||
if (params.stdout) {
|
||||
stdout.emit("data", Buffer.from(params.stdout));
|
||||
}
|
||||
if (params.stderr) {
|
||||
stderr.emit("data", Buffer.from(params.stderr));
|
||||
}
|
||||
cb(params.code ?? 0, params.signal ?? null);
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createFakeProcess() {
|
||||
return Object.assign(new EventEmitter(), {
|
||||
pid: 4242,
|
||||
@@ -322,6 +350,125 @@ describe("run-node script", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("tees launcher output into the requested generic output log", async () => {
|
||||
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
|
||||
await setupTrackedProject(tmp);
|
||||
const outputPath = path.join(tmp, ".artifacts", "qa-e2e", "matrix", "output.log");
|
||||
const spawnCalls: Array<{
|
||||
args: string[];
|
||||
env: Record<string, string | undefined>;
|
||||
stdio: unknown;
|
||||
}> = [];
|
||||
const spawn = (_cmd: string, args: string[], options?: unknown) => {
|
||||
const opts = options as { env?: NodeJS.ProcessEnv; stdio?: unknown } | undefined;
|
||||
spawnCalls.push({
|
||||
args,
|
||||
env: { ...opts?.env },
|
||||
stdio: opts?.stdio,
|
||||
});
|
||||
return createPipedExitedProcess({
|
||||
stdout: args[0] === "openclaw.mjs" ? "child stdout\n" : "",
|
||||
stderr: args[0] === "openclaw.mjs" ? "child stderr\n" : "",
|
||||
});
|
||||
};
|
||||
const mutedStream = {
|
||||
write: () => true,
|
||||
} as unknown as NodeJS.WriteStream;
|
||||
|
||||
const exitCode = await runNodeMain({
|
||||
cwd: tmp,
|
||||
args: ["status"],
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_FORCE_BUILD: "1",
|
||||
OPENCLAW_RUNNER_LOG: "1",
|
||||
OPENCLAW_RUN_NODE_OUTPUT_LOG: outputPath,
|
||||
},
|
||||
spawn,
|
||||
stderr: mutedStream,
|
||||
stdout: mutedStream,
|
||||
execPath: process.execPath,
|
||||
platform: process.platform,
|
||||
} as Parameters<typeof runNodeMain>[0] & { stdout: NodeJS.WriteStream });
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
await expect(fs.readFile(outputPath, "utf-8")).resolves.toContain("child stdout\n");
|
||||
await expect(fs.readFile(outputPath, "utf-8")).resolves.toContain("child stderr\n");
|
||||
await expect(fs.readFile(outputPath, "utf-8")).resolves.toContain("[openclaw]");
|
||||
expect(spawnCalls.at(-1)?.args).toEqual(["openclaw.mjs", "status"]);
|
||||
expect(spawnCalls.at(-1)?.env.OPENCLAW_RUN_NODE_OUTPUT_LOG).toBe(outputPath);
|
||||
expect(spawnCalls.at(-1)?.stdio).toEqual(["inherit", "pipe", "pipe"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("surfaces generic output log stream errors", async () => {
|
||||
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
|
||||
await setupTrackedProject(tmp);
|
||||
const outputPath = path.join(tmp, ".artifacts", "qa-e2e", "matrix", "output.log");
|
||||
await fs.mkdir(outputPath, { recursive: true });
|
||||
const spawn = () => createPipedExitedProcess({ stdout: "child stdout\n" });
|
||||
const stderrChunks: string[] = [];
|
||||
const mutedStream = {
|
||||
write: (chunk: string | Buffer) => {
|
||||
stderrChunks.push(String(chunk));
|
||||
return true;
|
||||
},
|
||||
} as unknown as NodeJS.WriteStream;
|
||||
|
||||
const exitCode = await runNodeMain({
|
||||
cwd: tmp,
|
||||
args: ["status"],
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_RUNNER_LOG: "0",
|
||||
OPENCLAW_RUN_NODE_OUTPUT_LOG: outputPath,
|
||||
},
|
||||
spawn,
|
||||
stderr: mutedStream,
|
||||
stdout: mutedStream,
|
||||
execPath: process.execPath,
|
||||
platform: process.platform,
|
||||
} as Parameters<typeof runNodeMain>[0] & { stdout: NodeJS.WriteStream });
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(stderrChunks.join("")).toContain("Failed to write output log");
|
||||
});
|
||||
});
|
||||
|
||||
it("does not mutate Matrix QA args when no generic output log is requested", async () => {
|
||||
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
|
||||
await setupTrackedProject(tmp);
|
||||
const spawnCalls: Array<{ args: string[]; env: Record<string, string | undefined> }> = [];
|
||||
const spawn = (_cmd: string, args: string[], options?: unknown) => {
|
||||
const opts = options as { env?: NodeJS.ProcessEnv } | undefined;
|
||||
spawnCalls.push({ args, env: { ...opts?.env } });
|
||||
return createPipedExitedProcess({});
|
||||
};
|
||||
const mutedStream = {
|
||||
write: () => true,
|
||||
} as unknown as NodeJS.WriteStream;
|
||||
|
||||
const exitCode = await runNodeMain({
|
||||
cwd: tmp,
|
||||
args: ["qa", "matrix"],
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_RUNNER_LOG: "0",
|
||||
},
|
||||
spawn,
|
||||
stderr: mutedStream,
|
||||
stdout: mutedStream,
|
||||
execPath: process.execPath,
|
||||
platform: process.platform,
|
||||
} as Parameters<typeof runNodeMain>[0] & { stdout: NodeJS.WriteStream });
|
||||
|
||||
expect(exitCode).toBe(0);
|
||||
const childArgs = spawnCalls.at(-1)?.args ?? [];
|
||||
expect(childArgs).toEqual(["openclaw.mjs", "qa", "matrix"]);
|
||||
expect(spawnCalls.at(-1)?.env.OPENCLAW_RUN_NODE_OUTPUT_LOG).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("skips rebuilding when dist is current and the source tree is clean", async () => {
|
||||
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
|
||||
await setupTrackedProject(tmp, {
|
||||
|
||||
Reference in New Issue
Block a user