perf: reduce unit test hot path overhead

This commit is contained in:
Peter Steinberger 2026-03-18 18:19:12 +00:00
parent fa52d122c4
commit a0d3dc94d0
15 changed files with 213 additions and 34 deletions

View File

@ -92,20 +92,7 @@ export function createWhatsAppPluginBase(params: {
setupWizard: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["setupWizard"]>;
setup: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["setup"]>;
isConfigured: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["config"]>["isConfigured"];
}): Pick<
ChannelPlugin<ResolvedWhatsAppAccount>,
| "id"
| "meta"
| "setupWizard"
| "capabilities"
| "reload"
| "gatewayMethods"
| "configSchema"
| "config"
| "security"
| "setup"
| "groups"
> {
}) {
const collectWhatsAppSecurityWarnings =
createAllowlistProviderRouteAllowlistWarningCollector<ResolvedWhatsAppAccount>({
providerConfigPresent: (cfg) => cfg.channels?.whatsapp !== undefined,
@ -126,7 +113,7 @@ export function createWhatsAppPluginBase(params: {
groupAllowFromPath: "channels.whatsapp.groupAllowFrom",
},
});
return createChannelPluginBase({
const base = createChannelPluginBase({
id: WHATSAPP_CHANNEL,
meta: {
...getChatChannelMeta(WHATSAPP_CHANNEL),
@ -167,7 +154,18 @@ export function createWhatsAppPluginBase(params: {
},
setup: params.setup,
groups: params.groups,
}) as Pick<
});
return {
...base,
setupWizard: base.setupWizard!,
capabilities: base.capabilities!,
reload: base.reload!,
gatewayMethods: base.gatewayMethods!,
configSchema: base.configSchema!,
config: base.config!,
security: base.security!,
groups: base.groups!,
} satisfies Pick<
ChannelPlugin<ResolvedWhatsAppAccount>,
| "id"
| "meta"

View File

@ -1,6 +1,6 @@
export const optionalBundledClusters: string[];
export const optionalBundledClusterSet: Set<string>;
export const OPTIONAL_BUNDLED_BUILD_ENV: string;
export const OPTIONAL_BUNDLED_BUILD_ENV: "OPENCLAW_INCLUDE_OPTIONAL_BUNDLED";
export function isOptionalBundledCluster(cluster: string): boolean;
export function shouldIncludeOptionalBundledClusters(env?: NodeJS.ProcessEnv): boolean;
export function shouldBuildBundledCluster(cluster: string, env?: NodeJS.ProcessEnv): boolean;

View File

@ -0,0 +1,6 @@
export const optionalBundledClusters: string[];
export const optionalBundledClusterSet: Set<string>;
export const OPTIONAL_BUNDLED_BUILD_ENV: "OPENCLAW_INCLUDE_OPTIONAL_BUNDLED";
export function isOptionalBundledCluster(cluster: string): boolean;
export function shouldIncludeOptionalBundledClusters(env?: NodeJS.ProcessEnv): boolean;
export function shouldBuildBundledCluster(cluster: string, env?: NodeJS.ProcessEnv): boolean;

View File

@ -236,11 +236,16 @@ const parseEnvNumber = (name, fallback) => {
const parsed = Number.parseInt(process.env[name] ?? "", 10);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
};
const allKnownUnitFiles = allKnownTestFiles.filter((file) => inferTarget(file).owner === "unit");
const allKnownUnitFiles = allKnownTestFiles.filter((file) => {
if (file.endsWith(".live.test.ts") || file.endsWith(".e2e.test.ts")) {
return false;
}
return inferTarget(file).owner !== "gateway";
});
const defaultHeavyUnitFileLimit =
testProfile === "serial" ? 0 : testProfile === "low" ? 8 : highMemLocalHost ? 24 : 16;
testProfile === "serial" ? 0 : testProfile === "low" ? 20 : highMemLocalHost ? 80 : 60;
const defaultHeavyUnitLaneCount =
testProfile === "serial" ? 0 : testProfile === "low" ? 1 : highMemLocalHost ? 3 : 2;
testProfile === "serial" ? 0 : testProfile === "low" ? 2 : highMemLocalHost ? 5 : 4;
const heavyUnitFileLimit = parseEnvNumber(
"OPENCLAW_TEST_HEAVY_UNIT_FILE_LIMIT",
defaultHeavyUnitFileLimit,
@ -582,8 +587,10 @@ const defaultWorkerBudget =
}
: highMemLocalHost
? {
// High-memory local hosts can prioritize wall-clock speed.
unit: Math.max(4, Math.min(14, Math.floor((localWorkers * 7) / 8))),
// After peeling measured hotspots into dedicated lanes, the shared
// unit-fast lane shuts down more reliably with a slightly smaller
// worker fan-out than the old "max it out" local default.
unit: Math.max(4, Math.min(10, Math.floor((localWorkers * 5) / 8))),
unitIsolated: Math.max(1, Math.min(2, Math.floor(localWorkers / 6) || 1)),
extensions: Math.max(1, Math.min(4, Math.floor(localWorkers / 4))),
gateway: Math.max(2, Math.min(6, Math.floor(localWorkers / 2))),

View File

@ -5,11 +5,10 @@ import type {
SetSessionConfigOptionRequest,
SetSessionModeRequest,
} from "@agentclientprotocol/sdk";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { listThinkingLevels } from "../auto-reply/thinking.js";
import type { GatewayClient } from "../gateway/client.js";
import type { EventFrame } from "../gateway/protocol/index.js";
import { resetProviderRuntimeHookCacheForTest } from "../plugins/provider-runtime.js";
import { createInMemorySessionStore } from "./session.js";
import { AcpGatewayAgent } from "./translator.js";
import { createAcpConnection, createAcpGateway } from "./translator.test-helpers.js";
@ -121,10 +120,6 @@ async function expectOversizedPromptRejected(params: { sessionId: string; text:
sessionStore.clearAllSessionsForTest();
}
beforeEach(() => {
resetProviderRuntimeHookCacheForTest();
});
describe("acp session creation rate limit", () => {
it("rate limits excessive newSession bursts", async () => {
const sessionStore = createInMemorySessionStore();

View File

@ -14,6 +14,25 @@ export type ThinkingCatalogEntry = {
const BASE_THINKING_LEVELS: ThinkLevel[] = ["off", "minimal", "low", "medium", "high", "adaptive"];
const ANTHROPIC_CLAUDE_46_MODEL_RE = /^claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
const AMAZON_BEDROCK_CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
const OPENAI_XHIGH_MODEL_IDS = [
"gpt-5.4",
"gpt-5.4-pro",
"gpt-5.4-mini",
"gpt-5.4-nano",
"gpt-5.2",
] as const;
const OPENAI_CODEX_XHIGH_MODEL_IDS = [
"gpt-5.4",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
"gpt-5.2-codex",
"gpt-5.1-codex",
] as const;
const GITHUB_COPILOT_XHIGH_MODEL_IDS = ["gpt-5.2", "gpt-5.2-codex"] as const;
function matchesExactOrPrefix(modelId: string, ids: readonly string[]): boolean {
return ids.some((candidate) => modelId === candidate || modelId.startsWith(`${candidate}-`));
}
export function normalizeProviderId(provider?: string | null): string {
if (!provider) {
@ -33,6 +52,27 @@ export function isBinaryThinkingProvider(provider?: string | null): boolean {
return normalizeProviderId(provider) === "zai";
}
export function supportsBuiltInXHighThinking(
provider?: string | null,
model?: string | null,
): boolean {
const providerId = normalizeProviderId(provider);
const modelId = model?.trim().toLowerCase();
if (!providerId || !modelId) {
return false;
}
if (providerId === "openai") {
return matchesExactOrPrefix(modelId, OPENAI_XHIGH_MODEL_IDS);
}
if (providerId === "openai-codex") {
return matchesExactOrPrefix(modelId, OPENAI_CODEX_XHIGH_MODEL_IDS);
}
if (providerId === "github-copilot") {
return GITHUB_COPILOT_XHIGH_MODEL_IDS.includes(modelId as never);
}
return false;
}
// Normalize user-provided thinking level strings to the canonical enum.
export function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined {
if (!raw) {

View File

@ -5,6 +5,7 @@ import {
listThinkingLevels as listThinkingLevelsFallback,
normalizeProviderId,
resolveThinkingDefaultForModel as resolveThinkingDefaultForModelFallback,
supportsBuiltInXHighThinking,
} from "./thinking.shared.js";
import type { ThinkLevel, ThinkingCatalogEntry } from "./thinking.shared.js";
export {
@ -36,6 +37,9 @@ import {
} from "../plugins/provider-runtime.js";
export function isBinaryThinkingProvider(provider?: string | null, model?: string | null): boolean {
if (isBinaryThinkingProviderFallback(provider)) {
return true;
}
const normalizedProvider = normalizeProviderId(provider);
if (!normalizedProvider) {
return false;
@ -59,6 +63,9 @@ export function supportsXHighThinking(provider?: string | null, model?: string |
if (!modelKey) {
return false;
}
if (supportsBuiltInXHighThinking(provider, modelKey)) {
return true;
}
const providerKey = normalizeProviderId(provider);
if (providerKey) {
const pluginDecision = resolveProviderXHighThinking({

View File

@ -1,3 +1,7 @@
import { matrixPlugin } from "../../extensions/matrix/index.js";
import { msteamsPlugin } from "../../extensions/msteams/index.js";
import { nostrPlugin } from "../../extensions/nostr/index.js";
import { tlonPlugin } from "../../extensions/tlon/index.js";
import { bundledChannelPlugins } from "../channels/plugins/bundled.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createTestRegistry } from "../test-utils/channel-plugins.js";
@ -20,7 +24,13 @@ type PatchedSetupAdapterFields = {
};
export function setDefaultChannelPluginRegistryForTests(): void {
const channels = bundledChannelPlugins.map((plugin) => ({
const channels = [
...bundledChannelPlugins,
matrixPlugin,
msteamsPlugin,
nostrPlugin,
tlonPlugin,
].map((plugin) => ({
pluginId: plugin.id,
plugin,
source: "test" as const,

View File

@ -1,9 +1,18 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { NON_ENV_SECRETREF_MARKER } from "../agents/model-auth-markers.js";
import { resolveProviderAuths, type ProviderAuth } from "./provider-usage.auth.js";
const resolveProviderUsageAuthWithPluginMock = vi.fn(async () => null);
vi.mock("../plugins/provider-runtime.js", () => ({
resolveProviderUsageAuthWithPlugin: (...args: unknown[]) =>
resolveProviderUsageAuthWithPluginMock(...args),
}));
let resolveProviderAuths: typeof import("./provider-usage.auth.js").resolveProviderAuths;
type ProviderAuth = import("./provider-usage.auth.js").ProviderAuth;
describe("resolveProviderAuths key normalization", () => {
let suiteRoot = "";
@ -18,6 +27,7 @@ describe("resolveProviderAuths key normalization", () => {
beforeAll(async () => {
suiteRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-provider-auth-suite-"));
({ resolveProviderAuths } = await import("./provider-usage.auth.js"));
});
afterAll(async () => {
@ -26,6 +36,11 @@ describe("resolveProviderAuths key normalization", () => {
suiteCase = 0;
});
beforeEach(() => {
resolveProviderUsageAuthWithPluginMock.mockReset();
resolveProviderUsageAuthWithPluginMock.mockResolvedValue(null);
});
async function withSuiteHome<T>(
fn: (home: string) => Promise<T>,
env: Record<string, string | undefined>,

View File

@ -229,17 +229,19 @@ export async function resolveProviderAuths(params: {
providers: UsageProviderId[];
auth?: ProviderAuth[];
agentDir?: string;
config?: OpenClawConfig;
env?: NodeJS.ProcessEnv;
}): Promise<ProviderAuth[]> {
if (params.auth) {
return params.auth;
}
const state: UsageAuthState = {
cfg: loadConfig(),
cfg: params.config ?? loadConfig(),
store: ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
}),
env: process.env,
env: params.env ?? process.env,
agentDir: params.agentDir,
};
const auths: ProviderAuth[] = [];

View File

@ -179,6 +179,8 @@ export async function loadProviderUsageSummary(
providers: opts.providers ?? usageProviders,
auth: opts.auth,
agentDir: opts.agentDir,
config,
env,
});
if (auths.length === 0) {
return { updatedAt: now, providers: [] };

View File

@ -1,3 +1,4 @@
import type { OpenClawConfig } from "../config/config.js";
import { createProviderUsageFetch } from "../test-utils/provider-usage-fetch.js";
import type { ProviderAuth } from "./provider-usage.auth.js";
import type { UsageSummary } from "./provider-usage.types.js";
@ -8,6 +9,7 @@ type ProviderUsageLoader = (params: {
now: number;
auth?: ProviderAuth[];
fetch?: typeof fetch;
config?: OpenClawConfig;
}) => Promise<UsageSummary>;
export type ProviderUsageAuth<T extends ProviderUsageLoader> = NonNullable<
@ -23,5 +25,7 @@ export async function loadUsageWithAuth<T extends ProviderUsageLoader>(
now: usageNow,
auth,
fetch: mockFetch as unknown as typeof fetch,
// These tests exercise the built-in usage fetchers, not provider plugin hooks.
config: { plugins: { enabled: false } } as OpenClawConfig,
});
}

View File

@ -294,6 +294,7 @@ describe("provider usage loading", () => {
providers: ["anthropic"],
agentDir,
fetch: mockFetch as unknown as typeof fetch,
config: { plugins: { enabled: false } },
});
const claude = expectSingleAnthropicProvider(summary);

View File

@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const loadWebMediaMock = vi.hoisted(() => vi.fn());
vi.mock("../media/web-media.js", () => ({
vi.mock("./web-media.js", () => ({
loadWebMedia: loadWebMediaMock,
}));

View File

@ -130,6 +130,98 @@
"src/agents/session-tool-result-guard.tool-result-persist-hook.test.ts": {
"durationMs": 1600,
"testCount": 22
},
"src/plugins/tools.optional.test.ts": {
"durationMs": 1590,
"testCount": 18
},
"src/security/fix.test.ts": {
"durationMs": 1580,
"testCount": 24
},
"src/utils.test.ts": {
"durationMs": 1570,
"testCount": 34
},
"src/auto-reply/tool-meta.test.ts": {
"durationMs": 1560,
"testCount": 26
},
"src/auto-reply/envelope.test.ts": {
"durationMs": 1550,
"testCount": 20
},
"src/commands/auth-choice.test.ts": {
"durationMs": 1540,
"testCount": 18
},
"src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts": {
"durationMs": 1530,
"testCount": 14
},
"src/media/store.header-ext.test.ts": {
"durationMs": 1520,
"testCount": 16
},
"extensions/whatsapp/src/media.test.ts": {
"durationMs": 1510,
"testCount": 16
},
"extensions/whatsapp/src/auto-reply.web-auto-reply.falls-back-text-media-send-fails.test.ts": {
"durationMs": 1500,
"testCount": 10
},
"src/browser/server.covers-additional-endpoint-branches.test.ts": {
"durationMs": 1490,
"testCount": 18
},
"src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts": {
"durationMs": 1480,
"testCount": 12
},
"src/browser/server.skips-default-maxchars-explicitly-set-zero.test.ts": {
"durationMs": 1470,
"testCount": 10
},
"src/browser/server.auth-token-gates-http.test.ts": {
"durationMs": 1460,
"testCount": 15
},
"extensions/acpx/src/runtime.test.ts": {
"durationMs": 1450,
"testCount": 12
},
"test/scripts/ios-team-id.test.ts": {
"durationMs": 1440,
"testCount": 12
},
"src/agents/bash-tools.exec.background-abort.test.ts": {
"durationMs": 1430,
"testCount": 10
},
"src/agents/subagent-announce.format.test.ts": {
"durationMs": 1420,
"testCount": 12
},
"src/auto-reply/reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.test.ts": {
"durationMs": 1410,
"testCount": 14
},
"src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.test.ts": {
"durationMs": 1400,
"testCount": 10
},
"src/auto-reply/reply.triggers.group-intro-prompts.test.ts": {
"durationMs": 1390,
"testCount": 12
},
"src/auto-reply/reply.triggers.trigger-handling.handles-inline-commands-strips-it-before-agent.test.ts": {
"durationMs": 1380,
"testCount": 10
},
"extensions/whatsapp/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts": {
"durationMs": 1370,
"testCount": 10
}
}
}