mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-22 11:58:08 +00:00
refactor(scripts): dedupe release assertion readers
This commit is contained in:
69
scripts/e2e/lib/release-assertion-files.mjs
Normal file
69
scripts/e2e/lib/release-assertion-files.mjs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Shared bounded file readers for release E2E assertion scripts.
|
||||
import fs from "node:fs";
|
||||
import { readTextFileTail } from "./text-file-utils.mjs";
|
||||
|
||||
const SCAN_CHUNK_BYTES = 64 * 1024;
|
||||
const SCAN_CARRY_CHARS = 256;
|
||||
export const ERROR_DETAIL_TAIL_BYTES = 16 * 1024;
|
||||
const JSON_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
export function readJson(file, maxBytes = JSON_ARTIFACT_MAX_BYTES) {
|
||||
const stat = fs.statSync(file);
|
||||
if (!stat.isFile()) {
|
||||
throw new Error(`${file} is not a file`);
|
||||
}
|
||||
if (stat.size > maxBytes) {
|
||||
throw new Error(
|
||||
`JSON artifact exceeded ${maxBytes} bytes: ${file} (${stat.size} bytes). Tail: ${readTextFileTail(
|
||||
file,
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
const text = fs.readFileSync(file, "utf8");
|
||||
const bytes = Buffer.byteLength(text, "utf8");
|
||||
if (bytes > maxBytes) {
|
||||
throw new Error(
|
||||
`JSON artifact exceeded ${maxBytes} bytes: ${file} (${bytes} bytes). Tail: ${readTextFileTail(
|
||||
file,
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
return JSON.parse(text);
|
||||
}
|
||||
|
||||
export function fileContainsText(file, needle) {
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.statSync(file);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (!stat.isFile() || stat.size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fd = fs.openSync(file, "r");
|
||||
try {
|
||||
const buffer = Buffer.alloc(Math.min(SCAN_CHUNK_BYTES, stat.size));
|
||||
let carry = "";
|
||||
let offset = 0;
|
||||
while (offset < stat.size) {
|
||||
const bytesToRead = Math.min(buffer.length, stat.size - offset);
|
||||
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
|
||||
if (bytesRead <= 0) {
|
||||
break;
|
||||
}
|
||||
offset += bytesRead;
|
||||
const text = carry + buffer.subarray(0, bytesRead).toString("utf8");
|
||||
if (text.includes(needle)) {
|
||||
return true;
|
||||
}
|
||||
carry = text.slice(-Math.max(SCAN_CARRY_CHARS, needle.length - 1));
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
@@ -12,82 +12,21 @@ import {
|
||||
parseMockOpenAiPort,
|
||||
} from "../fixtures/mock-openai-config.mjs";
|
||||
import { readPluginInstallRecords } from "../plugin-index-sqlite.mjs";
|
||||
import {
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
fileContainsText,
|
||||
readJson,
|
||||
} from "../release-assertion-files.mjs";
|
||||
import { readTextFileTail } from "../text-file-utils.mjs";
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
const SCAN_CHUNK_BYTES = 64 * 1024;
|
||||
const SCAN_CARRY_CHARS = 256;
|
||||
const ERROR_DETAIL_TAIL_BYTES = 16 * 1024;
|
||||
const JSON_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function readJson(file, maxBytes = JSON_ARTIFACT_MAX_BYTES) {
|
||||
const stat = fs.statSync(file);
|
||||
if (!stat.isFile()) {
|
||||
throw new Error(`${file} is not a file`);
|
||||
}
|
||||
if (stat.size > maxBytes) {
|
||||
throw new Error(
|
||||
`JSON artifact exceeded ${maxBytes} bytes: ${file} (${stat.size} bytes). Tail: ${readTextFileTail(
|
||||
file,
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
const text = fs.readFileSync(file, "utf8");
|
||||
const bytes = Buffer.byteLength(text, "utf8");
|
||||
if (bytes > maxBytes) {
|
||||
throw new Error(
|
||||
`JSON artifact exceeded ${maxBytes} bytes: ${file} (${bytes} bytes). Tail: ${readTextFileTail(
|
||||
file,
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
return JSON.parse(text);
|
||||
}
|
||||
|
||||
function fileContainsText(file, needle) {
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.statSync(file);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (!stat.isFile() || stat.size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fd = fs.openSync(file, "r");
|
||||
try {
|
||||
const buffer = Buffer.alloc(Math.min(SCAN_CHUNK_BYTES, stat.size));
|
||||
let carry = "";
|
||||
let offset = 0;
|
||||
while (offset < stat.size) {
|
||||
const bytesToRead = Math.min(buffer.length, stat.size - offset);
|
||||
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
|
||||
if (bytesRead <= 0) {
|
||||
break;
|
||||
}
|
||||
offset += bytesRead;
|
||||
const text = carry + buffer.subarray(0, bytesRead).toString("utf8");
|
||||
if (text.includes(needle)) {
|
||||
return true;
|
||||
}
|
||||
carry = text.slice(-Math.max(SCAN_CARRY_CHARS, needle.length - 1));
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
function configPath() {
|
||||
return (
|
||||
process.env.OPENCLAW_CONFIG_PATH ??
|
||||
|
||||
@@ -12,13 +12,13 @@ import {
|
||||
parseMockOpenAiPort,
|
||||
} from "../fixtures/mock-openai-config.mjs";
|
||||
import { readPluginInstallRecords } from "../plugin-index-sqlite.mjs";
|
||||
import {
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
fileContainsText,
|
||||
readJson,
|
||||
} from "../release-assertion-files.mjs";
|
||||
import { readTextFileTail } from "../text-file-utils.mjs";
|
||||
|
||||
const SCAN_CHUNK_BYTES = 64 * 1024;
|
||||
const SCAN_CARRY_CHARS = 256;
|
||||
const ERROR_DETAIL_TAIL_BYTES = 16 * 1024;
|
||||
const JSON_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
function clickClackHttpTimeoutMs() {
|
||||
return readPositiveInt(
|
||||
process.env.OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS,
|
||||
@@ -35,32 +35,6 @@ function clickClackHttpBodyMaxBytes() {
|
||||
);
|
||||
}
|
||||
|
||||
function readJson(file, maxBytes = JSON_ARTIFACT_MAX_BYTES) {
|
||||
const stat = fs.statSync(file);
|
||||
if (!stat.isFile()) {
|
||||
throw new Error(`${file} is not a file`);
|
||||
}
|
||||
if (stat.size > maxBytes) {
|
||||
throw new Error(
|
||||
`JSON artifact exceeded ${maxBytes} bytes: ${file} (${stat.size} bytes). Tail: ${readTextFileTail(
|
||||
file,
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
const text = fs.readFileSync(file, "utf8");
|
||||
const bytes = Buffer.byteLength(text, "utf8");
|
||||
if (bytes > maxBytes) {
|
||||
throw new Error(
|
||||
`JSON artifact exceeded ${maxBytes} bytes: ${file} (${bytes} bytes). Tail: ${readTextFileTail(
|
||||
file,
|
||||
ERROR_DETAIL_TAIL_BYTES,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
return JSON.parse(text);
|
||||
}
|
||||
|
||||
function readPositiveInt(raw, fallback, label) {
|
||||
const text = String(raw ?? "").trim();
|
||||
if (!text) {
|
||||
@@ -76,41 +50,6 @@ function readPositiveInt(raw, fallback, label) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function fileContainsText(file, needle) {
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.statSync(file);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (!stat.isFile() || stat.size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fd = fs.openSync(file, "r");
|
||||
try {
|
||||
const buffer = Buffer.alloc(Math.min(SCAN_CHUNK_BYTES, stat.size));
|
||||
let carry = "";
|
||||
let offset = 0;
|
||||
while (offset < stat.size) {
|
||||
const bytesToRead = Math.min(buffer.length, stat.size - offset);
|
||||
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
|
||||
if (bytesRead <= 0) {
|
||||
break;
|
||||
}
|
||||
offset += bytesRead;
|
||||
const text = carry + buffer.subarray(0, bytesRead).toString("utf8");
|
||||
if (text.includes(needle)) {
|
||||
return true;
|
||||
}
|
||||
carry = text.slice(-Math.max(SCAN_CARRY_CHARS, needle.length - 1));
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
async function withClickClackFixtureResponse(url, init, consume, options = {}) {
|
||||
const timeoutMs = options.timeoutMs ?? clickClackHttpTimeoutMs();
|
||||
const controller = new AbortController();
|
||||
|
||||
@@ -1030,6 +1030,13 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
|
||||
"test/scripts/release-user-journey-assertions.test.ts",
|
||||
],
|
||||
],
|
||||
[
|
||||
"scripts/e2e/lib/release-assertion-files.mjs",
|
||||
[
|
||||
"test/scripts/release-scenarios-assertions.test.ts",
|
||||
"test/scripts/release-user-journey-assertions.test.ts",
|
||||
],
|
||||
],
|
||||
["scripts/e2e/lib/skills/clawhub-install-proof.sh", ["test/scripts/e2e-shell-tempfiles.test.ts"]],
|
||||
[
|
||||
"scripts/e2e/lib/update-channel-switch/assertions.mjs",
|
||||
|
||||
@@ -365,6 +365,13 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
"scripts/e2e/lib/release-user-journey/assertions.mjs",
|
||||
["test/scripts/release-user-journey-assertions.test.ts"],
|
||||
],
|
||||
[
|
||||
"scripts/e2e/lib/release-assertion-files.mjs",
|
||||
[
|
||||
"test/scripts/release-scenarios-assertions.test.ts",
|
||||
"test/scripts/release-user-journey-assertions.test.ts",
|
||||
],
|
||||
],
|
||||
[
|
||||
"scripts/e2e/lib/openai-chat-tools/write-config.mjs",
|
||||
["test/scripts/openai-chat-tools-client.test.ts"],
|
||||
@@ -375,10 +382,7 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
],
|
||||
[
|
||||
"scripts/e2e/openai-chat-tools-docker.sh",
|
||||
[
|
||||
"test/scripts/openai-chat-tools-client.test.ts",
|
||||
"test/scripts/docker-e2e-plan.test.ts",
|
||||
],
|
||||
["test/scripts/openai-chat-tools-client.test.ts", "test/scripts/docker-e2e-plan.test.ts"],
|
||||
],
|
||||
[
|
||||
"scripts/e2e/lib/openai-web-search-minimal/mock-server.mjs",
|
||||
@@ -595,10 +599,7 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
],
|
||||
["scripts/e2e/mcp-channels-seed.ts", ["test/scripts/docker-e2e-seeds.test.ts"]],
|
||||
["scripts/e2e/docker-openai-seed.ts", ["test/scripts/docker-e2e-seeds.test.ts"]],
|
||||
[
|
||||
"scripts/e2e/mcp-code-mode-gateway-seed.ts",
|
||||
["test/scripts/docker-e2e-seeds.test.ts"],
|
||||
],
|
||||
["scripts/e2e/mcp-code-mode-gateway-seed.ts", ["test/scripts/docker-e2e-seeds.test.ts"]],
|
||||
[
|
||||
"scripts/e2e/cron-mcp-cleanup-docker.sh",
|
||||
[
|
||||
@@ -903,14 +904,15 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
});
|
||||
|
||||
it("routes code-mode namespace live Docker repro changes through its regression tests", () => {
|
||||
expect(resolveChangedTestTargetPlan(["scripts/repro/code-mode-namespace-live-docker.sh"]))
|
||||
.toEqual({
|
||||
mode: "targets",
|
||||
targets: [
|
||||
"test/scripts/code-mode-namespace-live.test.ts",
|
||||
"test/scripts/docker-build-helper.test.ts",
|
||||
],
|
||||
});
|
||||
expect(
|
||||
resolveChangedTestTargetPlan(["scripts/repro/code-mode-namespace-live-docker.sh"]),
|
||||
).toEqual({
|
||||
mode: "targets",
|
||||
targets: [
|
||||
"test/scripts/code-mode-namespace-live.test.ts",
|
||||
"test/scripts/docker-build-helper.test.ts",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("routes group visible reply config changes through channel delivery regressions", () => {
|
||||
|
||||
Reference in New Issue
Block a user