perf: reduce unit test hot path overhead
This commit is contained in:
parent
fa52d122c4
commit
a0d3dc94d0
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
6
scripts/lib/optional-bundled-clusters.d.ts
vendored
Normal file
6
scripts/lib/optional-bundled-clusters.d.ts
vendored
Normal 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;
|
||||
@ -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))),
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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[] = [];
|
||||
|
||||
@ -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: [] };
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
}));
|
||||
|
||||
|
||||
92
test/fixtures/test-timings.unit.json
vendored
92
test/fixtures/test-timings.unit.json
vendored
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user