mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(test): route extension tests through scoped paths
This commit is contained in:
parent
743051d400
commit
92242f4f68
8 changed files with 425 additions and 78 deletions
|
|
@ -6,6 +6,7 @@ import { channelTestRoots } from "../../test/vitest/vitest.channel-paths.mjs";
|
|||
import { isAcpxExtensionRoot } from "../../test/vitest/vitest.extension-acpx-paths.mjs";
|
||||
import { isBrowserExtensionRoot } from "../../test/vitest/vitest.extension-browser-paths.mjs";
|
||||
import { resolveSplitChannelExtensionShard } from "../../test/vitest/vitest.extension-channel-split-paths.mjs";
|
||||
import { isCodexExtensionRoot } from "../../test/vitest/vitest.extension-codex-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";
|
||||
|
|
@ -38,6 +39,7 @@ const EXTENSION_TEST_COST_MULTIPLIERS = {
|
|||
// suites vary widely, and file count alone leaves long tail shards.
|
||||
"test/vitest/vitest.extension-acpx.config.ts": 0.75,
|
||||
"test/vitest/vitest.extension-browser.config.ts": 0.5,
|
||||
"test/vitest/vitest.extension-codex.config.ts": 1.3,
|
||||
"test/vitest/vitest.extension-diffs.config.ts": 0.6,
|
||||
"test/vitest/vitest.extension-discord.config.ts": 0.62,
|
||||
"test/vitest/vitest.extension-feishu.config.ts": 0.18,
|
||||
|
|
@ -207,6 +209,7 @@ export function resolveExtensionTestPlan(params = {}) {
|
|||
const usesChannelConfig = roots.some((root) => channelTestRoots.includes(root));
|
||||
const usesAcpxConfig = roots.some((root) => isAcpxExtensionRoot(root));
|
||||
const usesBrowserConfig = roots.some((root) => isBrowserExtensionRoot(root));
|
||||
const usesCodexConfig = roots.some((root) => isCodexExtensionRoot(root));
|
||||
const usesDiffsConfig = roots.some((root) => isDiffsExtensionRoot(root));
|
||||
const usesFeishuConfig = roots.some((root) => isFeishuExtensionRoot(root));
|
||||
const usesIrcConfig = roots.some((root) => isIrcExtensionRoot(root));
|
||||
|
|
@ -232,6 +235,8 @@ export function resolveExtensionTestPlan(params = {}) {
|
|||
? "test/vitest/vitest.extension-acpx.config.ts"
|
||||
: usesBrowserConfig
|
||||
? "test/vitest/vitest.extension-browser.config.ts"
|
||||
: usesCodexConfig
|
||||
? "test/vitest/vitest.extension-codex.config.ts"
|
||||
: usesDiffsConfig
|
||||
? "test/vitest/vitest.extension-diffs.config.ts"
|
||||
: usesFeishuConfig
|
||||
|
|
|
|||
97
scripts/lib/extension-vitest-paths.mjs
Normal file
97
scripts/lib/extension-vitest-paths.mjs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import path from "node:path";
|
||||
|
||||
const EXTENSIONS_PATH_PREFIX = "extensions/";
|
||||
const repoRoot = path.resolve(import.meta.dirname, "../..");
|
||||
const VITEST_VALUE_FLAGS = new Set([
|
||||
"-c",
|
||||
"-r",
|
||||
"-t",
|
||||
"--browser",
|
||||
"--changed",
|
||||
"--config",
|
||||
"--coverage.all",
|
||||
"--coverage.exclude",
|
||||
"--coverage.extension",
|
||||
"--coverage.include",
|
||||
"--coverage.provider",
|
||||
"--coverage.reporter",
|
||||
"--coverage.reportsDirectory",
|
||||
"--dir",
|
||||
"--environment",
|
||||
"--environmentOptions",
|
||||
"--hookTimeout",
|
||||
"--inspect",
|
||||
"--inspectBrk",
|
||||
"--maxConcurrency",
|
||||
"--maxWorkers",
|
||||
"--minWorkers",
|
||||
"--mode",
|
||||
"--name",
|
||||
"--outputFile",
|
||||
"--pool",
|
||||
"--project",
|
||||
"--reporter",
|
||||
"--retry",
|
||||
"--root",
|
||||
"--sequence",
|
||||
"--shard",
|
||||
"--testNamePattern",
|
||||
"--testTimeout",
|
||||
"--workspace",
|
||||
]);
|
||||
|
||||
export function normalizeRelativePath(inputPath, cwd = process.cwd()) {
|
||||
const absolutePath = path.isAbsolute(inputPath)
|
||||
? inputPath
|
||||
: inputPath.startsWith(EXTENSIONS_PATH_PREFIX)
|
||||
? path.resolve(repoRoot, inputPath)
|
||||
: path.resolve(cwd, inputPath);
|
||||
const repoRelative = path.relative(repoRoot, absolutePath).split(path.sep).join("/");
|
||||
return repoRelative === ".." || repoRelative.startsWith("../")
|
||||
? inputPath.split(path.sep).join("/")
|
||||
: repoRelative;
|
||||
}
|
||||
|
||||
export function relativizeExtensionVitestPath(inputPath, cwd = process.cwd()) {
|
||||
const normalized = normalizeRelativePath(inputPath, cwd);
|
||||
return normalized.startsWith(EXTENSIONS_PATH_PREFIX)
|
||||
? normalized.slice(EXTENSIONS_PATH_PREFIX.length)
|
||||
: normalized;
|
||||
}
|
||||
|
||||
export function relativizeExtensionVitestArgs(vitestArgs, cwd = process.cwd()) {
|
||||
const args = [];
|
||||
for (let index = 0; index < vitestArgs.length; index += 1) {
|
||||
const arg = vitestArgs[index];
|
||||
if (arg === "--exclude") {
|
||||
const value = vitestArgs[index + 1];
|
||||
args.push(arg);
|
||||
if (value) {
|
||||
args.push(relativizeExtensionVitestPath(value, cwd));
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (VITEST_VALUE_FLAGS.has(arg)) {
|
||||
args.push(arg);
|
||||
const value = vitestArgs[index + 1];
|
||||
if (value) {
|
||||
args.push(value);
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const excludePrefix = "--exclude=";
|
||||
if (arg.startsWith(excludePrefix)) {
|
||||
args.push(
|
||||
`${excludePrefix}${relativizeExtensionVitestPath(arg.slice(excludePrefix.length), cwd)}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
args.push(arg.startsWith("-") ? arg : relativizeExtensionVitestPath(arg, cwd));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
|
@ -6,6 +6,11 @@ import {
|
|||
listTrackedTestFilesForRoots,
|
||||
resolveExtensionBatchPlan,
|
||||
} from "./lib/extension-test-plan.mjs";
|
||||
import {
|
||||
normalizeRelativePath,
|
||||
relativizeExtensionVitestArgs,
|
||||
relativizeExtensionVitestPath,
|
||||
} from "./lib/extension-vitest-paths.mjs";
|
||||
import { parsePositiveInt } from "./lib/numeric-options.mjs";
|
||||
import { isDirectScriptRun, runVitestBatch } from "./lib/vitest-batch-runner.mjs";
|
||||
|
||||
|
|
@ -100,17 +105,18 @@ function orderPlanGroups(planGroups, parallelism) {
|
|||
});
|
||||
}
|
||||
|
||||
function normalizeRelativePath(inputPath) {
|
||||
return path
|
||||
.relative(process.cwd(), path.resolve(process.cwd(), inputPath))
|
||||
.split(path.sep)
|
||||
.join("/");
|
||||
}
|
||||
|
||||
function isExactExcludePath(inputPath) {
|
||||
return !/[*!?[\]{}]/u.test(inputPath);
|
||||
}
|
||||
|
||||
function addExactExcludePath(excludePaths, value) {
|
||||
const normalized = normalizeRelativePath(value);
|
||||
excludePaths.add(normalized);
|
||||
if (!normalized.startsWith("extensions/")) {
|
||||
excludePaths.add(`extensions/${normalized}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects exact --exclude paths so empty groups can be reported accurately.
|
||||
*/
|
||||
|
|
@ -121,7 +127,7 @@ export function parseExactVitestExcludePaths(vitestArgs) {
|
|||
if (arg === "--exclude") {
|
||||
const value = vitestArgs[index + 1];
|
||||
if (value && isExactExcludePath(value)) {
|
||||
excludePaths.add(normalizeRelativePath(value));
|
||||
addExactExcludePath(excludePaths, value);
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
|
|
@ -130,7 +136,7 @@ export function parseExactVitestExcludePaths(vitestArgs) {
|
|||
if (arg.startsWith(prefix)) {
|
||||
const value = arg.slice(prefix.length);
|
||||
if (value && isExactExcludePath(value)) {
|
||||
excludePaths.add(normalizeRelativePath(value));
|
||||
addExactExcludePath(excludePaths, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -161,7 +167,7 @@ async function runPlanGroup(group, params) {
|
|||
`[test-extension-batch] ${group.config}: ${group.extensionIds.join(", ")} (${targets.length} targets)`,
|
||||
);
|
||||
return await params.runGroup({
|
||||
args: params.vitestArgs,
|
||||
args: relativizeExtensionVitestArgs(params.vitestArgs),
|
||||
config: group.config,
|
||||
env: createGroupEnv({
|
||||
baseEnv: params.env,
|
||||
|
|
@ -169,7 +175,7 @@ async function runPlanGroup(group, params) {
|
|||
groupIndex: params.groupIndex,
|
||||
useDedicatedCache: params.useDedicatedCache,
|
||||
}),
|
||||
targets,
|
||||
targets: targets.map((target) => relativizeExtensionVitestPath(target)),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
// Runs the Vitest plan for one bundled plugin by id or path.
|
||||
import { formatErrorMessage } from "./lib/error-format.mjs";
|
||||
import { resolveExtensionTestPlan } from "./lib/extension-test-plan.mjs";
|
||||
import {
|
||||
relativizeExtensionVitestArgs,
|
||||
relativizeExtensionVitestPath,
|
||||
} from "./lib/extension-vitest-paths.mjs";
|
||||
import { isDirectScriptRun, runVitestBatch } from "./lib/vitest-batch-runner.mjs";
|
||||
|
||||
const ALLOW_NO_TESTS_FLAG = "--allow-no-tests";
|
||||
|
|
@ -54,10 +58,10 @@ async function run() {
|
|||
|
||||
console.log(`[test-extension] Running ${plan.testFileCount} test files for ${plan.extensionId}`);
|
||||
const exitCode = await runVitestBatch({
|
||||
args: passthroughArgs,
|
||||
args: relativizeExtensionVitestArgs(passthroughArgs),
|
||||
config: plan.config,
|
||||
env: process.env,
|
||||
targets: plan.roots,
|
||||
targets: plan.roots.map((target) => relativizeExtensionVitestPath(target)),
|
||||
});
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
resolveExtensionBatchPlan,
|
||||
resolveExtensionTestPlan,
|
||||
} from "../../scripts/lib/extension-test-plan.mjs";
|
||||
import { relativizeExtensionVitestArgs } from "../../scripts/lib/extension-vitest-paths.mjs";
|
||||
import { buildVitestBatchPnpmArgs } from "../../scripts/lib/vitest-batch-runner.mjs";
|
||||
import {
|
||||
parseExtensionIds,
|
||||
|
|
@ -227,11 +228,11 @@ describe("scripts/test-extension.mjs", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("keeps unmatched non-provider extensions on the shared extensions vitest config", () => {
|
||||
it("resolves codex onto the codex vitest config", () => {
|
||||
const plan = resolveExtensionTestPlan({ targetArg: "codex", cwd: process.cwd() });
|
||||
|
||||
expect(plan.extensionId).toBe("codex");
|
||||
expect(plan.config).toBe("test/vitest/vitest.extensions.config.ts");
|
||||
expect(plan.config).toBe("test/vitest/vitest.extension-codex.config.ts");
|
||||
expect(plan.roots).toContain(bundledPluginRoot("codex"));
|
||||
expect(plan.hasTests).toBe(true);
|
||||
});
|
||||
|
|
@ -614,7 +615,7 @@ describe("scripts/test-extension.mjs", () => {
|
|||
"0-heavy",
|
||||
),
|
||||
},
|
||||
targets: ["extensions/two"],
|
||||
targets: ["two"],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -650,9 +651,9 @@ describe("scripts/test-extension.mjs", () => {
|
|||
it("places Vitest passthrough options before batch target roots", () => {
|
||||
expect(
|
||||
buildVitestBatchPnpmArgs({
|
||||
args: ["--exclude", "extensions/codex/src/app-server/run-attempt.test.ts"],
|
||||
args: ["--exclude", "codex/src/app-server/run-attempt.test.ts"],
|
||||
config: "test/vitest/vitest.extensions.config.ts",
|
||||
targets: ["extensions/codex"],
|
||||
targets: ["codex"],
|
||||
}),
|
||||
).toEqual([
|
||||
"exec",
|
||||
|
|
@ -661,11 +662,86 @@ describe("scripts/test-extension.mjs", () => {
|
|||
"--config",
|
||||
"test/vitest/vitest.extensions.config.ts",
|
||||
"--exclude",
|
||||
"extensions/codex/src/app-server/run-attempt.test.ts",
|
||||
"extensions/codex",
|
||||
"codex/src/app-server/run-attempt.test.ts",
|
||||
"codex",
|
||||
]);
|
||||
});
|
||||
|
||||
it("relativizes extension Vitest path args to the scoped extensions dir", () => {
|
||||
expect(
|
||||
relativizeExtensionVitestArgs([
|
||||
"--exclude",
|
||||
"extensions/codex/src/app-server/run-attempt.test.ts",
|
||||
"--outputFile",
|
||||
"extensions/codex/report.json",
|
||||
"-c",
|
||||
"./vitest.local.ts",
|
||||
"-r",
|
||||
".",
|
||||
"--exclude=extensions/codex/src/app-server/client.test.ts",
|
||||
"extensions/codex/src/app-server/models.test.ts",
|
||||
"--reporter=dot",
|
||||
]),
|
||||
).toEqual([
|
||||
"--exclude",
|
||||
"codex/src/app-server/run-attempt.test.ts",
|
||||
"--outputFile",
|
||||
"extensions/codex/report.json",
|
||||
"-c",
|
||||
"./vitest.local.ts",
|
||||
"-r",
|
||||
".",
|
||||
"--exclude=codex/src/app-server/client.test.ts",
|
||||
"codex/src/app-server/models.test.ts",
|
||||
"--reporter=dot",
|
||||
]);
|
||||
});
|
||||
|
||||
posixIt("relativizes single-extension Vitest paths from extension cwd", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-test-extension-args-"));
|
||||
const fakePnpmPath = path.join(root, "pnpm");
|
||||
const argsPath = path.join(root, "args.json");
|
||||
const extensionCwd = path.join(process.cwd(), "extensions", "codex");
|
||||
|
||||
writeFakePnpm(fakePnpmPath);
|
||||
try {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
scriptPath,
|
||||
"codex",
|
||||
"--exclude",
|
||||
path.join(extensionCwd, "src", "app-server", "run-attempt.test.ts"),
|
||||
path.join(extensionCwd, "src", "app-server", "client.test.ts"),
|
||||
],
|
||||
{
|
||||
cwd: extensionCwd,
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_FAKE_PNPM_ARGS_PATH: argsPath,
|
||||
npm_execpath: fakePnpmPath,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(JSON.parse(readFileSync(argsPath, "utf8"))).toEqual([
|
||||
"exec",
|
||||
"vitest",
|
||||
"run",
|
||||
"--config",
|
||||
"test/vitest/vitest.extension-codex.config.ts",
|
||||
"--exclude",
|
||||
"codex/src/app-server/run-attempt.test.ts",
|
||||
"codex/src/app-server/client.test.ts",
|
||||
"codex",
|
||||
]);
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
posixIt(
|
||||
"preserves wrapper termination when the pnpm child exits cleanly after SIGTERM",
|
||||
async () => {
|
||||
|
|
@ -739,7 +815,8 @@ describe("scripts/test-extension.mjs", () => {
|
|||
|
||||
const runParams = requireFirstMockArg<RunGroupParams>(runGroup);
|
||||
expect(runParams.targets).not.toContain("extensions/codex/src/app-server/run-attempt.test.ts");
|
||||
expect(runParams.targets).toContain("extensions/codex/src/app-server/client.test.ts");
|
||||
expect(runParams.targets).not.toContain("codex/src/app-server/run-attempt.test.ts");
|
||||
expect(runParams.targets).toContain("codex/src/app-server/client.test.ts");
|
||||
});
|
||||
|
||||
it("fails extension batch groups when exact excludes remove every test", async () => {
|
||||
|
|
@ -756,6 +833,20 @@ describe("scripts/test-extension.mjs", () => {
|
|||
expect(runGroup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fails extension batch groups when dir-relative exact excludes remove every test", async () => {
|
||||
const runGroup = vi.fn<() => Promise<number>>().mockResolvedValue(0);
|
||||
const result = await runExtensionBatchPlan(
|
||||
resolveExtensionBatchPlan({ cwd: process.cwd(), extensionIds: ["firecrawl"] }),
|
||||
{
|
||||
runGroup,
|
||||
vitestArgs: ["--exclude", "firecrawl/src/firecrawl-tools.test.ts"],
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toBe(1);
|
||||
expect(runGroup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows extension batch groups to opt into empty exact excludes", async () => {
|
||||
const runGroup = vi.fn<() => Promise<number>>().mockResolvedValue(0);
|
||||
const result = await runExtensionBatchPlan(
|
||||
|
|
@ -811,6 +902,10 @@ function writeFakePnpm(filePath: string): void {
|
|||
[
|
||||
"#!/usr/bin/env node",
|
||||
'const fs = require("node:fs");',
|
||||
"if (process.env.OPENCLAW_FAKE_PNPM_ARGS_PATH) {",
|
||||
" fs.writeFileSync(process.env.OPENCLAW_FAKE_PNPM_ARGS_PATH, JSON.stringify(process.argv.slice(2)));",
|
||||
" process.exit(0);",
|
||||
"}",
|
||||
"fs.writeFileSync(process.env.OPENCLAW_FAKE_PNPM_PID_PATH, String(process.pid));",
|
||||
'process.on("SIGTERM", () => {',
|
||||
' fs.writeFileSync(process.env.OPENCLAW_FAKE_PNPM_SIGNALED_PATH, "SIGTERM");',
|
||||
|
|
|
|||
|
|
@ -263,6 +263,78 @@ describe("createScopedVitestConfig", () => {
|
|||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("narrows scoped includes to matching dot-prefixed CLI file filters", () => {
|
||||
const config = createScopedVitestConfig(["extensions/codex/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "./extensions/codex/src/app-server/client.test.ts"],
|
||||
dir: "extensions",
|
||||
env: {},
|
||||
});
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
expect(testConfig.include).toEqual(["codex/src/app-server/client.test.ts"]);
|
||||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("narrows scoped includes to matching dir-relative CLI file filters", () => {
|
||||
const config = createScopedVitestConfig(["extensions/codex/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "codex/src/app-server/client.test.ts"],
|
||||
dir: "extensions",
|
||||
env: {},
|
||||
});
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
expect(testConfig.include).toEqual(["codex/src/app-server/client.test.ts"]);
|
||||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not narrow scoped includes for bare Vitest name filters", () => {
|
||||
const config = createScopedVitestConfig(["extensions/codex/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "client"],
|
||||
dir: "extensions",
|
||||
env: {},
|
||||
});
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
expect(testConfig.include).toEqual(["codex/**/*.test.ts"]);
|
||||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not narrow scoped includes for changed refs", () => {
|
||||
const config = createScopedVitestConfig(["extensions/codex/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "--changed", "origin/main"],
|
||||
dir: "extensions",
|
||||
env: {},
|
||||
});
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
expect(testConfig.include).toEqual(["codex/**/*.test.ts"]);
|
||||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not narrow scoped includes for coverage option values", () => {
|
||||
const config = createScopedVitestConfig(["extensions/codex/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "--coverage.include", "codex/src/app-server/client.ts"],
|
||||
dir: "extensions",
|
||||
env: {},
|
||||
});
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
expect(testConfig.include).toEqual(["codex/**/*.test.ts"]);
|
||||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not narrow scoped includes for exclude option values", () => {
|
||||
const config = createScopedVitestConfig(["extensions/codex/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "--exclude", "codex/src/app-server/run-attempt.test.ts"],
|
||||
dir: "extensions",
|
||||
env: {},
|
||||
});
|
||||
const testConfig = requireTestConfig(config);
|
||||
|
||||
expect(testConfig.include).toEqual(["codex/**/*.test.ts"]);
|
||||
expect(testConfig.passWithNoTests).toBeUndefined();
|
||||
});
|
||||
|
||||
it("lets root Vitest project runs skip scoped files owned by unit-fast", () => {
|
||||
const config = createScopedVitestConfig(["src/acp/**/*.test.ts"], {
|
||||
argv: ["node", "vitest", "run", "src/acp/client.test.ts"],
|
||||
|
|
|
|||
|
|
@ -2,6 +2,45 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const VITEST_OPTION_VALUE_FLAGS = new Set([
|
||||
"-c",
|
||||
"-r",
|
||||
"-t",
|
||||
"--browser",
|
||||
"--changed",
|
||||
"--config",
|
||||
"--coverage.all",
|
||||
"--coverage.exclude",
|
||||
"--coverage.extension",
|
||||
"--coverage.include",
|
||||
"--coverage.provider",
|
||||
"--coverage.reporter",
|
||||
"--coverage.reportsDirectory",
|
||||
"--dir",
|
||||
"--environment",
|
||||
"--environmentOptions",
|
||||
"--exclude",
|
||||
"--hookTimeout",
|
||||
"--inspect",
|
||||
"--inspectBrk",
|
||||
"--maxConcurrency",
|
||||
"--maxWorkers",
|
||||
"--minWorkers",
|
||||
"--mode",
|
||||
"--name",
|
||||
"--outputFile",
|
||||
"--pool",
|
||||
"--project",
|
||||
"--reporter",
|
||||
"--retry",
|
||||
"--root",
|
||||
"--sequence",
|
||||
"--shard",
|
||||
"--testNamePattern",
|
||||
"--testTimeout",
|
||||
"--workspace",
|
||||
]);
|
||||
|
||||
function normalizeCliPattern(value: string): string {
|
||||
let normalized = value
|
||||
.trim()
|
||||
|
|
@ -17,6 +56,39 @@ function normalizeCliPattern(value: string): string {
|
|||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeScopedDir(value: string | undefined): string {
|
||||
return value?.trim().replaceAll("\\", "/").replace(/\/+$/u, "") ?? "";
|
||||
}
|
||||
|
||||
function hasRepoRootPrefix(value: string): boolean {
|
||||
return /^(?:src|test|extensions|ui|packages|apps)(?:\/|$)/u.test(value);
|
||||
}
|
||||
|
||||
function looksLikeDirRelativePath(value: string): boolean {
|
||||
return (
|
||||
value.includes("/") ||
|
||||
value.includes(".test.") ||
|
||||
value.includes(".e2e.") ||
|
||||
value.includes(".live.")
|
||||
);
|
||||
}
|
||||
|
||||
function applyScopedDir(value: string, scopedDir: string): string {
|
||||
const normalizedValue = value
|
||||
.trim()
|
||||
.replace(/^\.\/+/u, "")
|
||||
.replaceAll("\\", "/");
|
||||
if (
|
||||
!scopedDir ||
|
||||
hasRepoRootPrefix(normalizedValue) ||
|
||||
path.isAbsolute(value) ||
|
||||
!looksLikeDirRelativePath(normalizedValue)
|
||||
) {
|
||||
return normalizedValue;
|
||||
}
|
||||
return `${scopedDir}/${normalizedValue}`;
|
||||
}
|
||||
|
||||
function looksLikeCliIncludePattern(value: string): boolean {
|
||||
const normalized = normalizeCliPattern(value);
|
||||
return (
|
||||
|
|
@ -72,24 +144,13 @@ export function loadPatternListFromEnv(
|
|||
}
|
||||
|
||||
export function loadPatternListFromArgv(argv: string[] = process.argv): string[] | null {
|
||||
const optionValueFlags = new Set([
|
||||
"-c",
|
||||
"-r",
|
||||
"-t",
|
||||
"--config",
|
||||
"--dir",
|
||||
"--environment",
|
||||
"--exclude",
|
||||
"--maxWorkers",
|
||||
"--mode",
|
||||
"--outputFile",
|
||||
"--pool",
|
||||
"--project",
|
||||
"--reporter",
|
||||
"--root",
|
||||
"--shard",
|
||||
"--testNamePattern",
|
||||
]);
|
||||
return loadPatternListFromArgvForScope(argv);
|
||||
}
|
||||
|
||||
function loadPatternListFromArgvForScope(
|
||||
argv: string[] = process.argv,
|
||||
options: { scopedDir?: string } = {},
|
||||
): string[] | null {
|
||||
const values: string[] = [];
|
||||
let skipNext = false;
|
||||
for (const value of argv.slice(2)) {
|
||||
|
|
@ -100,7 +161,7 @@ export function loadPatternListFromArgv(argv: string[] = process.argv): string[]
|
|||
if (value === "run" || value === "watch" || value === "bench") {
|
||||
continue;
|
||||
}
|
||||
if (optionValueFlags.has(value)) {
|
||||
if (VITEST_OPTION_VALUE_FLAGS.has(value)) {
|
||||
skipNext = true;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -110,7 +171,11 @@ export function loadPatternListFromArgv(argv: string[] = process.argv): string[]
|
|||
values.push(value);
|
||||
}
|
||||
|
||||
const patterns = values.filter(looksLikeCliIncludePattern).map(normalizeCliPattern);
|
||||
const scopedDir = normalizeScopedDir(options.scopedDir);
|
||||
const patterns = values
|
||||
.map((value) => applyScopedDir(value, scopedDir))
|
||||
.filter(looksLikeCliIncludePattern)
|
||||
.map(normalizeCliPattern);
|
||||
|
||||
return patterns.length > 0 ? [...new Set(patterns)] : null;
|
||||
}
|
||||
|
|
@ -118,8 +183,9 @@ export function loadPatternListFromArgv(argv: string[] = process.argv): string[]
|
|||
export function narrowIncludePatternsForCli(
|
||||
includePatterns: string[],
|
||||
argv: string[] = process.argv,
|
||||
options: { scopedDir?: string } = {},
|
||||
): string[] | null {
|
||||
const cliPatterns = loadPatternListFromArgv(argv);
|
||||
const cliPatterns = loadPatternListFromArgvForScope(argv, options);
|
||||
if (!cliPatterns) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,7 +219,9 @@ export function createScopedVitestConfig(
|
|||
const resolvedScopedDir = scopedDir ? path.join(repoRoot, scopedDir) : undefined;
|
||||
const env = options?.env;
|
||||
const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
|
||||
const cliInclude = narrowIncludePatternsForCli(include, options?.argv);
|
||||
const cliInclude = narrowIncludePatternsForCli(include, options?.argv, {
|
||||
scopedDir,
|
||||
});
|
||||
const unitFastExcludePatterns =
|
||||
options?.excludeUnitFastTests === false ? [] : getUnitFastTestFiles();
|
||||
const exclude = relativizeScopedPatterns(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue