mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 19:12:22 +00:00
fix(test): stream QA Lab stdout artifacts (#95119)
* fix(test): bound QA Lab stdout artifact reads * fix(test): scan QA Lab stdout artifacts incrementally
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { spawn, spawnSync, type ChildProcess } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, open, rm, writeFile } from "node:fs/promises";
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import { Socket } from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
@@ -158,6 +158,7 @@ const MAX_STDOUT_DIAGNOSTIC_LINE_BYTES = readPositiveIntegerEnv(
|
||||
"OPENCLAW_QA_OTEL_MAX_STDOUT_DIAGNOSTIC_LINE_BYTES",
|
||||
512 * 1024,
|
||||
);
|
||||
const GATEWAY_STDOUT_ARTIFACT_READ_CHUNK_BYTES = 64 * 1024;
|
||||
|
||||
function readPositiveIntegerEnv(
|
||||
name: string,
|
||||
@@ -1075,7 +1076,11 @@ async function appendGatewayStdoutArtifactLogs(params: {
|
||||
"gateway.stdout.log",
|
||||
);
|
||||
try {
|
||||
params.capture.append(await readFile(gatewayStdoutPath, "utf8"));
|
||||
await appendUtf8FileToStdoutDiagnosticCapture(
|
||||
gatewayStdoutPath,
|
||||
params.capture,
|
||||
GATEWAY_STDOUT_ARTIFACT_READ_CHUNK_BYTES,
|
||||
);
|
||||
params.capture.flush();
|
||||
} catch (error) {
|
||||
if (!isErrnoCode(error, "ENOENT")) {
|
||||
@@ -1084,6 +1089,26 @@ async function appendGatewayStdoutArtifactLogs(params: {
|
||||
}
|
||||
}
|
||||
|
||||
async function appendUtf8FileToStdoutDiagnosticCapture(
|
||||
filePath: string,
|
||||
capture: ReturnType<typeof createStdoutDiagnosticLogCapture>,
|
||||
chunkBytes = GATEWAY_STDOUT_ARTIFACT_READ_CHUNK_BYTES,
|
||||
): Promise<void> {
|
||||
const file = await open(filePath, "r");
|
||||
try {
|
||||
const buffer = Buffer.alloc(Math.max(1, chunkBytes));
|
||||
for (;;) {
|
||||
const { bytesRead } = await file.read(buffer, 0, buffer.length);
|
||||
if (bytesRead === 0) {
|
||||
break;
|
||||
}
|
||||
capture.append(buffer.subarray(0, bytesRead));
|
||||
}
|
||||
} finally {
|
||||
await file.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function stopDockerContainer(name: string): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
const child = spawn("docker", ["stop", name], {
|
||||
@@ -1882,14 +1907,17 @@ async function main() {
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
appendGatewayStdoutArtifactLogs,
|
||||
appendCapturedBodyText,
|
||||
assertSmoke,
|
||||
createBoundedTextAccumulator,
|
||||
createStdoutDiagnosticLogCapture,
|
||||
decodeRequestBody,
|
||||
parseArgs,
|
||||
parseStdoutDiagnosticLogLine,
|
||||
readPositiveIntegerEnv,
|
||||
readRequestBody,
|
||||
appendUtf8FileToStdoutDiagnosticCapture,
|
||||
startLocalOtlpReceiver,
|
||||
startDockerOtelCollector,
|
||||
terminateChildTree,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// QA OTEL Smoke tests cover QA Lab telemetry evidence.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, statSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { createConnection as createNetConnection } from "node:net";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -501,6 +501,65 @@ describe("qa-otel-smoke receiver bounds", () => {
|
||||
expect(output.text()).not.toContain("DO_NOT_RETAIN_COLLECTOR_PREFIX");
|
||||
});
|
||||
|
||||
it("streams gateway stdout artifact records without requiring them in the tail", async () => {
|
||||
const tempRoot = mkdtempSync(path.join(os.tmpdir(), "openclaw-qa-otel-stdout-stream-"));
|
||||
const logPath = path.join(tempRoot, "gateway.stdout.log");
|
||||
const capture = testing.createStdoutDiagnosticLogCapture();
|
||||
const record = {
|
||||
signal: "openclaw.diagnostic.log",
|
||||
ts: "2026-06-18T00:00:00.000Z",
|
||||
"service.name": "openclaw-qa-lab-otel-smoke",
|
||||
severityText: "INFO",
|
||||
severityNumber: 9,
|
||||
body: "early log",
|
||||
attributes: {},
|
||||
};
|
||||
try {
|
||||
writeFileSync(
|
||||
logPath,
|
||||
`${JSON.stringify(record)}\n${"x".repeat(256 * 1024)}\nGATEWAY_STDOUT_TAIL\n`,
|
||||
);
|
||||
|
||||
await testing.appendUtf8FileToStdoutDiagnosticCapture(logPath, capture);
|
||||
capture.flush();
|
||||
|
||||
expect(capture.records).toEqual([record]);
|
||||
expect(capture.lines).toHaveLength(1);
|
||||
} finally {
|
||||
rmSync(tempRoot, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps gateway stdout artifact fallback parsing bounded", async () => {
|
||||
const tempRoot = mkdtempSync(path.join(os.tmpdir(), "openclaw-qa-otel-stdout-artifact-"));
|
||||
const outputDir = path.join(tempRoot, "output");
|
||||
const artifactDir = path.join(outputDir, "artifacts", "gateway-runtime");
|
||||
const record = {
|
||||
signal: "openclaw.diagnostic.log",
|
||||
ts: "2026-06-18T00:00:00.000Z",
|
||||
"service.name": "openclaw-qa-lab-otel-smoke",
|
||||
severityText: "INFO",
|
||||
severityNumber: 9,
|
||||
body: "tail log",
|
||||
attributes: {},
|
||||
};
|
||||
try {
|
||||
mkdirSync(artifactDir, { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(artifactDir, "gateway.stdout.log"),
|
||||
`${JSON.stringify(record)}\n${"x".repeat(256 * 1024)}\n`,
|
||||
);
|
||||
const capture = testing.createStdoutDiagnosticLogCapture();
|
||||
|
||||
await testing.appendGatewayStdoutArtifactLogs({ capture, outputDir });
|
||||
|
||||
expect(capture.records).toEqual([record]);
|
||||
expect(capture.lines).toHaveLength(1);
|
||||
} finally {
|
||||
rmSync(tempRoot, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("times out and kills a wedged QA suite child with a detached gateway", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user