Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
540
test/scripts/test-live-shard.test.ts
Normal file
540
test/scripts/test-live-shard.test.ts
Normal file
@@ -0,0 +1,540 @@
|
||||
// Test Live Shard tests cover test live shard script behavior.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
LIVE_TEST_SHARDS,
|
||||
RELEASE_LIVE_TEST_SHARDS,
|
||||
addLiveShardReportArgs,
|
||||
buildLiveShardPnpmArgs,
|
||||
buildLiveShardReportPath,
|
||||
buildLiveShardSpawnParams,
|
||||
collectAllLiveTestFiles,
|
||||
parseLiveShardArgs,
|
||||
removeLiveShardReportFile,
|
||||
selectLiveShardFiles,
|
||||
validateLiveShardReportPayload,
|
||||
} from "../../scripts/test-live-shard.mjs";
|
||||
import { expectNoReaddirSyncDuring } from "../../src/test-utils/fs-scan-assertions.js";
|
||||
|
||||
describe("scripts/test-live-shard", () => {
|
||||
const allFiles = collectAllLiveTestFiles();
|
||||
|
||||
it("discovers live tests without scanning source roots in-process", () => {
|
||||
expectNoReaddirSyncDuring(() => {
|
||||
const files = collectAllLiveTestFiles();
|
||||
|
||||
expect(files.length).toBeGreaterThan(0);
|
||||
expect(files.every((file) => file.endsWith(".live.test.ts"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("covers every native live test and tracks provider-filtered release fanout", () => {
|
||||
const selected = RELEASE_LIVE_TEST_SHARDS.flatMap((shard) =>
|
||||
selectLiveShardFiles(shard, allFiles).map((file) => ({ file, shard })),
|
||||
);
|
||||
const selectedFiles = selected.map(({ file }) => file);
|
||||
const duplicateFiles = selectedFiles.filter(
|
||||
(file, index) => selectedFiles.indexOf(file) !== index,
|
||||
);
|
||||
const musicProviderFanout = selected
|
||||
.filter(({ file }) => file === "extensions/music-generation-providers.live.test.ts")
|
||||
.map(({ shard }) => shard)
|
||||
.toSorted();
|
||||
|
||||
expect(allFiles.length).toBeGreaterThan(0);
|
||||
expect([...new Set(selectedFiles)].toSorted((a, b) => a.localeCompare(b))).toEqual(allFiles);
|
||||
expect(duplicateFiles).toEqual([
|
||||
"src/agents/zai.live.test.ts",
|
||||
"extensions/music-generation-providers.live.test.ts",
|
||||
]);
|
||||
expect(musicProviderFanout).toEqual([
|
||||
"native-live-extensions-media-music-google",
|
||||
"native-live-extensions-media-music-minimax",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps aggregate shard aliases available outside the release partition", () => {
|
||||
expect(LIVE_TEST_SHARDS).toEqual([
|
||||
...RELEASE_LIVE_TEST_SHARDS,
|
||||
"native-live-extensions-o-z",
|
||||
"native-live-extensions-media",
|
||||
"native-live-extensions-media-music",
|
||||
]);
|
||||
|
||||
const oToZAlias = selectLiveShardFiles("native-live-extensions-o-z", allFiles);
|
||||
expect(oToZAlias).toEqual(
|
||||
[
|
||||
...selectLiveShardFiles("native-live-extensions-o-z-other", allFiles),
|
||||
...selectLiveShardFiles("native-live-extensions-xai", allFiles),
|
||||
].toSorted((a, b) => a.localeCompare(b)),
|
||||
);
|
||||
|
||||
const mediaAlias = selectLiveShardFiles("native-live-extensions-media", allFiles);
|
||||
expect(mediaAlias).toEqual(
|
||||
[
|
||||
...selectLiveShardFiles("native-live-extensions-media-audio", allFiles),
|
||||
...selectLiveShardFiles("native-live-extensions-media-music", allFiles),
|
||||
...selectLiveShardFiles("native-live-extensions-media-video", allFiles),
|
||||
].toSorted((a, b) => a.localeCompare(b)),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps slow gateway backend and media-capable extension files in their own shards", () => {
|
||||
expect(selectLiveShardFiles("native-live-src-agents", allFiles)).toContain(
|
||||
"src/llm/providers/stream-wrappers/anthropic-family-tool-payload-compat.live.test.ts",
|
||||
);
|
||||
expect(selectLiveShardFiles("native-live-src-agents-zai-coding", allFiles)).toEqual([
|
||||
"src/agents/zai.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-src-gateway-backends", allFiles)).toEqual([
|
||||
"src/gateway/gateway-acp-bind.live.test.ts",
|
||||
"src/gateway/gateway-cli-backend.live.test.ts",
|
||||
"src/gateway/gateway-codex-bind.live.test.ts",
|
||||
"src/gateway/gateway-codex-harness.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-src-gateway-core", allFiles)).toEqual([
|
||||
"src/crestodian/rescue-channel.live.test.ts",
|
||||
"src/gateway/android-node.capabilities.live.test.ts",
|
||||
"src/gateway/gateway-acp-spawn-defaults.live.test.ts",
|
||||
"src/gateway/gateway-trajectory-export.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-src-infra", allFiles)).toEqual([
|
||||
"src/infra/push-apns-http2.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-test", allFiles)).toEqual([
|
||||
"test/image-generation.infer-cli.live.test.ts",
|
||||
"test/image-generation.runtime.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-extensions-media", allFiles)).toEqual([
|
||||
"extensions/minimax/minimax.live.test.ts",
|
||||
"extensions/music-generation-providers.live.test.ts",
|
||||
"extensions/openai/openai-tts.live.test.ts",
|
||||
"extensions/video-generation-providers.live.test.ts",
|
||||
"extensions/volcengine/tts.live.test.ts",
|
||||
"extensions/vydra/vydra.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-extensions-openai", allFiles)).toEqual([
|
||||
"extensions/openai/openai-provider.live.test.ts",
|
||||
"extensions/openai/openai.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-extensions-l-n", allFiles)).toEqual([
|
||||
"extensions/memory-lancedb/memory-lancedb.live.test.ts",
|
||||
"extensions/microsoft/microsoft.live.test.ts",
|
||||
"extensions/mistral/mistral.live.test.ts",
|
||||
]);
|
||||
expect(selectLiveShardFiles("native-live-extensions-moonshot", allFiles)).toEqual([
|
||||
"extensions/moonshot/moonshot.live.test.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the Codex CLI backend live smoke on a minimal tool profile", () => {
|
||||
const source = readFileSync("src/gateway/gateway-cli-backend.live.test.ts", "utf8");
|
||||
|
||||
expect(source).toContain('providerId === "codex-cli" && !schemaProbePluginPath');
|
||||
expect(source).toContain('profile: "minimal" as const');
|
||||
});
|
||||
|
||||
it("rejects unknown shard names", () => {
|
||||
expect(() => selectLiveShardFiles("native-live-missing")).toThrow(/Unknown live test shard/u);
|
||||
expect(() => selectLiveShardFiles("native-live-extensions-l-z")).toThrow(
|
||||
/Unknown live test shard/u,
|
||||
);
|
||||
});
|
||||
|
||||
it("parses list mode and rejects unknown live shard options", () => {
|
||||
expect(parseLiveShardArgs(["native-live-src-agents", "--list"])).toEqual({
|
||||
shard: "native-live-src-agents",
|
||||
listOnly: true,
|
||||
passthroughArgs: [],
|
||||
});
|
||||
|
||||
expect(() => parseLiveShardArgs(["--lisst", "native-live-src-agents"])).toThrow(
|
||||
/Unknown option: --lisst/u,
|
||||
);
|
||||
});
|
||||
|
||||
it("prints CLI help before validating shard options", () => {
|
||||
const result = spawnSync(process.execPath, ["scripts/test-live-shard.mjs", "--help"], {
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
expect(result.stdout).toContain("Usage: node scripts/test-live-shard.mjs");
|
||||
});
|
||||
|
||||
it("preserves Vitest passthrough args after the live shard separator", () => {
|
||||
expect(parseLiveShardArgs(["native-live-test", "--", "-t", "smoke"])).toEqual({
|
||||
shard: "native-live-test",
|
||||
listOnly: false,
|
||||
passthroughArgs: ["-t", "smoke"],
|
||||
});
|
||||
expect(buildLiveShardPnpmArgs(["test/foo.live.test.ts"], ["-t", "smoke"])).toEqual([
|
||||
"test:live",
|
||||
"--",
|
||||
"test/foo.live.test.ts",
|
||||
"-t",
|
||||
"smoke",
|
||||
]);
|
||||
});
|
||||
|
||||
it("adds JSON report evidence without dropping operator output", () => {
|
||||
const reportPath = buildLiveShardReportPath("native-live-src-agents", {
|
||||
OPENCLAW_LIVE_SHARD_REPORT_DIR: ".artifacts/live-proof",
|
||||
});
|
||||
|
||||
expect(reportPath).toBe(".artifacts/live-proof/native-live-src-agents.vitest.json");
|
||||
expect(addLiveShardReportArgs(["-t", "smoke"], reportPath)).toEqual([
|
||||
"-t",
|
||||
"smoke",
|
||||
"--reporter=default",
|
||||
"--reporter=json",
|
||||
"--outputFile.json=.artifacts/live-proof/native-live-src-agents.vitest.json",
|
||||
]);
|
||||
expect(
|
||||
buildLiveShardPnpmArgs(
|
||||
["src/agents/xai.live.test.ts"],
|
||||
addLiveShardReportArgs([], reportPath),
|
||||
),
|
||||
).toContain("--reporter=json");
|
||||
});
|
||||
|
||||
it("fails live shard reports with no passing tests", () => {
|
||||
expect(validateLiveShardReportPayload({ numPassedTests: 1, numTotalTests: 3 })).toEqual({
|
||||
ok: true,
|
||||
});
|
||||
expect(validateLiveShardReportPayload({ numPassedTests: 4, numTotalTests: 3 })).toEqual({
|
||||
ok: false,
|
||||
reason: "Vitest report numPassedTests exceeds numTotalTests.",
|
||||
});
|
||||
expect(validateLiveShardReportPayload({ numPassedTests: 0, numTotalTests: 3 })).toEqual({
|
||||
ok: false,
|
||||
reason: "Vitest report has no passing live tests.",
|
||||
});
|
||||
expect(validateLiveShardReportPayload({ numPassedTests: 0, numTotalTests: 0 })).toEqual({
|
||||
ok: false,
|
||||
reason: "Vitest report has no passing live tests.",
|
||||
});
|
||||
expect(validateLiveShardReportPayload({ numPassedTests: 0 })).toEqual({
|
||||
ok: false,
|
||||
reason: "Vitest report numTotalTests must be a non-negative integer.",
|
||||
});
|
||||
});
|
||||
|
||||
it("requires live shard report evidence for each selected file", () => {
|
||||
const payload = {
|
||||
numPassedTests: 1,
|
||||
numTotalTests: 2,
|
||||
testResults: [
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-acp-bind.live.test.ts"),
|
||||
assertionResults: [{ status: "passed" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
validateLiveShardReportPayload(payload, ["src/gateway/gateway-acp-bind.live.test.ts"]),
|
||||
).toEqual({ ok: true });
|
||||
expect(
|
||||
validateLiveShardReportPayload(payload, [
|
||||
"src/gateway/gateway-acp-bind.live.test.ts",
|
||||
"src/gateway/gateway-cli-backend.live.test.ts",
|
||||
]),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
reason:
|
||||
"Vitest report missing selected live test file evidence: src/gateway/gateway-cli-backend.live.test.ts",
|
||||
});
|
||||
expect(
|
||||
validateLiveShardReportPayload({ numPassedTests: 1, numTotalTests: 1 }, [
|
||||
"src/gateway/gateway-acp-bind.live.test.ts",
|
||||
]),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
reason: "Vitest report is missing testResults file evidence.",
|
||||
});
|
||||
});
|
||||
|
||||
it("requires each selected live shard file to have a passing assertion", () => {
|
||||
const payload = {
|
||||
numPassedTests: 1,
|
||||
numTotalTests: 2,
|
||||
testResults: [
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-acp-bind.live.test.ts"),
|
||||
assertionResults: [{ status: "passed" }],
|
||||
},
|
||||
{
|
||||
name: path.join(process.cwd(), "src/agents/openai-reasoning-compat.live.test.ts"),
|
||||
assertionResults: [{ status: "skipped" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
validateLiveShardReportPayload(payload, [
|
||||
"src/gateway/gateway-acp-bind.live.test.ts",
|
||||
"src/agents/openai-reasoning-compat.live.test.ts",
|
||||
]),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
reason:
|
||||
"Vitest report selected live test files had no passing assertions: src/agents/openai-reasoning-compat.live.test.ts",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows explicitly opt-in live shard files to be skipped until their env is enabled", () => {
|
||||
const payload = {
|
||||
numPassedTests: 1,
|
||||
numTotalTests: 2,
|
||||
testResults: [
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-codex-harness.live.test.ts"),
|
||||
assertionResults: [{ status: "passed" }],
|
||||
},
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-cli-backend.live.test.ts"),
|
||||
assertionResults: [{ status: "skipped" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const expectedFiles = [
|
||||
"src/gateway/gateway-codex-harness.live.test.ts",
|
||||
"src/gateway/gateway-cli-backend.live.test.ts",
|
||||
];
|
||||
|
||||
expect(validateLiveShardReportPayload(payload, expectedFiles, process.cwd(), {})).toEqual({
|
||||
ok: true,
|
||||
});
|
||||
expect(
|
||||
validateLiveShardReportPayload(payload, expectedFiles, process.cwd(), {
|
||||
OPENCLAW_LIVE_CLI_BACKEND: "1",
|
||||
}),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
reason:
|
||||
"Vitest report selected live test files had no passing assertions: src/gateway/gateway-cli-backend.live.test.ts",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows gateway core opt-in live files to be skipped until their env is enabled", () => {
|
||||
const payload = {
|
||||
numPassedTests: 1,
|
||||
numTotalTests: 2,
|
||||
testResults: [
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-codex-harness.live.test.ts"),
|
||||
assertionResults: [{ status: "passed" }],
|
||||
},
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-acp-spawn-defaults.live.test.ts"),
|
||||
assertionResults: [{ status: "skipped" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const expectedFiles = [
|
||||
"src/gateway/gateway-codex-harness.live.test.ts",
|
||||
"src/gateway/gateway-acp-spawn-defaults.live.test.ts",
|
||||
];
|
||||
|
||||
expect(validateLiveShardReportPayload(payload, expectedFiles, process.cwd(), {})).toEqual({
|
||||
ok: true,
|
||||
});
|
||||
expect(
|
||||
validateLiveShardReportPayload(payload, expectedFiles, process.cwd(), {
|
||||
OPENCLAW_LIVE_ACP_SPAWN_DEFAULTS: "1",
|
||||
}),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
reason:
|
||||
"Vitest report selected live test files had no passing assertions: src/gateway/gateway-acp-spawn-defaults.live.test.ts",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not count disabled opt-in sentinel assertions as live shard proof", () => {
|
||||
const payload = {
|
||||
numPassedTests: 1,
|
||||
numTotalTests: 2,
|
||||
testResults: [
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-codex-harness.live.test.ts"),
|
||||
assertionResults: [
|
||||
{
|
||||
ancestorTitles: ["gateway live (Codex harness disabled)"],
|
||||
status: "passed",
|
||||
title: "is opt-in",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: path.join(process.cwd(), "src/gateway/gateway-cli-backend.live.test.ts"),
|
||||
assertionResults: [{ status: "skipped" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
validateLiveShardReportPayload(
|
||||
payload,
|
||||
[
|
||||
"src/gateway/gateway-codex-harness.live.test.ts",
|
||||
"src/gateway/gateway-cli-backend.live.test.ts",
|
||||
],
|
||||
process.cwd(),
|
||||
{},
|
||||
),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
reason: "Vitest report has no enabled selected live test files with passing assertions.",
|
||||
});
|
||||
});
|
||||
|
||||
it("removes stale live shard reports before running a shard", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-live-shard-"));
|
||||
const reportPath = path.join(root, "stale.vitest.json");
|
||||
writeFileSync(reportPath, JSON.stringify({ numPassedTests: 1, numTotalTests: 1 }), "utf8");
|
||||
|
||||
try {
|
||||
removeLiveShardReportFile(reportPath);
|
||||
|
||||
expect(existsSync(reportPath)).toBe(false);
|
||||
expect(() => removeLiveShardReportFile(reportPath)).not.toThrow();
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("spawns live shard children in a cleanup-friendly process group", () => {
|
||||
expect(buildLiveShardSpawnParams({ PATH: "/usr/bin" }, "darwin")).toEqual({
|
||||
detached: true,
|
||||
env: { PATH: "/usr/bin" },
|
||||
stdio: "inherit",
|
||||
});
|
||||
expect(buildLiveShardSpawnParams({ PATH: "/usr/bin" }, "win32")).toEqual({
|
||||
detached: false,
|
||||
env: { PATH: "/usr/bin" },
|
||||
stdio: "inherit",
|
||||
});
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"cleans live shard descendants before forwarding parent SIGTERM",
|
||||
async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-live-shard-signal-"));
|
||||
const fakePnpmPath = path.join(root, "pnpm");
|
||||
const childPidPath = path.join(root, "child.pid");
|
||||
const descendantPidPath = path.join(root, "descendant.pid");
|
||||
const signaledPath = path.join(root, "signaled");
|
||||
let childPid = 0;
|
||||
let descendantPid = 0;
|
||||
let runner: ReturnType<typeof spawn> | undefined;
|
||||
|
||||
try {
|
||||
writeFakePnpm(fakePnpmPath);
|
||||
runner = spawn(
|
||||
process.execPath,
|
||||
["scripts/test-live-shard.mjs", "native-live-src-agents"],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_FAKE_PNPM_DESCENDANT_PID_PATH: descendantPidPath,
|
||||
OPENCLAW_FAKE_PNPM_PID_PATH: childPidPath,
|
||||
OPENCLAW_FAKE_PNPM_SIGNALED_PATH: signaledPath,
|
||||
npm_execpath: fakePnpmPath,
|
||||
},
|
||||
stdio: "ignore",
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => existsSync(childPidPath), 5_000);
|
||||
await waitFor(() => existsSync(descendantPidPath), 5_000);
|
||||
childPid = Number(readFileSync(childPidPath, "utf8"));
|
||||
descendantPid = Number(readFileSync(descendantPidPath, "utf8"));
|
||||
expect(Number.isInteger(childPid)).toBe(true);
|
||||
expect(Number.isInteger(descendantPid)).toBe(true);
|
||||
|
||||
runner.kill("SIGTERM");
|
||||
|
||||
await expect(waitForClose(runner)).resolves.toEqual({ code: null, signal: "SIGTERM" });
|
||||
await waitFor(() => existsSync(signaledPath), 5_000);
|
||||
expect(readFileSync(signaledPath, "utf8")).toBe("SIGTERM");
|
||||
await waitFor(() => !isProcessAlive(childPid), 5_000);
|
||||
await waitFor(() => !isProcessAlive(descendantPid), 5_000);
|
||||
} finally {
|
||||
if (runner?.pid && isProcessAlive(runner.pid)) {
|
||||
process.kill(runner.pid, "SIGKILL");
|
||||
}
|
||||
if (childPid && isProcessAlive(childPid)) {
|
||||
process.kill(childPid, "SIGKILL");
|
||||
}
|
||||
if (descendantPid && isProcessAlive(descendantPid)) {
|
||||
process.kill(descendantPid, "SIGKILL");
|
||||
}
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
function writeFakePnpm(filePath: string): void {
|
||||
writeFileSync(
|
||||
filePath,
|
||||
[
|
||||
"#!/usr/bin/env node",
|
||||
'const { spawn } = require("node:child_process");',
|
||||
'const fs = require("node:fs");',
|
||||
"const child = spawn(process.execPath, [",
|
||||
' "-e",',
|
||||
" \"process.on('SIGTERM', () => {}); setInterval(() => {}, 1000);\",",
|
||||
"], { stdio: 'ignore' });",
|
||||
"fs.writeFileSync(process.env.OPENCLAW_FAKE_PNPM_DESCENDANT_PID_PATH, String(child.pid));",
|
||||
"fs.writeFileSync(process.env.OPENCLAW_FAKE_PNPM_PID_PATH, String(process.pid));",
|
||||
'process.on("SIGTERM", () => {',
|
||||
' fs.writeFileSync(process.env.OPENCLAW_FAKE_PNPM_SIGNALED_PATH, "SIGTERM");',
|
||||
" process.exit(0);",
|
||||
"});",
|
||||
"setInterval(() => {}, 1000);",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
chmodSync(filePath, 0o755);
|
||||
}
|
||||
|
||||
async function waitFor(condition: () => boolean, timeoutMs: number): Promise<void> {
|
||||
const startedAt = Date.now();
|
||||
while (!condition()) {
|
||||
if (Date.now() - startedAt > timeoutMs) {
|
||||
throw new Error("timed out waiting for condition");
|
||||
}
|
||||
await delay(25);
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForClose(
|
||||
child: ReturnType<typeof spawn>,
|
||||
timeoutMs = 5_000,
|
||||
): Promise<{ code: number | null; signal: NodeJS.Signals | null }> {
|
||||
return await Promise.race([
|
||||
new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve) => {
|
||||
child.once("close", (code, signal) => resolve({ code, signal }));
|
||||
}),
|
||||
delay(timeoutMs).then(() => {
|
||||
throw new Error("timed out waiting for child close");
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
function isProcessAlive(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user