perf(test): slim entry and chat tests

This commit is contained in:
Peter Steinberger
2026-04-20 19:55:24 +01:00
parent 43fa394b83
commit 8e519aa826
4 changed files with 132 additions and 167 deletions

View File

@@ -3,11 +3,12 @@ import { spawn } from "node:child_process";
import { enableCompileCache } from "node:module";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { isRootHelpInvocation, isRootVersionInvocation } from "./cli/argv.js";
import { isRootHelpInvocation } from "./cli/argv.js";
import { parseCliContainerArgs, resolveCliContainerTarget } from "./cli/container-target.js";
import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js";
import { normalizeWindowsArgv } from "./cli/windows-argv.js";
import { buildCliRespawnPlan } from "./entry.respawn.js";
import { tryHandleRootVersionFastPath } from "./entry.version-fast-path.js";
import { isTruthyEnvValue, normalizeEnv } from "./infra/env.js";
import { isMainModule } from "./infra/is-main.js";
import { ensureOpenClawExecMarkerOnProcess } from "./infra/openclaw-exec-env.js";
@@ -99,29 +100,6 @@ if (
return true;
}
function tryHandleRootVersionFastPath(argv: string[]): boolean {
if (resolveCliContainerTarget(argv)) {
return false;
}
if (!isRootVersionInvocation(argv)) {
return false;
}
Promise.all([import("./version.js"), import("./infra/git-commit.js")])
.then(([{ VERSION }, { resolveCommitHash }]) => {
const commit = resolveCommitHash({ moduleUrl: import.meta.url });
console.log(commit ? `OpenClaw ${VERSION} (${commit})` : `OpenClaw ${VERSION}`);
process.exit(0);
})
.catch((error) => {
console.error(
"[openclaw] Failed to resolve version:",
error instanceof Error ? (error.stack ?? error.message) : error,
);
process.exitCode = 1;
});
return true;
}
process.argv = normalizeWindowsArgv(process.argv);
if (!ensureCliRespawnReady()) {

View File

@@ -1,163 +1,80 @@
import process from "node:process";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../test/helpers/import-fresh.js";
const applyCliProfileEnvMock = vi.hoisted(() => vi.fn());
const attachChildProcessBridgeMock = vi.hoisted(() => vi.fn());
const installProcessWarningFilterMock = vi.hoisted(() => vi.fn());
const isMainModuleMock = vi.hoisted(() => vi.fn(() => true));
const isRootHelpInvocationMock = vi.hoisted(() => vi.fn(() => false));
const isRootVersionInvocationMock = vi.hoisted(() => vi.fn(() => true));
const normalizeEnvMock = vi.hoisted(() => vi.fn());
const normalizeWindowsArgvMock = vi.hoisted(() => vi.fn((argv: string[]) => argv));
const parseCliProfileArgsMock = vi.hoisted(() => vi.fn((argv: string[]) => ({ ok: true, argv })));
const resolveCliContainerTargetMock = vi.hoisted(() => vi.fn<() => string | null>(() => null));
const resolveCommitHashMock = vi.hoisted(() => vi.fn<() => string | null>(() => "abc1234"));
const runCliMock = vi.hoisted(() => vi.fn(async () => {}));
const shouldSkipRespawnForArgvMock = vi.hoisted(() => vi.fn(() => true));
import { describe, expect, it, vi } from "vitest";
import { tryHandleRootVersionFastPath } from "./entry.version-fast-path.js";
vi.mock("./cli/argv.js", () => ({
isRootHelpInvocation: isRootHelpInvocationMock,
isRootVersionInvocation: isRootVersionInvocationMock,
isRootHelpInvocation: () => false,
isRootVersionInvocation: (argv: string[]) => argv.includes("--version"),
}));
vi.mock("./cli/container-target.js", () => ({
parseCliContainerArgs: (argv: string[]) => ({ ok: true, container: null, argv }),
resolveCliContainerTarget: resolveCliContainerTargetMock,
resolveCliContainerTarget: (argv: string[], env: NodeJS.ProcessEnv = process.env) =>
argv.includes("--container") ? "demo" : (env.OPENCLAW_CONTAINER ?? null),
}));
vi.mock("./cli/profile.js", () => ({
applyCliProfileEnv: applyCliProfileEnvMock,
parseCliProfileArgs: parseCliProfileArgsMock,
}));
vi.mock("./cli/run-main.js", () => ({
runCli: runCliMock,
}));
vi.mock("./cli/respawn-policy.js", () => ({
shouldSkipRespawnForArgv: shouldSkipRespawnForArgvMock,
}));
vi.mock("./cli/windows-argv.js", () => ({
normalizeWindowsArgv: normalizeWindowsArgvMock,
}));
vi.mock("./infra/env.js", () => ({
isTruthyEnvValue: () => false,
normalizeEnv: normalizeEnvMock,
}));
vi.mock("./infra/git-commit.js", () => ({
resolveCommitHash: resolveCommitHashMock,
}));
vi.mock("./infra/gaxios-fetch-compat.js", () => ({
installGaxiosFetchCompat: vi.fn(async () => {}),
}));
vi.mock("./infra/is-main.js", () => ({
isMainModule: isMainModuleMock,
}));
vi.mock("./infra/warning-filter.js", () => ({
installProcessWarningFilter: installProcessWarningFilterMock,
}));
vi.mock("./process/child-process-bridge.js", () => ({
attachChildProcessBridge: attachChildProcessBridgeMock,
}));
vi.mock("./version.js", () => ({
VERSION: "9.9.9-test",
}));
async function importEntry(scope: string) {
return await importFreshModule<typeof import("./entry.js")>(
import.meta.url,
`./entry.js?scope=${scope}`,
);
}
async function flushEntrySideEffects() {
async function flushVersionFastPath() {
await Promise.resolve();
await Promise.resolve();
await new Promise((resolve) => setTimeout(resolve, 0));
}
describe("entry root version fast path", () => {
let originalArgv: string[];
let originalGatewayToken: string | undefined;
let exitSpy: ReturnType<typeof vi.spyOn>;
it("prints version output and skips host handling when container-targeted", async () => {
const output = vi.fn();
const exit = vi.fn();
const resolveVersion = vi.fn(async () => ({
VERSION: "9.9.9-test",
resolveCommitHash: vi.fn(() => "abc1234"),
}));
beforeEach(() => {
vi.clearAllMocks();
originalArgv = [...process.argv];
originalGatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_TOKEN;
process.argv = ["node", "openclaw", "--version"];
exitSpy = vi
.spyOn(process, "exit")
.mockImplementation(((_code?: number) => undefined) as typeof process.exit);
});
expect(
tryHandleRootVersionFastPath(["node", "openclaw", "--version"], {
output,
exit,
resolveVersion,
}),
).toBe(true);
await flushVersionFastPath();
expect(output).toHaveBeenCalledWith("OpenClaw 9.9.9-test (abc1234)");
expect(exit).toHaveBeenCalledWith(0);
afterEach(() => {
process.argv = originalArgv;
if (originalGatewayToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
} else {
process.env.OPENCLAW_GATEWAY_TOKEN = originalGatewayToken;
}
exitSpy.mockRestore();
});
output.mockClear();
exit.mockClear();
resolveVersion.mockResolvedValueOnce({
VERSION: "9.9.9-test",
resolveCommitHash: vi.fn(() => null),
});
it("prints commit-tagged version output when commit metadata is available", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
expect(
tryHandleRootVersionFastPath(["node", "openclaw", "--version"], {
output,
exit,
resolveVersion,
}),
).toBe(true);
await flushVersionFastPath();
expect(output).toHaveBeenCalledWith("OpenClaw 9.9.9-test");
expect(exit).toHaveBeenCalledWith(0);
await importEntry("commit-tagged");
await flushEntrySideEffects();
expect(logSpy).toHaveBeenCalledWith("OpenClaw 9.9.9-test (abc1234)");
expect(exitSpy).toHaveBeenCalledWith(0);
output.mockClear();
exit.mockClear();
expect(
tryHandleRootVersionFastPath(["node", "openclaw", "--container", "demo", "--version"], {
output,
exit,
resolveVersion,
}),
).toBe(false);
expect(resolveVersion).toHaveBeenCalledTimes(2);
expect(output).not.toHaveBeenCalled();
expect(exit).not.toHaveBeenCalled();
logSpy.mockRestore();
});
it("falls back to plain version output when commit metadata is unavailable", async () => {
resolveCommitHashMock.mockReturnValueOnce(null);
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await importEntry("plain-version");
await flushEntrySideEffects();
expect(logSpy).toHaveBeenCalledWith("OpenClaw 9.9.9-test");
expect(exitSpy).toHaveBeenCalledWith(0);
logSpy.mockRestore();
});
it("skips the host version fast path when a container target is active", async () => {
resolveCliContainerTargetMock.mockReturnValue("demo");
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await importEntry("container-target");
await flushEntrySideEffects();
expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]);
expect(logSpy).not.toHaveBeenCalled();
expect(exitSpy).not.toHaveBeenCalled();
logSpy.mockRestore();
});
it("allows root version container mode when gateway override env vars are set", async () => {
resolveCliContainerTargetMock.mockReturnValue("demo");
process.env.OPENCLAW_GATEWAY_TOKEN = "demo-token";
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
await importEntry("gateway-override");
await flushEntrySideEffects();
expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
expect(
tryHandleRootVersionFastPath(["node", "openclaw", "--version"], {
env: { OPENCLAW_CONTAINER: "demo" },
output,
exit,
resolveVersion,
}),
).toBe(false);
});
});

View File

@@ -0,0 +1,53 @@
import { isRootVersionInvocation } from "./cli/argv.js";
import { resolveCliContainerTarget } from "./cli/container-target.js";
export function tryHandleRootVersionFastPath(
argv: string[],
deps: {
env?: NodeJS.ProcessEnv;
moduleUrl?: string;
output?: (message: string) => void;
exit?: (code?: number) => void;
onError?: (error: unknown) => void;
resolveVersion?: () => Promise<{
VERSION: string;
resolveCommitHash: (params: { moduleUrl: string }) => string | null;
}>;
} = {},
): boolean {
if (resolveCliContainerTarget(argv, deps.env)) {
return false;
}
if (!isRootVersionInvocation(argv)) {
return false;
}
const output = deps.output ?? ((message: string) => console.log(message));
const exit = deps.exit ?? ((code?: number) => process.exit(code));
const onError =
deps.onError ??
((error: unknown) => {
console.error(
"[openclaw] Failed to resolve version:",
error instanceof Error ? (error.stack ?? error.message) : error,
);
process.exitCode = 1;
});
const resolveVersion =
deps.resolveVersion ??
(async () => {
const [{ VERSION }, { resolveCommitHash }] = await Promise.all([
import("./version.js"),
import("./infra/git-commit.js"),
]);
return { VERSION, resolveCommitHash };
});
resolveVersion()
.then(({ VERSION, resolveCommitHash }) => {
const commit = resolveCommitHash({ moduleUrl: deps.moduleUrl ?? import.meta.url });
output(commit ? `OpenClaw ${VERSION} (${commit})` : `OpenClaw ${VERSION}`);
exit(0);
})
.catch(onError);
return true;
}

View File

@@ -16,6 +16,23 @@ vi.mock("../markdown.ts", () => ({
toSanitizedMarkdownHtml: (value: string) => value,
}));
vi.mock("../chat/export.ts", () => ({
exportChatMarkdown: vi.fn(),
}));
vi.mock("../chat/speech.ts", () => ({
isSttActive: () => false,
isSttSupported: () => false,
isTtsSpeaking: () => false,
isTtsSupported: () => false,
speakText: () => false,
startStt: () => false,
stopStt: () => undefined,
stopTts: () => undefined,
}));
vi.mock("../components/resizable-divider.ts", () => ({}));
vi.mock("./markdown-sidebar.ts", async () => {
const { html } = await import("lit");
return {