mirror of https://github.com/openclaw/openclaw.git
fix(ci): reduce slow channel test skew
This commit is contained in:
parent
da7f016db6
commit
cf3ae2612b
|
|
@ -5,10 +5,7 @@ export {
|
|||
resolveConfiguredFromRequiredCredentialStatuses,
|
||||
} from "openclaw/plugin-sdk/channel-status";
|
||||
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
export {
|
||||
looksLikeSlackTargetId,
|
||||
normalizeSlackMessagingTarget,
|
||||
} from "openclaw/plugin-sdk/slack-targets";
|
||||
export { looksLikeSlackTargetId, normalizeSlackMessagingTarget } from "./targets.js";
|
||||
export type { ChannelPlugin, OpenClawConfig, SlackAccountConfig } from "openclaw/plugin-sdk/slack";
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { normalizeSlackMessagingTarget } from "openclaw/plugin-sdk/slack";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseSlackTarget, resolveSlackChannelId } from "./targets.js";
|
||||
import {
|
||||
normalizeSlackMessagingTarget,
|
||||
parseSlackTarget,
|
||||
resolveSlackChannelId,
|
||||
} from "./targets.js";
|
||||
|
||||
describe("parseSlackTarget", () => {
|
||||
it("parses user mentions and prefixes", () => {
|
||||
|
|
|
|||
|
|
@ -55,3 +55,27 @@ export function resolveSlackChannelId(raw: string): string {
|
|||
const target = parseSlackTarget(raw, { defaultKind: "channel" });
|
||||
return requireTargetKind({ platform: "Slack", target, kind: "channel" });
|
||||
}
|
||||
|
||||
export function normalizeSlackMessagingTarget(raw: string): string | undefined {
|
||||
return parseSlackTarget(raw, { defaultKind: "channel" })?.normalized;
|
||||
}
|
||||
|
||||
export function looksLikeSlackTargetId(raw: string): boolean {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
if (/^<@([A-Z0-9]+)>$/i.test(trimmed)) {
|
||||
return true;
|
||||
}
|
||||
if (/^(user|channel):/i.test(trimmed)) {
|
||||
return true;
|
||||
}
|
||||
if (/^slack:/i.test(trimmed)) {
|
||||
return true;
|
||||
}
|
||||
if (/^[@#]/.test(trimmed)) {
|
||||
return true;
|
||||
}
|
||||
return /^[CUWGD][A-Z0-9]{8,}$/i.test(trimmed);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { readChannelAllowFromStore } from "openclaw/plugin-sdk/conversation-runt
|
|||
import { upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { dispatchReplyWithBufferedBlockDispatcher } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
||||
import { syncTelegramMenuCommands } from "./bot-native-command-menu.js";
|
||||
import { deliverReplies, emitInternalMessageSentHook } from "./bot/delivery.js";
|
||||
import { createTelegramDraftStream } from "./draft-stream.js";
|
||||
import { resolveTelegramExecApproval } from "./exec-approval-resolver.js";
|
||||
|
|
@ -26,6 +27,7 @@ export type TelegramBotDeps = {
|
|||
loadWebMedia?: typeof loadWebMedia;
|
||||
buildModelsProviderData: typeof buildModelsProviderData;
|
||||
listSkillCommandsForAgents: typeof listSkillCommandsForAgents;
|
||||
syncTelegramMenuCommands?: typeof syncTelegramMenuCommands;
|
||||
wasSentByBot: typeof wasSentByBot;
|
||||
resolveExecApproval?: typeof resolveTelegramExecApproval;
|
||||
createTelegramDraftStream?: typeof createTelegramDraftStream;
|
||||
|
|
@ -65,6 +67,9 @@ export const defaultTelegramBotDeps: TelegramBotDeps = {
|
|||
get listSkillCommandsForAgents() {
|
||||
return listSkillCommandsForAgents;
|
||||
},
|
||||
get syncTelegramMenuCommands() {
|
||||
return syncTelegramMenuCommands;
|
||||
},
|
||||
get wasSentByBot() {
|
||||
return wasSentByBot;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { vi } from "vitest";
|
||||
import type { BuildTelegramMessageContextParams, TelegramMediaRef } from "./bot-message-context.js";
|
||||
|
||||
export const baseTelegramMessageContextConfig = {
|
||||
|
|
@ -22,8 +23,7 @@ export async function buildTelegramMessageContextForTest(
|
|||
): Promise<
|
||||
Awaited<ReturnType<typeof import("./bot-message-context.js").buildTelegramMessageContext>>
|
||||
> {
|
||||
const { vi } = await import("vitest");
|
||||
const { buildTelegramMessageContext } = await import("./bot-message-context.js");
|
||||
const buildTelegramMessageContext = await loadBuildTelegramMessageContext();
|
||||
return await buildTelegramMessageContext({
|
||||
primaryCtx: {
|
||||
message: {
|
||||
|
|
@ -64,3 +64,15 @@ export async function buildTelegramMessageContextForTest(
|
|||
sendChatActionHandler: { sendChatAction: vi.fn() } as never,
|
||||
});
|
||||
}
|
||||
|
||||
let buildTelegramMessageContextLoader:
|
||||
| typeof import("./bot-message-context.js").buildTelegramMessageContext
|
||||
| undefined;
|
||||
|
||||
async function loadBuildTelegramMessageContext() {
|
||||
if (!buildTelegramMessageContextLoader) {
|
||||
({ buildTelegramMessageContext: buildTelegramMessageContextLoader } =
|
||||
await import("./bot-message-context.js"));
|
||||
}
|
||||
return buildTelegramMessageContextLoader;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
let createNativeCommandsHarness: typeof import("./bot-native-commands.test-helpers.js").createNativeCommandsHarness;
|
||||
let deliverReplies: typeof import("./bot-native-commands.test-helpers.js").deliverReplies;
|
||||
|
|
@ -27,7 +27,7 @@ let executePluginCommandMock: {
|
|||
};
|
||||
|
||||
describe("registerTelegramNativeCommands (plugin auth)", () => {
|
||||
beforeEach(async () => {
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
({
|
||||
createNativeCommandsHarness,
|
||||
|
|
@ -40,6 +40,9 @@ describe("registerTelegramNativeCommands (plugin auth)", () => {
|
|||
getPluginCommandSpecs as unknown as typeof getPluginCommandSpecsMock;
|
||||
matchPluginCommandMock = matchPluginCommand as unknown as typeof matchPluginCommandMock;
|
||||
executePluginCommandMock = executePluginCommand as unknown as typeof executePluginCommandMock;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ function registerAndResolveCommandHandlerBase(params: {
|
|||
modelNames: new Map<string, string>(),
|
||||
})),
|
||||
listSkillCommandsForAgents: vi.fn(() => []),
|
||||
syncTelegramMenuCommands: vi.fn(),
|
||||
wasSentByBot: vi.fn(() => false),
|
||||
};
|
||||
registerTelegramNativeCommands({
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ export function createNativeCommandsHarness(params?: {
|
|||
modelNames: new Map<string, string>(),
|
||||
})),
|
||||
listSkillCommandsForAgents: vi.fn(() => []),
|
||||
syncTelegramMenuCommands: vi.fn(),
|
||||
wasSentByBot: vi.fn(() => false),
|
||||
};
|
||||
const bot = {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ import type { TelegramMessageContextOptions } from "./bot-message-context.types.
|
|||
import {
|
||||
buildCappedTelegramMenuCommands,
|
||||
buildPluginTelegramMenuCommands,
|
||||
syncTelegramMenuCommands,
|
||||
syncTelegramMenuCommands as syncTelegramMenuCommandsRuntime,
|
||||
} from "./bot-native-command-menu.js";
|
||||
import { TelegramUpdateKeyContext } from "./bot-updates.js";
|
||||
import { TelegramBotOptions } from "./bot.js";
|
||||
|
|
@ -501,6 +501,8 @@ export const registerTelegramNativeCommands = ({
|
|||
`Use channels.telegram.commands.native: false to disable, or reduce plugin/skill/custom commands.`,
|
||||
);
|
||||
}
|
||||
const syncTelegramMenuCommands =
|
||||
telegramDeps.syncTelegramMenuCommands ?? syncTelegramMenuCommandsRuntime;
|
||||
// Telegram only limits the setMyCommands payload (menu entries).
|
||||
// Keep hidden commands callable by registering handlers for the full catalog.
|
||||
syncTelegramMenuCommands({
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ export function loadTestCatalog() {
|
|||
const isolated =
|
||||
options.unitMemoryIsolatedFiles?.includes(normalizedFile) ||
|
||||
options.extensionTimedIsolatedFiles?.includes(normalizedFile) ||
|
||||
options.channelTimedIsolatedFiles?.includes(normalizedFile) ||
|
||||
unitForkIsolatedFileSet.has(normalizedFile) ||
|
||||
extensionForkIsolatedFileSet.has(normalizedFile) ||
|
||||
channelIsolatedFileSet.has(normalizedFile);
|
||||
|
|
@ -92,6 +93,9 @@ export function loadTestCatalog() {
|
|||
if (options.extensionTimedIsolatedFiles?.includes(normalizedFile)) {
|
||||
reasons.push("extensions-timed-heavy");
|
||||
}
|
||||
if (options.channelTimedIsolatedFiles?.includes(normalizedFile)) {
|
||||
reasons.push("channels-timed-heavy");
|
||||
}
|
||||
if (unitForkIsolatedFileSet.has(normalizedFile)) {
|
||||
reasons.push("unit-isolated-manifest");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -455,6 +455,30 @@ const resolveExtensionTimedHeavyFiles = (context) => {
|
|||
});
|
||||
};
|
||||
|
||||
const resolveChannelTimedHeavyFiles = (context) => {
|
||||
const { env, runtime, catalog, channelTimingManifest } = context;
|
||||
const timedHeavyChannelFileLimit = parseEnvNumber(
|
||||
env,
|
||||
"OPENCLAW_TEST_HEAVY_CHANNEL_FILE_LIMIT",
|
||||
runtime.isCI ? 10 : 6,
|
||||
);
|
||||
const timedHeavyChannelMinDurationMs = parseEnvNumber(
|
||||
env,
|
||||
"OPENCLAW_TEST_HEAVY_CHANNEL_MIN_MS",
|
||||
runtime.isCI ? 12_000 : 18_000,
|
||||
);
|
||||
return selectTimedHeavyFiles({
|
||||
candidates: catalog.allKnownTestFiles.filter(
|
||||
(file) =>
|
||||
catalog.channelTestPrefixes.some((prefix) => file.startsWith(prefix)) &&
|
||||
!catalog.channelIsolatedFileSet.has(file),
|
||||
),
|
||||
limit: timedHeavyChannelFileLimit,
|
||||
minDurationMs: timedHeavyChannelMinDurationMs,
|
||||
timings: channelTimingManifest,
|
||||
});
|
||||
};
|
||||
|
||||
const buildDefaultUnits = (context, request) => {
|
||||
const {
|
||||
env,
|
||||
|
|
@ -481,6 +505,7 @@ const buildDefaultUnits = (context, request) => {
|
|||
(file) => !catalog.unitBehaviorOverrideSet.has(file),
|
||||
);
|
||||
const extensionTimedHeavyFiles = resolveExtensionTimedHeavyFiles(context);
|
||||
const channelTimedHeavyFiles = resolveChannelTimedHeavyFiles(context);
|
||||
const unitSchedulingOverrideSet = new Set([
|
||||
...catalog.unitBehaviorOverrideSet,
|
||||
...memoryHeavyUnitFiles,
|
||||
|
|
@ -489,6 +514,10 @@ const buildDefaultUnits = (context, request) => {
|
|||
...catalog.extensionForkIsolatedFiles,
|
||||
...extensionTimedHeavyFiles,
|
||||
]);
|
||||
const channelSchedulingOverrideSet = new Set([
|
||||
...catalog.channelIsolatedFiles,
|
||||
...channelTimedHeavyFiles,
|
||||
]);
|
||||
const unitFastExcludedFiles = [
|
||||
...new Set([
|
||||
...unitSchedulingOverrideSet,
|
||||
|
|
@ -518,7 +547,7 @@ const buildDefaultUnits = (context, request) => {
|
|||
const channelSharedCandidateFiles = catalog.allKnownTestFiles.filter(
|
||||
(file) =>
|
||||
catalog.channelTestPrefixes.some((prefix) => file.startsWith(prefix)) &&
|
||||
!catalog.channelIsolatedFileSet.has(file),
|
||||
!channelSchedulingOverrideSet.has(file),
|
||||
);
|
||||
const defaultExtensionsBatchTargetMs = executionBudget.extensionsBatchTargetMs;
|
||||
const extensionsBatchTargetMs = parseEnvNumber(
|
||||
|
|
@ -709,6 +738,26 @@ const buildDefaultUnits = (context, request) => {
|
|||
}),
|
||||
);
|
||||
}
|
||||
for (const file of channelTimedHeavyFiles) {
|
||||
units.push(
|
||||
createExecutionUnit(context, {
|
||||
id: `channels-${path.basename(file, ".test.ts")}-isolated`,
|
||||
surface: "channels",
|
||||
isolate: true,
|
||||
estimatedDurationMs: estimateChannelDurationMs(file),
|
||||
args: [
|
||||
"vitest",
|
||||
"run",
|
||||
"--config",
|
||||
"vitest.channels.config.ts",
|
||||
"--pool=forks",
|
||||
...noIsolateArgs,
|
||||
file,
|
||||
],
|
||||
reasons: ["channels-timed-heavy"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedSurfaceSet.has("contracts")) {
|
||||
|
|
@ -839,7 +888,7 @@ const buildDefaultUnits = (context, request) => {
|
|||
);
|
||||
}
|
||||
|
||||
return { units, unitMemoryIsolatedFiles };
|
||||
return { units, unitMemoryIsolatedFiles, channelTimedHeavyFiles };
|
||||
};
|
||||
|
||||
const createTargetedUnit = (context, classification, filters) => {
|
||||
|
|
@ -961,6 +1010,7 @@ const buildTargetedUnits = (context, request) => {
|
|||
}
|
||||
const unitMemoryIsolatedFiles = request.unitMemoryIsolatedFiles ?? [];
|
||||
const extensionTimedIsolatedFiles = request.extensionTimedIsolatedFiles ?? [];
|
||||
const channelTimedIsolatedFiles = request.channelTimedIsolatedFiles ?? [];
|
||||
const estimateUnitDurationMs = (file) =>
|
||||
context.unitTimingManifest.files[file]?.durationMs ??
|
||||
context.unitTimingManifest.defaultDurationMs;
|
||||
|
|
@ -985,6 +1035,7 @@ const buildTargetedUnits = (context, request) => {
|
|||
const classification = context.catalog.classifyTestFile(normalizeRepoPath(fileFilter), {
|
||||
unitMemoryIsolatedFiles,
|
||||
extensionTimedIsolatedFiles,
|
||||
channelTimedIsolatedFiles,
|
||||
});
|
||||
const key = `${classification.legacyBasePinned ? "base-pinned" : classification.surface}:${
|
||||
classification.isolated ? "isolated" : "default"
|
||||
|
|
@ -998,6 +1049,7 @@ const buildTargetedUnits = (context, request) => {
|
|||
const classification = context.catalog.classifyTestFile(matchedFile, {
|
||||
unitMemoryIsolatedFiles,
|
||||
extensionTimedIsolatedFiles,
|
||||
channelTimedIsolatedFiles,
|
||||
});
|
||||
const key = `${classification.legacyBasePinned ? "base-pinned" : classification.surface}:${
|
||||
classification.isolated ? "isolated" : "default"
|
||||
|
|
@ -1017,6 +1069,7 @@ const buildTargetedUnits = (context, request) => {
|
|||
context.catalog.classifyTestFile(file, {
|
||||
unitMemoryIsolatedFiles,
|
||||
extensionTimedIsolatedFiles,
|
||||
channelTimedIsolatedFiles,
|
||||
}),
|
||||
[file],
|
||||
),
|
||||
|
|
@ -1154,10 +1207,18 @@ const estimateTopLevelEntryDurationMs = (unit, context) => {
|
|||
);
|
||||
}
|
||||
if (context.catalog.channelTestPrefixes.some((prefix) => file.startsWith(prefix))) {
|
||||
return totalMs + 3_000;
|
||||
return (
|
||||
totalMs +
|
||||
(context.channelTimingManifest.files[file]?.durationMs ??
|
||||
context.channelTimingManifest.defaultDurationMs)
|
||||
);
|
||||
}
|
||||
if (file.startsWith(BUNDLED_PLUGIN_PATH_PREFIX)) {
|
||||
return totalMs + 2_000;
|
||||
return (
|
||||
totalMs +
|
||||
(context.extensionTimingManifest.files[file]?.durationMs ??
|
||||
context.extensionTimingManifest.defaultDurationMs)
|
||||
);
|
||||
}
|
||||
return totalMs + 1_000;
|
||||
}, 0);
|
||||
|
|
@ -1497,9 +1558,11 @@ export function explainExecutionTarget(request, options = {}) {
|
|||
(file) => !context.catalog.unitBehaviorOverrideSet.has(file),
|
||||
);
|
||||
const extensionTimedIsolatedFiles = resolveExtensionTimedHeavyFiles(context);
|
||||
const channelTimedIsolatedFiles = resolveChannelTimedHeavyFiles(context);
|
||||
const classification = context.catalog.classifyTestFile(normalizedTarget, {
|
||||
unitMemoryIsolatedFiles,
|
||||
extensionTimedIsolatedFiles,
|
||||
channelTimedIsolatedFiles,
|
||||
});
|
||||
const targetedUnit = createTargetedUnit(context, classification, [normalizedTarget]);
|
||||
return {
|
||||
|
|
@ -1584,12 +1647,14 @@ export function buildExecutionPlan(request, options = {}) {
|
|||
|
||||
const defaultPlanning = buildDefaultUnits(context, { ...request, fileFilters });
|
||||
const extensionTimedIsolatedFiles = resolveExtensionTimedHeavyFiles(context);
|
||||
const channelTimedIsolatedFiles = resolveChannelTimedHeavyFiles(context);
|
||||
let units = defaultPlanning.units;
|
||||
const targetedUnits = buildTargetedUnits(context, {
|
||||
...request,
|
||||
fileFilters,
|
||||
unitMemoryIsolatedFiles: defaultPlanning.unitMemoryIsolatedFiles,
|
||||
extensionTimedIsolatedFiles,
|
||||
channelTimedIsolatedFiles,
|
||||
});
|
||||
if (context.configuredShardCount !== null && context.shardCount > 1) {
|
||||
units = expandUnitsAcrossTopLevelShards(context, units);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,38 @@
|
|||
import { parseSlackTarget } from "../../../plugin-sdk/slack-targets.js";
|
||||
import {
|
||||
buildMessagingTarget,
|
||||
ensureTargetId,
|
||||
parseMentionPrefixOrAtUserTarget,
|
||||
} from "../../targets.js";
|
||||
|
||||
export function normalizeSlackMessagingTarget(raw: string): string | undefined {
|
||||
const target = parseSlackTarget(raw, { defaultKind: "channel" });
|
||||
return target?.normalized;
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const userTarget = parseMentionPrefixOrAtUserTarget({
|
||||
raw: trimmed,
|
||||
mentionPattern: /^<@([A-Z0-9]+)>$/i,
|
||||
prefixes: [
|
||||
{ prefix: "user:", kind: "user" },
|
||||
{ prefix: "channel:", kind: "channel" },
|
||||
{ prefix: "slack:", kind: "user" },
|
||||
],
|
||||
atUserPattern: /^[A-Z0-9]+$/i,
|
||||
atUserErrorMessage: "Slack DMs require a user id (use user:<id> or <@id>)",
|
||||
});
|
||||
if (userTarget) {
|
||||
return userTarget.normalized;
|
||||
}
|
||||
if (trimmed.startsWith("#")) {
|
||||
const candidate = trimmed.slice(1).trim();
|
||||
const id = ensureTargetId({
|
||||
candidate,
|
||||
pattern: /^[A-Z0-9]+$/i,
|
||||
errorMessage: "Slack channels require a channel id (use channel:<id>)",
|
||||
});
|
||||
return buildMessagingTarget("channel", id, trimmed).normalized;
|
||||
}
|
||||
return buildMessagingTarget("channel", trimmed, trimmed).normalized;
|
||||
}
|
||||
|
||||
export function looksLikeSlackTargetId(raw: string): boolean {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"config": "vitest.channels.config.ts",
|
||||
"generatedAt": "2026-03-24T04:15:05Z",
|
||||
"generatedAt": "2026-03-31T10:45:00Z",
|
||||
"defaultDurationMs": 3000,
|
||||
"files": {
|
||||
"extensions/telegram/src/bot.create-telegram-bot.test.ts": {
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"durationMs": 3200
|
||||
},
|
||||
"extensions/telegram/src/bot/delivery.resolve-media-retry.test.ts": {
|
||||
"durationMs": 3000
|
||||
"durationMs": 18920
|
||||
},
|
||||
"extensions/telegram/src/webhook.test.ts": {
|
||||
"durationMs": 2900
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
"durationMs": 2300
|
||||
},
|
||||
"extensions/whatsapp/src/auto-reply/deliver-reply.test.ts": {
|
||||
"durationMs": 2200
|
||||
"durationMs": 12400
|
||||
},
|
||||
"extensions/slack/src/monitor/events/reactions.test.ts": {
|
||||
"durationMs": 2200
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
"durationMs": 2100
|
||||
},
|
||||
"extensions/telegram/src/bot-native-commands.session-meta.test.ts": {
|
||||
"durationMs": 2100
|
||||
"durationMs": 18000
|
||||
},
|
||||
"extensions/discord/src/monitor/native-command.plugin-dispatch.test.ts": {
|
||||
"durationMs": 2000
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
"durationMs": 1900
|
||||
},
|
||||
"extensions/telegram/src/bot-native-commands.test.ts": {
|
||||
"durationMs": 1700
|
||||
"durationMs": 17590
|
||||
},
|
||||
"extensions/whatsapp/src/auto-reply/heartbeat-runner.test.ts": {
|
||||
"durationMs": 1700
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
"durationMs": 1500
|
||||
},
|
||||
"extensions/telegram/src/bot.create-telegram-bot.channel-post-media.test.ts": {
|
||||
"durationMs": 1500
|
||||
"durationMs": 13000
|
||||
},
|
||||
"extensions/whatsapp/src/setup-surface.test.ts": {
|
||||
"durationMs": 1400
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
"durationMs": 1000
|
||||
},
|
||||
"extensions/telegram/src/bot-message-context.audio-transcript.test.ts": {
|
||||
"durationMs": 1000
|
||||
"durationMs": 17310
|
||||
},
|
||||
"extensions/whatsapp/src/monitor-inbox.append-upsert.test.ts": {
|
||||
"durationMs": 999
|
||||
|
|
@ -220,7 +220,7 @@
|
|||
"durationMs": 701
|
||||
},
|
||||
"extensions/telegram/src/bot-message-context.thread-binding.test.ts": {
|
||||
"durationMs": 695
|
||||
"durationMs": 17700
|
||||
},
|
||||
"src/browser/pw-tools-core.waits-next-download-saves-it.test.ts": {
|
||||
"durationMs": 677
|
||||
|
|
@ -241,7 +241,7 @@
|
|||
"durationMs": 570
|
||||
},
|
||||
"extensions/telegram/src/bot-native-commands.plugin-auth.test.ts": {
|
||||
"durationMs": 563
|
||||
"durationMs": 19500
|
||||
},
|
||||
"extensions/discord/src/monitor/agent-components.wildcard.test.ts": {
|
||||
"durationMs": 547
|
||||
|
|
@ -517,7 +517,7 @@
|
|||
"durationMs": 11
|
||||
},
|
||||
"extensions/telegram/src/bot-message-context.sender-prefix.test.ts": {
|
||||
"durationMs": 11
|
||||
"durationMs": 19410
|
||||
},
|
||||
"extensions/whatsapp/src/active-listener.test.ts": {
|
||||
"durationMs": 11
|
||||
|
|
@ -736,7 +736,7 @@
|
|||
"durationMs": 5
|
||||
},
|
||||
"extensions/slack/src/targets.test.ts": {
|
||||
"durationMs": 5
|
||||
"durationMs": 100
|
||||
},
|
||||
"extensions/imessage/src/monitor/loop-rate-limiter.test.ts": {
|
||||
"durationMs": 5
|
||||
|
|
|
|||
|
|
@ -152,6 +152,39 @@ describe("test planner", () => {
|
|||
artifacts.cleanupTempArtifacts();
|
||||
});
|
||||
|
||||
it("auto-isolates timed-heavy channel suites in CI", () => {
|
||||
const env = {
|
||||
CI: "true",
|
||||
GITHUB_ACTIONS: "true",
|
||||
RUNNER_OS: "Linux",
|
||||
OPENCLAW_TEST_HOST_CPU_COUNT: "4",
|
||||
OPENCLAW_TEST_HOST_MEMORY_GIB: "16",
|
||||
};
|
||||
const artifacts = createExecutionArtifacts(env);
|
||||
const plan = buildExecutionPlan(
|
||||
{
|
||||
profile: null,
|
||||
mode: "ci",
|
||||
surfaces: ["channels"],
|
||||
passthroughArgs: [],
|
||||
},
|
||||
{
|
||||
env,
|
||||
platform: "linux",
|
||||
writeTempJsonArtifact: artifacts.writeTempJsonArtifact,
|
||||
},
|
||||
);
|
||||
|
||||
const hotspotUnit = plan.selectedUnits.find(
|
||||
(unit) => unit.id === "channels-bot-native-commands.plugin-auth-isolated",
|
||||
);
|
||||
|
||||
expect(hotspotUnit).toBeTruthy();
|
||||
expect(hotspotUnit?.isolate).toBe(true);
|
||||
expect(hotspotUnit?.reasons).toContain("channels-timed-heavy");
|
||||
artifacts.cleanupTempArtifacts();
|
||||
});
|
||||
|
||||
it("scales down mid-tier local concurrency under saturated load", () => {
|
||||
const artifacts = createExecutionArtifacts({
|
||||
RUNNER_OS: "Linux",
|
||||
|
|
@ -437,6 +470,25 @@ describe("test planner", () => {
|
|||
expect(explanation.reasons).toContain("extensions-timed-heavy");
|
||||
});
|
||||
|
||||
it("explains timed-heavy channel suites as isolated", () => {
|
||||
const explanation = explainExecutionTarget(
|
||||
{
|
||||
mode: "ci",
|
||||
fileFilters: ["extensions/telegram/src/bot-native-commands.plugin-auth.test.ts"],
|
||||
},
|
||||
{
|
||||
env: {
|
||||
CI: "true",
|
||||
GITHUB_ACTIONS: "true",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(explanation.surface).toBe("channels");
|
||||
expect(explanation.isolate).toBe(true);
|
||||
expect(explanation.reasons).toContain("channels-timed-heavy");
|
||||
});
|
||||
|
||||
it("does not leak default-plan shard assignments into targeted units with the same id", () => {
|
||||
const artifacts = createExecutionArtifacts({});
|
||||
const plan = buildExecutionPlan(
|
||||
|
|
@ -573,13 +625,13 @@ describe("test planner", () => {
|
|||
|
||||
expect(manifest.jobs.buildArtifacts.enabled).toBe(true);
|
||||
expect(manifest.shardCounts.unit).toBe(4);
|
||||
expect(manifest.shardCounts.channels).toBe(3);
|
||||
expect(manifest.shardCounts.channels).toBe(4);
|
||||
expect(manifest.shardCounts.extensionFast).toBeGreaterThanOrEqual(4);
|
||||
expect(manifest.shardCounts.extensionFast).toBeLessThanOrEqual(5);
|
||||
expect(manifest.shardCounts.windows).toBe(6);
|
||||
expect(manifest.shardCounts.macosNode).toBe(9);
|
||||
expect(manifest.shardCounts.bun).toBe(6);
|
||||
expect(manifest.jobs.checks.matrix.include).toHaveLength(7);
|
||||
expect(manifest.jobs.checks.matrix.include).toHaveLength(8);
|
||||
expect(manifest.jobs.checksWindows.matrix.include).toHaveLength(6);
|
||||
expect(manifest.jobs.bunChecks.matrix.include).toHaveLength(6);
|
||||
expect(manifest.jobs.macosNode.matrix.include).toHaveLength(9);
|
||||
|
|
|
|||
Loading…
Reference in New Issue