test: split heavy extension test shards

This commit is contained in:
Peter Steinberger
2026-04-20 16:49:24 +01:00
parent 68b7666d7c
commit 0603ceba23
26 changed files with 503 additions and 104 deletions

View File

@@ -59,11 +59,13 @@ Scoped guides:
- Install: `pnpm install` (Bun supported; keep lockfiles/patches aligned if touched).
- Dev CLI: `pnpm openclaw ...` or `pnpm dev`.
- Build: `pnpm build`
- Smart local gate: `pnpm check:changed`
- Smart local gate: `pnpm check:changed` (scoped typecheck/lint/guards + relevant tests)
- Explain smart gate: `pnpm changed:lanes --json`
- Pre-commit view: `pnpm check:changed --staged`
- Normal full prod sweep: `pnpm check`
- Normal full prod sweep: `pnpm check` (prod typecheck/lint/guards, no tests)
- Full tests: `pnpm test`
- Changed tests only: `pnpm test:changed`
- Extension tests: `pnpm test:extensions` or `pnpm test extensions` = all extension shards; `pnpm test extensions/<id>` = one extension lane. Heavy channels/OpenAI have dedicated shards.
- Targeted tests: `pnpm test <path-or-filter> [vitest args...]`; do not call raw `vitest`.
- Coverage: `pnpm test:coverage`
- Format check/fix: `pnpm format:check` / `pnpm format`
@@ -92,7 +94,7 @@ Scoped guides:
- extension tests => extension test typecheck/tests only
- public SDK/plugin contract => extension prod/test validation too
- unknown root/config => all lanes
- Local loop: prefer `pnpm check:changed`; use `pnpm check` for full prod TS/lint sweep.
- Local loop: prefer `pnpm check:changed`; use `pnpm test:changed` for tests only; use `pnpm check` for full prod TS/lint sweep without tests.
- Landing on `main`: verify touched surface near landing; default bar is `pnpm check` + `pnpm test` when feasible.
- Hard build gate: run/pass `pnpm build` before push if build output, packaging, lazy/module boundaries, or published surfaces can change.
- Do not land related failing format/lint/type/build/tests. If failures are unrelated on latest `origin/main`, say so and give scoped proof.

View File

@@ -15,14 +15,13 @@ title: "Tests"
- `pnpm test:changed`: expands changed git paths into scoped Vitest lanes when the diff only touches routable source/test files. Config/setup changes still fall back to the native root projects run so wiring edits rerun broadly when needed.
- `pnpm changed:lanes`: shows the architectural lanes triggered by the diff against `origin/main`.
- `pnpm check:changed`: runs the smart changed gate for the diff against `origin/main`. It runs core work with core test lanes, extension work with extension test lanes, test-only work with test typecheck/tests only, and expands public Plugin SDK or plugin-contract changes to extension validation.
- `pnpm test`: routes explicit file/directory targets through scoped Vitest lanes. Untargeted runs now execute eleven sequential shard configs (`vitest.full-core-unit-src.config.ts`, `vitest.full-core-unit-security.config.ts`, `vitest.full-core-unit-ui.config.ts`, `vitest.full-core-unit-support.config.ts`, `vitest.full-core-support-boundary.config.ts`, `vitest.full-core-contracts.config.ts`, `vitest.full-core-bundled.config.ts`, `vitest.full-core-runtime.config.ts`, `vitest.full-agentic.config.ts`, `vitest.full-auto-reply.config.ts`, `vitest.full-extensions.config.ts`) instead of one giant root-project process.
- `pnpm test`: routes explicit file/directory targets through scoped Vitest lanes. Untargeted runs use fixed shard groups and expand to leaf configs for local parallel execution; the extension group always expands to the per-extension shard configs instead of one giant root-project process.
- Selected `plugin-sdk` and `commands` test files now route through dedicated light lanes that keep only `test/setup.ts`, leaving runtime-heavy cases on their existing lanes.
- Selected `plugin-sdk` and `commands` helper source files also map `pnpm test:changed` to explicit sibling tests in those light lanes, so small helper edits avoid rerunning the heavy runtime-backed suites.
- `auto-reply` now also splits into three dedicated configs (`core`, `top-level`, `reply`) so the reply harness does not dominate the lighter top-level status/token/helper tests.
- Base Vitest config now defaults to `pool: "threads"` and `isolate: false`, with the shared non-isolated runner enabled across the repo configs.
- `pnpm test:channels` runs `vitest.channels.config.ts`.
- `pnpm test:extensions` runs `vitest.extensions.config.ts`.
- `pnpm test:extensions`: runs extension/plugin suites.
- `pnpm test:extensions` and `pnpm test extensions` run all extension/plugin shards. Heavy channel extensions and OpenAI run as dedicated shards; other extension groups stay batched. Use `pnpm test extensions/<id>` for one bundled plugin lane.
- `pnpm test:perf:imports`: enables Vitest import-duration + import-breakdown reporting, while still using scoped lane routing for explicit file/directory targets.
- `pnpm test:perf:imports:changed`: same import profiling, but only for files changed since `origin/main`.
- `pnpm test:perf:changed:bench -- --ref <git-ref>` benchmarks the routed changed-mode path against the native root-project run for the same committed git diff.

View File

@@ -1437,7 +1437,7 @@
"test:e2e": "node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts",
"test:e2e:openshell": "OPENCLAW_E2E_OPENSHELL=1 node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/openshell/src/backend.e2e.test.ts",
"test:extension": "node scripts/test-extension.mjs",
"test:extensions": "node scripts/run-vitest.mjs run --config test/vitest/vitest.extensions.config.ts",
"test:extensions": "node scripts/test-projects.mjs extensions",
"test:extensions:batch": "node scripts/test-extension-batch.mjs",
"test:extensions:memory": "node scripts/profile-extension-memory.mjs",
"test:extensions:package-boundary": "node scripts/check-extension-package-tsc-boundary.mjs",

View File

@@ -3,6 +3,7 @@ import path from "node:path";
import { channelTestRoots } from "../../test/vitest/vitest.channel-paths.mjs";
import { isAcpxExtensionRoot } from "../../test/vitest/vitest.extension-acpx-paths.mjs";
import { isBlueBubblesExtensionRoot } from "../../test/vitest/vitest.extension-bluebubbles-paths.mjs";
import { resolveSplitChannelExtensionShard } from "../../test/vitest/vitest.extension-channel-split-paths.mjs";
import { isDiffsExtensionRoot } from "../../test/vitest/vitest.extension-diffs-paths.mjs";
import { isFeishuExtensionRoot } from "../../test/vitest/vitest.extension-feishu-paths.mjs";
import { isIrcExtensionRoot } from "../../test/vitest/vitest.extension-irc-paths.mjs";
@@ -11,7 +12,10 @@ import { isMattermostExtensionRoot } from "../../test/vitest/vitest.extension-ma
import { isMemoryExtensionRoot } from "../../test/vitest/vitest.extension-memory-paths.mjs";
import { isMessagingExtensionRoot } from "../../test/vitest/vitest.extension-messaging-paths.mjs";
import { isMsTeamsExtensionRoot } from "../../test/vitest/vitest.extension-msteams-paths.mjs";
import { isProviderExtensionRoot } from "../../test/vitest/vitest.extension-provider-paths.mjs";
import {
isProviderExtensionRoot,
isProviderOpenAiExtensionRoot,
} from "../../test/vitest/vitest.extension-provider-paths.mjs";
import { isTelegramExtensionRoot } from "../../test/vitest/vitest.extension-telegram-paths.mjs";
import { isVoiceCallExtensionRoot } from "../../test/vitest/vitest.extension-voice-call-paths.mjs";
import { isWhatsAppExtensionRoot } from "../../test/vitest/vitest.extension-whatsapp-paths.mjs";
@@ -111,6 +115,7 @@ export function resolveExtensionTestPlan(params = {}) {
const roots = [relativeExtensionDir];
const splitChannelShard = resolveSplitChannelExtensionShard(relativeExtensionDir);
const usesChannelConfig = roots.some((root) => channelTestRoots.includes(root));
const usesAcpxConfig = roots.some((root) => isAcpxExtensionRoot(root));
const usesDiffsConfig = roots.some((root) => isDiffsExtensionRoot(root));
@@ -126,40 +131,45 @@ export function resolveExtensionTestPlan(params = {}) {
const usesMemoryConfig = roots.some((root) => isMemoryExtensionRoot(root));
const usesMsTeamsConfig = roots.some((root) => isMsTeamsExtensionRoot(root));
const usesMessagingConfig = roots.some((root) => isMessagingExtensionRoot(root));
const usesProviderOpenAiConfig = roots.some((root) => isProviderOpenAiExtensionRoot(root));
const usesProviderConfig = roots.some((root) => isProviderExtensionRoot(root));
const config = usesChannelConfig
? "test/vitest/vitest.extension-channels.config.ts"
: usesAcpxConfig
? "test/vitest/vitest.extension-acpx.config.ts"
: usesDiffsConfig
? "test/vitest/vitest.extension-diffs.config.ts"
: usesBlueBubblesConfig
? "test/vitest/vitest.extension-bluebubbles.config.ts"
: usesFeishuConfig
? "test/vitest/vitest.extension-feishu.config.ts"
: usesIrcConfig
? "test/vitest/vitest.extension-irc.config.ts"
: usesMattermostConfig
? "test/vitest/vitest.extension-mattermost.config.ts"
: usesMatrixConfig
? "test/vitest/vitest.extension-matrix.config.ts"
: usesTelegramConfig
? "test/vitest/vitest.extension-telegram.config.ts"
: usesVoiceCallConfig
? "test/vitest/vitest.extension-voice-call.config.ts"
: usesWhatsAppConfig
? "test/vitest/vitest.extension-whatsapp.config.ts"
: usesZaloConfig
? "test/vitest/vitest.extension-zalo.config.ts"
: usesMemoryConfig
? "test/vitest/vitest.extension-memory.config.ts"
: usesMsTeamsConfig
? "test/vitest/vitest.extension-msteams.config.ts"
: usesMessagingConfig
? "test/vitest/vitest.extension-messaging.config.ts"
: usesProviderConfig
? "test/vitest/vitest.extension-providers.config.ts"
: "test/vitest/vitest.extensions.config.ts";
const config = splitChannelShard
? splitChannelShard.config
: usesChannelConfig
? "test/vitest/vitest.extension-channels.config.ts"
: usesAcpxConfig
? "test/vitest/vitest.extension-acpx.config.ts"
: usesDiffsConfig
? "test/vitest/vitest.extension-diffs.config.ts"
: usesBlueBubblesConfig
? "test/vitest/vitest.extension-bluebubbles.config.ts"
: usesFeishuConfig
? "test/vitest/vitest.extension-feishu.config.ts"
: usesIrcConfig
? "test/vitest/vitest.extension-irc.config.ts"
: usesMattermostConfig
? "test/vitest/vitest.extension-mattermost.config.ts"
: usesMatrixConfig
? "test/vitest/vitest.extension-matrix.config.ts"
: usesTelegramConfig
? "test/vitest/vitest.extension-telegram.config.ts"
: usesVoiceCallConfig
? "test/vitest/vitest.extension-voice-call.config.ts"
: usesWhatsAppConfig
? "test/vitest/vitest.extension-whatsapp.config.ts"
: usesZaloConfig
? "test/vitest/vitest.extension-zalo.config.ts"
: usesMemoryConfig
? "test/vitest/vitest.extension-memory.config.ts"
: usesMsTeamsConfig
? "test/vitest/vitest.extension-msteams.config.ts"
: usesMessagingConfig
? "test/vitest/vitest.extension-messaging.config.ts"
: usesProviderOpenAiConfig
? "test/vitest/vitest.extension-provider-openai.config.ts"
: usesProviderConfig
? "test/vitest/vitest.extension-providers.config.ts"
: "test/vitest/vitest.extensions.config.ts";
const testFileCount = roots.reduce(
(sum, root) => sum + countTestFiles(path.join(repoRoot, root)),
0,

View File

@@ -11,6 +11,7 @@ import {
applyParallelVitestCachePaths,
buildFullSuiteVitestRunPlans,
createVitestRunSpecs,
listFullExtensionVitestProjectConfigs,
parseTestProjectsArgs,
resolveParallelFullSuiteConcurrency,
resolveChangedTargetArgs,
@@ -33,7 +34,7 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
["test/vitest/vitest.agents.config.ts", 170],
["test/vitest/vitest.extension-voice-call.config.ts", 169],
["test/vitest/vitest.extensions.config.ts", 168],
["test/vitest/vitest.extension-channels.config.ts", 167],
["test/vitest/vitest.extension-provider-openai.config.ts", 167],
["test/vitest/vitest.runtime-config.config.ts", 166],
["test/vitest/vitest.contracts.config.ts", 165],
["test/vitest/vitest.tasks.config.ts", 165],
@@ -46,6 +47,7 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
["test/vitest/vitest.wizard.config.ts", 130],
["test/vitest/vitest.unit-src.config.ts", 125],
["test/vitest/vitest.extension-matrix.config.ts", 100],
["test/vitest/vitest.extension-discord.config.ts", 98],
["test/vitest/vitest.extension-providers.config.ts", 96],
["test/vitest/vitest.extension-telegram.config.ts", 94],
["test/vitest/vitest.extension-whatsapp.config.ts", 92],
@@ -54,6 +56,7 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
["test/vitest/vitest.media.config.ts", 84],
["test/vitest/vitest.plugins.config.ts", 82],
["test/vitest/vitest.bundled.config.ts", 80],
["test/vitest/vitest.extension-slack.config.ts", 78],
["test/vitest/vitest.commands-light.config.ts", 48],
["test/vitest/vitest.plugin-sdk.config.ts", 46],
["test/vitest/vitest.auto-reply-top-level.config.ts", 45],
@@ -70,6 +73,9 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
["test/vitest/vitest.extension-feishu.config.ts", 18],
["test/vitest/vitest.extension-mattermost.config.ts", 16],
["test/vitest/vitest.extension-messaging.config.ts", 14],
["test/vitest/vitest.extension-imessage.config.ts", 13],
["test/vitest/vitest.extension-line.config.ts", 12],
["test/vitest/vitest.extension-signal.config.ts", 11],
["test/vitest/vitest.extension-acpx.config.ts", 10],
["test/vitest/vitest.extension-diffs.config.ts", 8],
["test/vitest/vitest.extension-memory.config.ts", 6],
@@ -160,6 +166,19 @@ function orderFullSuiteSpecsForParallelRun(specs) {
});
}
function isFullExtensionsProjectRun(specs) {
const fullExtensionProjectConfigs = new Set(listFullExtensionVitestProjectConfigs());
return (
specs.length > 1 &&
specs.every(
(spec) =>
spec.watchMode === false &&
spec.includePatterns === null &&
fullExtensionProjectConfigs.has(spec.config),
)
);
}
async function runVitestSpecsParallel(specs, concurrency) {
let nextIndex = 0;
let exitCode = 0;
@@ -216,6 +235,11 @@ async function main() {
cwd: process.cwd(),
});
if (runSpecs.length === 0) {
console.error("[test] no changed test targets; skipping Vitest.");
return;
}
releaseLock = shouldAcquireLocalHeavyCheckLock(runSpecs, process.env)
? acquireLocalHeavyCheckLockSync({
cwd: process.cwd(),
@@ -228,7 +252,8 @@ async function main() {
targetArgs.length === 0 &&
changedTargetArgs === null &&
!runSpecs.some((spec) => spec.watchMode);
if (isFullSuiteRun) {
const isParallelShardRun = isFullSuiteRun || isFullExtensionsProjectRun(runSpecs);
if (isParallelShardRun) {
const concurrency = resolveParallelFullSuiteConcurrency(runSpecs.length, process.env);
if (concurrency > 1) {
const localFullSuiteProfile = resolveLocalFullSuiteProfile(process.env);

View File

@@ -35,6 +35,8 @@ export function resolveChangedTargetArgs(
listChangedPaths?: (baseRef: string, cwd: string) => string[],
): string[] | null;
export function listFullExtensionVitestProjectConfigs(): string[];
export function createVitestRunSpecs(
args: string[],
params?: {

View File

@@ -9,6 +9,7 @@ import {
} from "../test/vitest/vitest.commands-light-paths.mjs";
import { isAcpxExtensionRoot } from "../test/vitest/vitest.extension-acpx-paths.mjs";
import { isBlueBubblesExtensionRoot } from "../test/vitest/vitest.extension-bluebubbles-paths.mjs";
import { resolveSplitChannelExtensionShard } from "../test/vitest/vitest.extension-channel-split-paths.mjs";
import { isDiffsExtensionRoot } from "../test/vitest/vitest.extension-diffs-paths.mjs";
import { isFeishuExtensionRoot } from "../test/vitest/vitest.extension-feishu-paths.mjs";
import { isIrcExtensionRoot } from "../test/vitest/vitest.extension-irc-paths.mjs";
@@ -17,7 +18,10 @@ import { isMattermostExtensionRoot } from "../test/vitest/vitest.extension-matte
import { isMemoryExtensionRoot } from "../test/vitest/vitest.extension-memory-paths.mjs";
import { isMessagingExtensionRoot } from "../test/vitest/vitest.extension-messaging-paths.mjs";
import { isMsTeamsExtensionRoot } from "../test/vitest/vitest.extension-msteams-paths.mjs";
import { isProviderExtensionRoot } from "../test/vitest/vitest.extension-provider-paths.mjs";
import {
isProviderExtensionRoot,
isProviderOpenAiExtensionRoot,
} from "../test/vitest/vitest.extension-provider-paths.mjs";
import { isTelegramExtensionRoot } from "../test/vitest/vitest.extension-telegram-paths.mjs";
import { isVoiceCallExtensionRoot } from "../test/vitest/vitest.extension-voice-call-paths.mjs";
import { isWhatsAppExtensionRoot } from "../test/vitest/vitest.extension-whatsapp-paths.mjs";
@@ -32,6 +36,7 @@ import {
isBoundaryTestFile,
isBundledPluginDependentUnitTestFile,
} from "../test/vitest/vitest.unit-paths.mjs";
import { detectChangedLanes } from "./changed-lanes.mjs";
import { isCiLikeEnv, resolveLocalFullSuiteProfile } from "./lib/vitest-local-scheduling.mjs";
import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./run-vitest.mjs";
@@ -53,14 +58,21 @@ const EXTENSION_ACPX_VITEST_CONFIG = "test/vitest/vitest.extension-acpx.config.t
const EXTENSION_BLUEBUBBLES_VITEST_CONFIG = "test/vitest/vitest.extension-bluebubbles.config.ts";
const EXTENSION_CHANNELS_VITEST_CONFIG = "test/vitest/vitest.extension-channels.config.ts";
const EXTENSION_DIFFS_VITEST_CONFIG = "test/vitest/vitest.extension-diffs.config.ts";
const EXTENSION_DISCORD_VITEST_CONFIG = "test/vitest/vitest.extension-discord.config.ts";
const EXTENSION_FEISHU_VITEST_CONFIG = "test/vitest/vitest.extension-feishu.config.ts";
const EXTENSION_IMESSAGE_VITEST_CONFIG = "test/vitest/vitest.extension-imessage.config.ts";
const EXTENSION_IRC_VITEST_CONFIG = "test/vitest/vitest.extension-irc.config.ts";
const EXTENSION_LINE_VITEST_CONFIG = "test/vitest/vitest.extension-line.config.ts";
const EXTENSION_MATTERMOST_VITEST_CONFIG = "test/vitest/vitest.extension-mattermost.config.ts";
const EXTENSION_MATRIX_VITEST_CONFIG = "test/vitest/vitest.extension-matrix.config.ts";
const EXTENSION_MEMORY_VITEST_CONFIG = "test/vitest/vitest.extension-memory.config.ts";
const EXTENSION_MSTEAMS_VITEST_CONFIG = "test/vitest/vitest.extension-msteams.config.ts";
const EXTENSION_MESSAGING_VITEST_CONFIG = "test/vitest/vitest.extension-messaging.config.ts";
const EXTENSION_PROVIDER_OPENAI_VITEST_CONFIG =
"test/vitest/vitest.extension-provider-openai.config.ts";
const EXTENSION_PROVIDERS_VITEST_CONFIG = "test/vitest/vitest.extension-providers.config.ts";
const EXTENSION_SIGNAL_VITEST_CONFIG = "test/vitest/vitest.extension-signal.config.ts";
const EXTENSION_SLACK_VITEST_CONFIG = "test/vitest/vitest.extension-slack.config.ts";
const EXTENSION_TELEGRAM_VITEST_CONFIG = "test/vitest/vitest.extension-telegram.config.ts";
const EXTENSION_VOICE_CALL_VITEST_CONFIG = "test/vitest/vitest.extension-voice-call.config.ts";
const EXTENSION_WHATSAPP_VITEST_CONFIG = "test/vitest/vitest.extension-whatsapp.config.ts";
@@ -105,18 +117,25 @@ const VITEST_CONFIG_BY_KIND = {
daemon: DAEMON_VITEST_CONFIG,
e2e: E2E_VITEST_CONFIG,
extension: EXTENSIONS_VITEST_CONFIG,
extensionFull: FULL_EXTENSIONS_VITEST_CONFIG,
extensionAcpx: EXTENSION_ACPX_VITEST_CONFIG,
extensionBlueBubbles: EXTENSION_BLUEBUBBLES_VITEST_CONFIG,
extensionChannel: EXTENSION_CHANNELS_VITEST_CONFIG,
extensionDiffs: EXTENSION_DIFFS_VITEST_CONFIG,
extensionDiscord: EXTENSION_DISCORD_VITEST_CONFIG,
extensionFeishu: EXTENSION_FEISHU_VITEST_CONFIG,
extensionImessage: EXTENSION_IMESSAGE_VITEST_CONFIG,
extensionIrc: EXTENSION_IRC_VITEST_CONFIG,
extensionLine: EXTENSION_LINE_VITEST_CONFIG,
extensionMatrix: EXTENSION_MATRIX_VITEST_CONFIG,
extensionMattermost: EXTENSION_MATTERMOST_VITEST_CONFIG,
extensionMemory: EXTENSION_MEMORY_VITEST_CONFIG,
extensionMessaging: EXTENSION_MESSAGING_VITEST_CONFIG,
extensionMsTeams: EXTENSION_MSTEAMS_VITEST_CONFIG,
extensionProviderOpenAi: EXTENSION_PROVIDER_OPENAI_VITEST_CONFIG,
extensionProvider: EXTENSION_PROVIDERS_VITEST_CONFIG,
extensionSignal: EXTENSION_SIGNAL_VITEST_CONFIG,
extensionSlack: EXTENSION_SLACK_VITEST_CONFIG,
extensionTelegram: EXTENSION_TELEGRAM_VITEST_CONFIG,
extensionVoiceCall: EXTENSION_VOICE_CALL_VITEST_CONFIG,
extensionWhatsApp: EXTENSION_WHATSAPP_VITEST_CONFIG,
@@ -148,6 +167,7 @@ const BROAD_CHANGED_RERUN_PATTERNS = [
/^test\/setup(?:\.shared|\.extensions|-openclaw-runtime)?\.ts$/u,
/^vitest(?:\..+)?\.(?:config\.ts|paths\.mjs)$/u,
/^test\/vitest\/vitest(?:\..+)?\.(?:config\.ts|paths\.mjs)$/u,
/^test\/helpers\//u,
/^scripts\/run-vitest\.mjs$/u,
/^scripts\/test-projects(?:\.test-support)?\.mjs$/u,
];
@@ -260,7 +280,14 @@ function shouldKeepBroadChangedRun(changedPaths) {
}
function isRoutableChangedTarget(changedPath) {
return /^(?:src|test|extensions|ui|packages|apps)(?:\/|$)/u.test(changedPath);
return /^(?:src|test|extensions|ui|packages)(?:\/|$)/u.test(changedPath);
}
export function listFullExtensionVitestProjectConfigs() {
return (
fullSuiteVitestShards.find((shard) => shard.config === FULL_EXTENSIONS_VITEST_CONFIG)
?.projects ?? []
);
}
export function resolveChangedTargetArgs(
@@ -276,8 +303,16 @@ export function resolveChangedTargetArgs(
if (changedPaths.length === 0 || shouldKeepBroadChangedRun(changedPaths)) {
return null;
}
const changedLanes = detectChangedLanes(changedPaths);
if (changedLanes.lanes.all) {
return null;
}
const routablePaths = changedPaths.filter(isRoutableChangedTarget);
return routablePaths.length > 0 ? [...new Set(routablePaths)] : null;
const targets = [...routablePaths];
if (changedLanes.extensionImpactFromCore) {
targets.push("extensions");
}
return [...new Set(targets)];
}
function classifyTarget(arg, cwd) {
@@ -295,8 +330,18 @@ function classifyTarget(arg, cwd) {
) {
return "e2e";
}
if (relative === "extensions") {
return "extensionFull";
}
if (relative.startsWith("extensions/")) {
const extensionRoot = relative.split("/").slice(0, 2).join("/");
const splitChannelShard = resolveSplitChannelExtensionShard(extensionRoot);
if (splitChannelShard) {
return splitChannelShard.kind;
}
if (isProviderOpenAiExtensionRoot(extensionRoot)) {
return "extensionProviderOpenAi";
}
if (isChannelSurfaceTestFile(relative)) {
return "extensionChannel";
}
@@ -506,7 +551,11 @@ export function buildVitestRunPlans(
const changedTargetArgs =
targetArgs.length === 0 ? resolveChangedTargetArgs(args, cwd, listChangedPaths) : null;
const activeTargetArgs = changedTargetArgs ?? targetArgs;
const activeForwardedArgs = changedTargetArgs ? stripChangedArgs(forwardedArgs) : forwardedArgs;
const activeForwardedArgs =
changedTargetArgs !== null ? stripChangedArgs(forwardedArgs) : forwardedArgs;
if (changedTargetArgs !== null && activeTargetArgs.length === 0) {
return [];
}
if (activeTargetArgs.length === 0) {
return [
{
@@ -570,8 +619,11 @@ export function buildVitestRunPlans(
"extensionAcpx",
"extensionDiffs",
"extensionBlueBubbles",
"extensionDiscord",
"extensionFeishu",
"extensionImessage",
"extensionIrc",
"extensionLine",
"extensionMattermost",
"extensionChannel",
"extensionTelegram",
@@ -582,7 +634,11 @@ export function buildVitestRunPlans(
"extensionMemory",
"extensionMsTeams",
"extensionMessaging",
"extensionProviderOpenAi",
"extensionProvider",
"extensionSignal",
"extensionSlack",
"extensionFull",
"channel",
"extension",
];
@@ -592,6 +648,20 @@ export function buildVitestRunPlans(
if (!grouped || grouped.length === 0) {
continue;
}
if (kind === "extensionFull") {
const configs = watchMode
? [FULL_EXTENSIONS_VITEST_CONFIG]
: listFullExtensionVitestProjectConfigs();
for (const config of configs) {
plans.push({
config,
forwardedArgs: nonTargetArgs,
includePatterns: null,
watchMode,
});
}
continue;
}
const config = VITEST_CONFIG_BY_KIND[kind] ?? DEFAULT_VITEST_CONFIG;
const useCliTargetArgs =
kind === "e2e" ||

View File

@@ -785,12 +785,12 @@ describe("test-projects args", () => {
]);
});
it("routes direct channel extension file targets to the channels config", () => {
it("routes direct Discord extension file targets to the Discord config", () => {
expect(
buildVitestRunPlans(["extensions/discord/src/monitor/message-handler.preflight.test.ts"]),
).toEqual([
{
config: "test/vitest/vitest.extension-channels.config.ts",
config: "test/vitest/vitest.extension-discord.config.ts",
forwardedArgs: [],
includePatterns: ["extensions/discord/src/monitor/message-handler.preflight.test.ts"],
watchMode: false,
@@ -809,10 +809,10 @@ describe("test-projects args", () => {
]);
});
it("routes line extension targets to the extension channel config", () => {
it("routes line extension targets to the line config", () => {
expect(buildVitestRunPlans(["extensions/line/src/send.test.ts"])).toEqual([
{
config: "test/vitest/vitest.extension-channels.config.ts",
config: "test/vitest/vitest.extension-line.config.ts",
forwardedArgs: [],
includePatterns: ["extensions/line/src/send.test.ts"],
watchMode: false,
@@ -831,10 +831,10 @@ describe("test-projects args", () => {
]);
});
it("routes direct provider extension file targets to the extension providers config", () => {
it("routes direct OpenAI provider extension file targets to the OpenAI provider config", () => {
expect(buildVitestRunPlans(["extensions/openai/openai-codex-provider.test.ts"])).toEqual([
{
config: "test/vitest/vitest.extension-providers.config.ts",
config: "test/vitest/vitest.extension-provider-openai.config.ts",
forwardedArgs: [],
includePatterns: ["extensions/openai/openai-codex-provider.test.ts"],
watchMode: false,
@@ -869,7 +869,7 @@ describe("test-projects args", () => {
watchMode: false,
},
{
config: "test/vitest/vitest.extension-channels.config.ts",
config: "test/vitest/vitest.extension-discord.config.ts",
forwardedArgs: ["-t", "mention"],
includePatterns: ["extensions/discord/src/monitor/message-handler.preflight.test.ts"],
watchMode: false,
@@ -886,7 +886,7 @@ describe("test-projects args", () => {
...VITEST_NODE_PREFIX,
"run",
"--config",
"test/vitest/vitest.extension-channels.config.ts",
"test/vitest/vitest.extension-discord.config.ts",
]);
expect(spec?.includePatterns).toEqual([
"extensions/discord/src/monitor/message-handler.preflight.test.ts",

View File

@@ -33,12 +33,12 @@ function findExtensionWithoutTests() {
}
describe("scripts/test-extension.mjs", () => {
it("resolves channel-root extensions onto the channel vitest config", () => {
it("resolves split channel extensions onto their own vitest configs", () => {
const plan = resolveExtensionTestPlan({ targetArg: "slack", cwd: process.cwd() });
expect(plan.extensionId).toBe("slack");
expect(plan.extensionDir).toBe(bundledPluginRoot("slack"));
expect(plan.config).toBe("test/vitest/vitest.extension-channels.config.ts");
expect(plan.config).toBe("test/vitest/vitest.extension-slack.config.ts");
expect(plan.roots).toContain(bundledPluginRoot("slack"));
expect(plan.hasTests).toBe(true);
});
@@ -79,11 +79,11 @@ describe("scripts/test-extension.mjs", () => {
expect(plan.hasTests).toBe(true);
});
it("resolves provider extensions onto the provider vitest config", () => {
it("resolves OpenAI onto its own provider vitest config", () => {
const plan = resolveExtensionTestPlan({ targetArg: "openai", cwd: process.cwd() });
expect(plan.extensionId).toBe("openai");
expect(plan.config).toBe("test/vitest/vitest.extension-providers.config.ts");
expect(plan.config).toBe("test/vitest/vitest.extension-provider-openai.config.ts");
expect(plan.roots).toContain(bundledPluginRoot("openai"));
expect(plan.hasTests).toBe(true);
});
@@ -183,7 +183,7 @@ describe("scripts/test-extension.mjs", () => {
expect(plan.roots).toContain(bundledPluginRoot("line"));
expect(plan.roots).not.toContain("src/line");
expect(plan.config).toBe("test/vitest/vitest.extension-channels.config.ts");
expect(plan.config).toBe("test/vitest/vitest.extension-line.config.ts");
expect(plan.hasTests).toBe(true);
});
@@ -294,13 +294,6 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("bluebubbles")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-channels.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["line", "slack"],
roots: [bundledPluginRoot("slack"), bundledPluginRoot("line")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-diffs.config.ts",
estimatedCost: expect.any(Number),
@@ -322,6 +315,13 @@ describe("scripts/test-extension.mjs", () => {
roots: [bundledPluginRoot("irc")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-line.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["line"],
roots: [bundledPluginRoot("line")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-matrix.config.ts",
estimatedCost: expect.any(Number),
@@ -351,12 +351,19 @@ describe("scripts/test-extension.mjs", () => {
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-providers.config.ts",
config: "test/vitest/vitest.extension-provider-openai.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["openai"],
roots: [bundledPluginRoot("openai")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-slack.config.ts",
estimatedCost: expect.any(Number),
extensionIds: ["slack"],
roots: [bundledPluginRoot("slack")],
testFileCount: expect.any(Number),
},
{
config: "test/vitest/vitest.extension-telegram.config.ts",
estimatedCost: expect.any(Number),

View File

@@ -4,6 +4,7 @@ import {
applyParallelVitestCachePaths,
buildFullSuiteVitestRunPlans,
buildVitestRunPlans,
listFullExtensionVitestProjectConfigs,
shouldAcquireLocalHeavyCheckLock,
resolveChangedTargetArgs,
resolveParallelFullSuiteConcurrency,
@@ -28,12 +29,89 @@ describe("scripts/test-projects changed-target routing", () => {
).toBeNull();
});
it("ignores changed files that cannot map to test lanes", () => {
it("keeps the broad changed run for shared test helpers", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"test/helpers/channels/plugin.ts",
]),
).toBeNull();
});
it("keeps the broad changed run for unknown root surfaces", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"unknown/file.txt",
]),
).toBeNull();
});
it("skips changed docs files that cannot map to test lanes", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"docs/help/testing.md",
]),
).toBeNull();
).toEqual([]);
});
it("skips root agent guidance changes instead of broad-running tests", () => {
expect(
buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => ["AGENTS.md"]),
).toEqual([]);
});
it("skips app-only changes because app tests are separate from Vitest lanes", () => {
expect(
buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"apps/macos/OpenClaw/AppDelegate.swift",
]),
).toEqual([]);
});
it("adds extension tests for public plugin SDK changes", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"src/plugin-sdk/provider-entry.ts",
]);
expect(plans).toEqual([
{
config: "test/vitest/vitest.unit-fast.config.ts",
forwardedArgs: [],
includePatterns: ["src/plugin-sdk/provider-entry.test.ts"],
watchMode: false,
},
...listFullExtensionVitestProjectConfigs().map((config) => ({
config,
forwardedArgs: [],
includePatterns: null,
watchMode: false,
})),
]);
});
it("routes LM Studio changes to the provider extension lane", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"extensions/lmstudio/src/runtime.ts",
]);
expect(plans).toEqual([
{
config: "test/vitest/vitest.extension-providers.config.ts",
forwardedArgs: [],
includePatterns: ["extensions/lmstudio/src/**/*.test.ts"],
watchMode: false,
},
]);
});
it("routes the top-level extensions target to every extension shard", () => {
expect(buildVitestRunPlans(["extensions"], process.cwd())).toEqual(
listFullExtensionVitestProjectConfigs().map((config) => ({
config,
forwardedArgs: [],
includePatterns: null,
watchMode: false,
})),
);
});
it("narrows default-lane changed source files to include globs", () => {
@@ -115,7 +193,7 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("routes changed plugin-sdk source allowlist files to sibling light tests", () => {
it("keeps changed plugin-sdk allowlist files on sibling light tests plus extension tests", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"src/plugin-sdk/provider-entry.ts",
]);
@@ -127,6 +205,12 @@ describe("scripts/test-projects changed-target routing", () => {
includePatterns: ["src/plugin-sdk/provider-entry.test.ts"],
watchMode: false,
},
...listFullExtensionVitestProjectConfigs().map((config) => ({
config,
forwardedArgs: [],
includePatterns: null,
watchMode: false,
})),
]);
});
@@ -149,7 +233,7 @@ describe("scripts/test-projects changed-target routing", () => {
]);
});
it("keeps non-allowlisted plugin-sdk source files on the heavy lane", () => {
it("keeps non-allowlisted plugin-sdk source files on the heavy lane plus extension tests", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"src/plugin-sdk/facade-runtime.ts",
]);
@@ -161,6 +245,12 @@ describe("scripts/test-projects changed-target routing", () => {
includePatterns: ["src/plugin-sdk/**/*.test.ts"],
watchMode: false,
},
...listFullExtensionVitestProjectConfigs().map((config) => ({
config,
forwardedArgs: [],
includePatterns: null,
watchMode: false,
})),
]);
});
@@ -316,16 +406,21 @@ describe("scripts/test-projects full-suite sharding", () => {
"test/vitest/vitest.full-auto-reply.config.ts",
"test/vitest/vitest.extension-acpx.config.ts",
"test/vitest/vitest.extension-bluebubbles.config.ts",
"test/vitest/vitest.extension-channels.config.ts",
"test/vitest/vitest.extension-diffs.config.ts",
"test/vitest/vitest.extension-discord.config.ts",
"test/vitest/vitest.extension-feishu.config.ts",
"test/vitest/vitest.extension-imessage.config.ts",
"test/vitest/vitest.extension-irc.config.ts",
"test/vitest/vitest.extension-line.config.ts",
"test/vitest/vitest.extension-mattermost.config.ts",
"test/vitest/vitest.extension-matrix.config.ts",
"test/vitest/vitest.extension-memory.config.ts",
"test/vitest/vitest.extension-messaging.config.ts",
"test/vitest/vitest.extension-msteams.config.ts",
"test/vitest/vitest.extension-provider-openai.config.ts",
"test/vitest/vitest.extension-providers.config.ts",
"test/vitest/vitest.extension-signal.config.ts",
"test/vitest/vitest.extension-slack.config.ts",
"test/vitest/vitest.extension-telegram.config.ts",
"test/vitest/vitest.extension-voice-call.config.ts",
"test/vitest/vitest.extension-whatsapp.config.ts",
@@ -499,16 +594,21 @@ describe("scripts/test-projects full-suite sharding", () => {
"test/vitest/vitest.auto-reply-reply.config.ts",
"test/vitest/vitest.extension-acpx.config.ts",
"test/vitest/vitest.extension-bluebubbles.config.ts",
"test/vitest/vitest.extension-channels.config.ts",
"test/vitest/vitest.extension-diffs.config.ts",
"test/vitest/vitest.extension-discord.config.ts",
"test/vitest/vitest.extension-feishu.config.ts",
"test/vitest/vitest.extension-imessage.config.ts",
"test/vitest/vitest.extension-irc.config.ts",
"test/vitest/vitest.extension-line.config.ts",
"test/vitest/vitest.extension-mattermost.config.ts",
"test/vitest/vitest.extension-matrix.config.ts",
"test/vitest/vitest.extension-memory.config.ts",
"test/vitest/vitest.extension-messaging.config.ts",
"test/vitest/vitest.extension-msteams.config.ts",
"test/vitest/vitest.extension-provider-openai.config.ts",
"test/vitest/vitest.extension-providers.config.ts",
"test/vitest/vitest.extension-signal.config.ts",
"test/vitest/vitest.extension-slack.config.ts",
"test/vitest/vitest.extension-telegram.config.ts",
"test/vitest/vitest.extension-voice-call.config.ts",
"test/vitest/vitest.extension-whatsapp.config.ts",

View File

@@ -22,14 +22,20 @@ import { createExtensionAcpxVitestConfig } from "./vitest/vitest.extension-acpx.
import { createExtensionBlueBubblesVitestConfig } from "./vitest/vitest.extension-bluebubbles.config.ts";
import { createExtensionChannelsVitestConfig } from "./vitest/vitest.extension-channels.config.ts";
import { createExtensionDiffsVitestConfig } from "./vitest/vitest.extension-diffs.config.ts";
import { createExtensionDiscordVitestConfig } from "./vitest/vitest.extension-discord.config.ts";
import { createExtensionFeishuVitestConfig } from "./vitest/vitest.extension-feishu.config.ts";
import { createExtensionImessageVitestConfig } from "./vitest/vitest.extension-imessage.config.ts";
import { createExtensionIrcVitestConfig } from "./vitest/vitest.extension-irc.config.ts";
import { createExtensionLineVitestConfig } from "./vitest/vitest.extension-line.config.ts";
import { createExtensionMatrixVitestConfig } from "./vitest/vitest.extension-matrix.config.ts";
import { createExtensionMattermostVitestConfig } from "./vitest/vitest.extension-mattermost.config.ts";
import { createExtensionMemoryVitestConfig } from "./vitest/vitest.extension-memory.config.ts";
import { createExtensionMessagingVitestConfig } from "./vitest/vitest.extension-messaging.config.ts";
import { createExtensionMsTeamsVitestConfig } from "./vitest/vitest.extension-msteams.config.ts";
import { createExtensionProviderOpenAiVitestConfig } from "./vitest/vitest.extension-provider-openai.config.ts";
import { createExtensionProvidersVitestConfig } from "./vitest/vitest.extension-providers.config.ts";
import { createExtensionSignalVitestConfig } from "./vitest/vitest.extension-signal.config.ts";
import { createExtensionSlackVitestConfig } from "./vitest/vitest.extension-slack.config.ts";
import { createExtensionTelegramVitestConfig } from "./vitest/vitest.extension-telegram.config.ts";
import { createExtensionVoiceCallVitestConfig } from "./vitest/vitest.extension-voice-call.config.ts";
import { createExtensionWhatsAppVitestConfig } from "./vitest/vitest.extension-whatsapp.config.ts";
@@ -183,14 +189,20 @@ describe("scoped vitest configs", () => {
const defaultExtensionBlueBubblesConfig = createExtensionBlueBubblesVitestConfig({});
const defaultExtensionChannelsConfig = createExtensionChannelsVitestConfig({});
const defaultExtensionDiffsConfig = createExtensionDiffsVitestConfig({});
const defaultExtensionDiscordConfig = createExtensionDiscordVitestConfig({});
const defaultExtensionFeishuConfig = createExtensionFeishuVitestConfig({});
const defaultExtensionImessageConfig = createExtensionImessageVitestConfig({});
const defaultExtensionIrcConfig = createExtensionIrcVitestConfig({});
const defaultExtensionLineConfig = createExtensionLineVitestConfig({});
const defaultExtensionMatrixConfig = createExtensionMatrixVitestConfig({});
const defaultExtensionMattermostConfig = createExtensionMattermostVitestConfig({});
const defaultExtensionMemoryConfig = createExtensionMemoryVitestConfig({});
const defaultExtensionMsTeamsConfig = createExtensionMsTeamsVitestConfig({});
const defaultExtensionMessagingConfig = createExtensionMessagingVitestConfig({});
const defaultExtensionProviderOpenAiConfig = createExtensionProviderOpenAiVitestConfig({});
const defaultExtensionProvidersConfig = createExtensionProvidersVitestConfig({});
const defaultExtensionSignalConfig = createExtensionSignalVitestConfig({});
const defaultExtensionSlackConfig = createExtensionSlackVitestConfig({});
const defaultExtensionTelegramConfig = createExtensionTelegramVitestConfig({});
const defaultExtensionVoiceCallConfig = createExtensionVoiceCallVitestConfig({});
const defaultExtensionWhatsAppConfig = createExtensionWhatsAppVitestConfig({});
@@ -230,7 +242,13 @@ describe("scoped vitest configs", () => {
defaultAcpConfig,
defaultExtensionsConfig,
defaultExtensionChannelsConfig,
defaultExtensionDiscordConfig,
defaultExtensionImessageConfig,
defaultExtensionLineConfig,
defaultExtensionProviderOpenAiConfig,
defaultExtensionProvidersConfig,
defaultExtensionSignalConfig,
defaultExtensionSlackConfig,
defaultInfraConfig,
defaultAutoReplyConfig,
defaultAutoReplyCoreConfig,
@@ -337,17 +355,17 @@ describe("scoped vitest configs", () => {
);
});
it("normalizes extension channel include patterns relative to the scoped dir", () => {
expect(defaultExtensionChannelsConfig.test?.dir).toBe(path.join(process.cwd(), "extensions"));
expect(defaultExtensionChannelsConfig.test?.include).toEqual(
expect.arrayContaining([
"discord/**/*.test.ts",
"line/**/*.test.ts",
"slack/**/*.test.ts",
"signal/**/*.test.ts",
"imessage/**/*.test.ts",
]),
);
it("normalizes split extension channel include patterns relative to the scoped dir", () => {
for (const [config, include] of [
[defaultExtensionDiscordConfig, "discord/**/*.test.ts"],
[defaultExtensionLineConfig, "line/**/*.test.ts"],
[defaultExtensionSlackConfig, "slack/**/*.test.ts"],
[defaultExtensionSignalConfig, "signal/**/*.test.ts"],
[defaultExtensionImessageConfig, "imessage/**/*.test.ts"],
] as const) {
expect(config.test?.dir).toBe(path.join(process.cwd(), "extensions"));
expect(config.test?.include).toEqual([include]);
}
});
it("normalizes bluebubbles extension include patterns relative to the scoped dir", () => {
@@ -385,8 +403,13 @@ describe("scoped vitest configs", () => {
it("normalizes extension provider include patterns relative to the scoped dir", () => {
expect(defaultExtensionProvidersConfig.test?.dir).toBe(path.join(process.cwd(), "extensions"));
expect(defaultExtensionProvidersConfig.test?.include).toEqual(
expect.arrayContaining(["openai/**/*.test.ts", "xai/**/*.test.ts", "google/**/*.test.ts"]),
expect.arrayContaining(["xai/**/*.test.ts", "google/**/*.test.ts"]),
);
expect(defaultExtensionProvidersConfig.test?.include).not.toContain("openai/**/*.test.ts");
expect(defaultExtensionProviderOpenAiConfig.test?.dir).toBe(
path.join(process.cwd(), "extensions"),
);
expect(defaultExtensionProviderOpenAiConfig.test?.include).toEqual(["openai/**/*.test.ts"]);
});
it("normalizes extension messaging include patterns relative to the scoped dir", () => {

View File

@@ -1,8 +1,6 @@
import path from "node:path";
import {
BUNDLED_PLUGIN_PATH_PREFIX,
bundledPluginRoot,
} from "../../scripts/lib/bundled-plugin-paths.mjs";
import { BUNDLED_PLUGIN_PATH_PREFIX } from "../../scripts/lib/bundled-plugin-paths.mjs";
import { splitChannelExtensionTestRoots } from "./vitest.extension-channel-split-paths.mjs";
const normalizeRepoPath = (value) => value.split(path.sep).join("/");
@@ -10,17 +8,13 @@ export const extensionRoutedChannelTestFiles = [];
const extensionRoutedChannelTestFileSet = new Set(extensionRoutedChannelTestFiles);
export const channelTestRoots = [
"src/channels",
bundledPluginRoot("discord"),
bundledPluginRoot("slack"),
bundledPluginRoot("signal"),
bundledPluginRoot("imessage"),
bundledPluginRoot("line"),
];
export const channelTestRoots = ["src/channels", ...splitChannelExtensionTestRoots];
export const extensionChannelTestRoots = channelTestRoots.filter((root) =>
root.startsWith(BUNDLED_PLUGIN_PATH_PREFIX),
const splitChannelExtensionTestRootSet = new Set(splitChannelExtensionTestRoots);
export const extensionChannelTestRoots = channelTestRoots.filter(
(root) =>
root.startsWith(BUNDLED_PLUGIN_PATH_PREFIX) && !splitChannelExtensionTestRootSet.has(root),
);
export const coreChannelTestRoots = channelTestRoots.filter(
(root) => !root.startsWith(BUNDLED_PLUGIN_PATH_PREFIX),

View File

@@ -48,16 +48,21 @@ export const rootVitestProjects = [
"test/vitest/vitest.channels.config.ts",
"test/vitest/vitest.extension-acpx.config.ts",
"test/vitest/vitest.extension-bluebubbles.config.ts",
"test/vitest/vitest.extension-channels.config.ts",
"test/vitest/vitest.extension-diffs.config.ts",
"test/vitest/vitest.extension-discord.config.ts",
"test/vitest/vitest.extension-feishu.config.ts",
"test/vitest/vitest.extension-imessage.config.ts",
"test/vitest/vitest.extension-irc.config.ts",
"test/vitest/vitest.extension-line.config.ts",
"test/vitest/vitest.extension-mattermost.config.ts",
"test/vitest/vitest.extension-matrix.config.ts",
"test/vitest/vitest.extension-memory.config.ts",
"test/vitest/vitest.extension-msteams.config.ts",
"test/vitest/vitest.extension-messaging.config.ts",
"test/vitest/vitest.extension-provider-openai.config.ts",
"test/vitest/vitest.extension-providers.config.ts",
"test/vitest/vitest.extension-signal.config.ts",
"test/vitest/vitest.extension-slack.config.ts",
"test/vitest/vitest.extension-telegram.config.ts",
"test/vitest/vitest.extension-voice-call.config.ts",
"test/vitest/vitest.extension-whatsapp.config.ts",

View File

@@ -0,0 +1,14 @@
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export function createSingleChannelExtensionVitestConfig(
extensionId: string,
env: Record<string, string | undefined> = process.env,
) {
return createScopedVitestConfig([`${extensionId}/**/*.test.ts`], {
dir: "extensions",
env,
name: `extension-${extensionId}`,
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
});
}

View File

@@ -0,0 +1,44 @@
import { bundledPluginRoot } from "../../scripts/lib/bundled-plugin-paths.mjs";
export const splitChannelExtensionShardSpecs = [
{
id: "discord",
kind: "extensionDiscord",
config: "test/vitest/vitest.extension-discord.config.ts",
},
{
id: "slack",
kind: "extensionSlack",
config: "test/vitest/vitest.extension-slack.config.ts",
},
{
id: "signal",
kind: "extensionSignal",
config: "test/vitest/vitest.extension-signal.config.ts",
},
{
id: "imessage",
kind: "extensionImessage",
config: "test/vitest/vitest.extension-imessage.config.ts",
},
{
id: "line",
kind: "extensionLine",
config: "test/vitest/vitest.extension-line.config.ts",
},
];
export const splitChannelExtensionTestRoots = splitChannelExtensionShardSpecs.map((spec) =>
bundledPluginRoot(spec.id),
);
export function resolveSplitChannelExtensionShard(root) {
const normalizedRoot = root.replaceAll("\\", "/");
return splitChannelExtensionShardSpecs.find(
(spec) => bundledPluginRoot(spec.id) === normalizedRoot,
);
}
export function isSplitChannelExtensionRoot(root) {
return Boolean(resolveSplitChannelExtensionShard(root));
}

View File

@@ -0,0 +1,9 @@
import { createSingleChannelExtensionVitestConfig } from "./vitest.extension-channel-single-config.ts";
export function createExtensionDiscordVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createSingleChannelExtensionVitestConfig("discord", env);
}
export default createExtensionDiscordVitestConfig();

View File

@@ -0,0 +1,9 @@
import { createSingleChannelExtensionVitestConfig } from "./vitest.extension-channel-single-config.ts";
export function createExtensionImessageVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createSingleChannelExtensionVitestConfig("imessage", env);
}
export default createExtensionImessageVitestConfig();

View File

@@ -0,0 +1,9 @@
import { createSingleChannelExtensionVitestConfig } from "./vitest.extension-channel-single-config.ts";
export function createExtensionLineVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createSingleChannelExtensionVitestConfig("line", env);
}
export default createExtensionLineVitestConfig();

View File

@@ -0,0 +1,27 @@
import { providerOpenAiExtensionTestRoots } from "./vitest.extension-provider-paths.mjs";
import { loadPatternListFromEnv } from "./vitest.pattern-file.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
export function loadIncludePatternsFromEnv(
env: Record<string, string | undefined> = process.env,
): string[] | null {
return loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
}
export function createExtensionProviderOpenAiVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createScopedVitestConfig(
loadIncludePatternsFromEnv(env) ??
providerOpenAiExtensionTestRoots.map((root) => `${root}/**/*.test.ts`),
{
dir: "extensions",
env,
name: "extension-provider-openai",
passWithNoTests: true,
setupFiles: ["test/setup.extensions.ts"],
},
);
}
export default createExtensionProviderOpenAiVitestConfig();

View File

@@ -14,6 +14,7 @@ export const providerExtensionIds = [
"groq",
"huggingface",
"kimi-coding",
"lmstudio",
"microsoft",
"microsoft-foundry",
"minimax",
@@ -22,7 +23,6 @@ export const providerExtensionIds = [
"moonshot",
"nvidia",
"ollama",
"openai",
"openrouter",
"qianfan",
"stepfun",
@@ -33,8 +33,17 @@ export const providerExtensionIds = [
"zai",
];
export const providerOpenAiExtensionIds = ["openai"];
export const providerExtensionTestRoots = providerExtensionIds.map((id) => bundledPluginRoot(id));
export const providerOpenAiExtensionTestRoots = providerOpenAiExtensionIds.map((id) =>
bundledPluginRoot(id),
);
export function isProviderExtensionRoot(root) {
return providerExtensionTestRoots.includes(root);
}
export function isProviderOpenAiExtensionRoot(root) {
return providerOpenAiExtensionTestRoots.includes(root);
}

View File

@@ -0,0 +1,9 @@
import { createSingleChannelExtensionVitestConfig } from "./vitest.extension-channel-single-config.ts";
export function createExtensionSignalVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createSingleChannelExtensionVitestConfig("signal", env);
}
export default createExtensionSignalVitestConfig();

View File

@@ -0,0 +1,9 @@
import { createSingleChannelExtensionVitestConfig } from "./vitest.extension-channel-single-config.ts";
export function createExtensionSlackVitestConfig(
env: Record<string, string | undefined> = process.env,
) {
return createSingleChannelExtensionVitestConfig("slack", env);
}
export default createExtensionSlackVitestConfig();

View File

@@ -10,7 +10,10 @@ import { mattermostExtensionTestRoots } from "./vitest.extension-mattermost-path
import { memoryExtensionTestRoots } from "./vitest.extension-memory-paths.mjs";
import { messagingExtensionTestRoots } from "./vitest.extension-messaging-paths.mjs";
import { msTeamsExtensionTestRoots } from "./vitest.extension-msteams-paths.mjs";
import { providerExtensionTestRoots } from "./vitest.extension-provider-paths.mjs";
import {
providerExtensionTestRoots,
providerOpenAiExtensionTestRoots,
} from "./vitest.extension-provider-paths.mjs";
import { telegramExtensionTestRoots } from "./vitest.extension-telegram-paths.mjs";
import { voiceCallExtensionTestRoots } from "./vitest.extension-voice-call-paths.mjs";
import { whatsAppExtensionTestRoots } from "./vitest.extension-whatsapp-paths.mjs";
@@ -47,6 +50,7 @@ export function createExtensionsVitestConfig(
...memoryExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...messagingExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...msTeamsExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...providerOpenAiExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...providerExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...telegramExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
...voiceCallExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),

View File

@@ -61,14 +61,20 @@ const SCOPED_PROJECT_GROUP_ORDER_BY_NAME = new Map(
"extension-bluebubbles",
"extension-channels",
"extension-diffs",
"extension-discord",
"extension-feishu",
"extension-imessage",
"extension-irc",
"extension-line",
"extension-mattermost",
"extension-matrix",
"extension-memory",
"extension-messaging",
"extension-msteams",
"extension-provider-openai",
"extension-providers",
"extension-signal",
"extension-slack",
"extension-telegram",
"extension-voice-call",
"extension-whatsapp",

View File

@@ -142,13 +142,18 @@ export const sharedVitestConfig = {
"test/vitest/vitest.extension-acpx.config.ts",
"test/vitest/vitest.extension-bluebubbles-paths.mjs",
"test/vitest/vitest.extension-bluebubbles.config.ts",
"test/vitest/vitest.extension-channel-single-config.ts",
"test/vitest/vitest.extension-channel-split-paths.mjs",
"test/vitest/vitest.extension-channels.config.ts",
"test/vitest/vitest.extension-diffs-paths.mjs",
"test/vitest/vitest.extension-diffs.config.ts",
"test/vitest/vitest.extension-discord.config.ts",
"test/vitest/vitest.extension-feishu-paths.mjs",
"test/vitest/vitest.extension-feishu.config.ts",
"test/vitest/vitest.extension-imessage.config.ts",
"test/vitest/vitest.extension-irc-paths.mjs",
"test/vitest/vitest.extension-irc.config.ts",
"test/vitest/vitest.extension-line.config.ts",
"test/vitest/vitest.extension-mattermost-paths.mjs",
"test/vitest/vitest.extension-mattermost.config.ts",
"test/vitest/vitest.extension-matrix-paths.mjs",
@@ -195,7 +200,10 @@ export const sharedVitestConfig = {
"test/vitest/vitest.extension-zalo-paths.mjs",
"test/vitest/vitest.extension-zalo.config.ts",
"test/vitest/vitest.extension-provider-paths.mjs",
"test/vitest/vitest.extension-provider-openai.config.ts",
"test/vitest/vitest.extension-providers.config.ts",
"test/vitest/vitest.extension-signal.config.ts",
"test/vitest/vitest.extension-slack.config.ts",
"test/vitest/vitest.logging.config.ts",
"test/vitest/vitest.process.config.ts",
"test/vitest/vitest.tasks.config.ts",

View File

@@ -103,16 +103,21 @@ export const fullSuiteVitestShards = [
projects: [
"test/vitest/vitest.extension-acpx.config.ts",
"test/vitest/vitest.extension-bluebubbles.config.ts",
"test/vitest/vitest.extension-channels.config.ts",
"test/vitest/vitest.extension-diffs.config.ts",
"test/vitest/vitest.extension-discord.config.ts",
"test/vitest/vitest.extension-feishu.config.ts",
"test/vitest/vitest.extension-imessage.config.ts",
"test/vitest/vitest.extension-irc.config.ts",
"test/vitest/vitest.extension-line.config.ts",
"test/vitest/vitest.extension-mattermost.config.ts",
"test/vitest/vitest.extension-matrix.config.ts",
"test/vitest/vitest.extension-memory.config.ts",
"test/vitest/vitest.extension-messaging.config.ts",
"test/vitest/vitest.extension-msteams.config.ts",
"test/vitest/vitest.extension-provider-openai.config.ts",
"test/vitest/vitest.extension-providers.config.ts",
"test/vitest/vitest.extension-signal.config.ts",
"test/vitest/vitest.extension-slack.config.ts",
"test/vitest/vitest.extension-telegram.config.ts",
"test/vitest/vitest.extension-voice-call.config.ts",
"test/vitest/vitest.extension-whatsapp.config.ts",