improve: speed up focused test execution (#106307)

* test: speed up focused test execution

* style: format vitest config test

* test: preserve targeted watch routing
This commit is contained in:
Peter Steinberger
2026-07-13 04:38:05 -07:00
committed by GitHub
parent cad6df0a7c
commit 418dd85e66
16 changed files with 451 additions and 107 deletions
+10 -1
View File
@@ -4201,6 +4201,7 @@ describe("active-memory plugin", () => {
});
it("uses a late verbose summary after a successful result and later unavailable trace", async () => {
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
const CONFIGURED_TIMEOUT_MS = 1_000;
testing.setMinimumTimeoutMsForTests(1);
testing.setSetupGraceTimeoutMsForTests(0);
@@ -4219,6 +4220,10 @@ describe("active-memory plugin", () => {
};
const verboseSummary =
"This memory says the user usually orders tonkotsu ramen, keeps chili crisp nearby, and prefers short dinner suggestions without menu preamble.";
let markDelayScheduled: (() => void) | undefined;
const delayScheduled = new Promise<void>((resolve) => {
markDelayScheduled = resolve;
});
runEmbeddedAgent.mockImplementationOnce(async (params: { sessionFile: string }) => {
await writeTranscriptJsonl(params.sessionFile, [
{
@@ -4259,6 +4264,7 @@ describe("active-memory plugin", () => {
},
},
]);
markDelayScheduled?.();
await new Promise((resolve) => {
setTimeout(resolve, 550);
});
@@ -4281,10 +4287,13 @@ describe("active-memory plugin", () => {
};
});
const result = await requireHook("before_prompt_build")(
const resultPromise = requireHook("before_prompt_build")(
{ prompt: "what food do i usually order? unavailable then summary", messages: [] },
{ agentId: "main", trigger: "user", sessionKey, messageProvider: "webchat" },
);
await delayScheduled;
await vi.advanceTimersByTimeAsync(550);
const result = await resultPromise;
expect(requirePrependContext(result)).toContain(
"This memory says the user usually orders tonkotsu ramen",
@@ -26,6 +26,11 @@ const SUCCESS_STREAM_CHUNK = Buffer.alloc(64 * 1024, "x");
const SUCCESS_STREAM_BODY_BYTES = 33 * 1024 * 1024;
const BROWSER_SUCCESS_BODY_LIMIT_BYTES = 32 * 1024 * 1024;
function scheduleStreamChunk(writeNext: () => void): void {
// Separate event-loop turns preserve streaming and backpressure without a wall-clock sleep.
setImmediate(writeNext);
}
describe("fetchHttpJson error body boundary", () => {
let server: http.Server;
let baseUrl: string;
@@ -91,11 +96,10 @@ describe("fetchHttpJson error body boundary", () => {
? SUCCESS_STREAM_CHUNK
: SUCCESS_STREAM_CHUNK.subarray(0, remaining);
written += chunk.byteLength;
const scheduleNext = () => setTimeout(writeNext, 2);
if (res.write(chunk)) {
scheduleNext();
scheduleStreamChunk(writeNext);
} else {
res.once("drain", scheduleNext);
res.once("drain", () => scheduleStreamChunk(writeNext));
}
};
writeNext();
@@ -143,11 +147,10 @@ describe("fetchHttpJson error body boundary", () => {
return;
}
written += STREAM_CHUNK.byteLength;
const writeMore = () => setTimeout(writeNext, 2);
if (res.write(STREAM_CHUNK)) {
writeMore();
scheduleStreamChunk(writeNext);
} else {
res.once("drain", writeMore);
res.once("drain", () => scheduleStreamChunk(writeNext));
}
};
writeNext();
@@ -28,6 +28,26 @@ function createMutableFrame(initialUrl: string) {
};
}
async function runWithVirtualNavigationGrace<T>(run: () => Promise<T>): Promise<T> {
vi.useFakeTimers();
try {
// Observe rejection before advancing the production grace timer to avoid a transient
// unhandled rejection; timing-specific cases below still advance exact durations.
const settled = run().then(
(value) => ({ status: "fulfilled" as const, value }),
(reason: unknown) => ({ status: "rejected" as const, reason }),
);
await vi.runAllTimersAsync();
const result = await settled;
if (result.status === "rejected") {
throw result.reason;
}
return result.value;
} finally {
vi.useRealTimers();
}
}
describe("pw-tools-core interaction navigation guard", () => {
it("waits for the grace window before completing a successful non-navigating click", async () => {
vi.useFakeTimers();
@@ -845,12 +865,14 @@ describe("pw-tools-core interaction navigation guard", () => {
getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely.mockRejectedValueOnce(blocked);
await expect(
mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
}),
runWithVirtualNavigationGrace(() =>
mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
}),
),
).rejects.toThrow("blocked interaction navigation");
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
@@ -918,12 +940,14 @@ describe("pw-tools-core interaction navigation guard", () => {
};
setPwToolsCoreCurrentPage(page);
const result = await mod.evaluateViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
fn: "() => location.href = 'http://127.0.0.1:9222/json/version'",
ssrfPolicy: { allowPrivateNetwork: false },
});
const result = await runWithVirtualNavigationGrace(() =>
mod.evaluateViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
fn: "() => location.href = 'http://127.0.0.1:9222/json/version'",
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(result).toBe("ok");
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
@@ -1125,12 +1149,14 @@ describe("pw-tools-core interaction navigation guard", () => {
setPwToolsCoreCurrentRefLocator({ click });
setPwToolsCoreCurrentPage(page);
await mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
});
await runWithVirtualNavigationGrace(() =>
mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
cdpUrl: "http://127.0.0.1:18792",
@@ -1152,12 +1178,14 @@ describe("pw-tools-core interaction navigation guard", () => {
setPwToolsCoreCurrentRefLocator({ click });
setPwToolsCoreCurrentPage(page);
await mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
});
await runWithVirtualNavigationGrace(() =>
mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
cdpUrl: "http://127.0.0.1:18792",
@@ -1196,12 +1224,14 @@ describe("pw-tools-core interaction navigation guard", () => {
setPwToolsCoreCurrentRefLocator({ click });
setPwToolsCoreCurrentPage(page);
await mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
});
await runWithVirtualNavigationGrace(() =>
mod.clickViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ref: "1",
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
cdpUrl: "http://127.0.0.1:18792",
@@ -1219,12 +1249,14 @@ describe("pw-tools-core interaction navigation guard", () => {
};
setPwToolsCoreCurrentPage(page);
const result = await mod.evaluateViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
fn: "() => 1",
ssrfPolicy: { allowPrivateNetwork: false },
});
const result = await runWithVirtualNavigationGrace(() =>
mod.evaluateViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
fn: "() => 1",
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(result).toBe("ok");
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
@@ -1250,12 +1282,14 @@ describe("pw-tools-core interaction navigation guard", () => {
setPwToolsCoreCurrentRefLocator({ click });
setPwToolsCoreCurrentPage(page);
await mod.batchViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ssrfPolicy: { allowPrivateNetwork: false },
actions: [{ kind: "click", ref: "1" }],
});
await runWithVirtualNavigationGrace(() =>
mod.batchViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
ssrfPolicy: { allowPrivateNetwork: false },
actions: [{ kind: "click", ref: "1" }],
}),
);
expect(getPwToolsCoreSessionMocks().assertPageNavigationCompletedSafely).toHaveBeenCalledWith({
cdpUrl: "http://127.0.0.1:18792",
@@ -1343,12 +1377,14 @@ describe("pw-tools-core interaction navigation guard", () => {
setPwToolsCoreCurrentPage(page);
setPwToolsCoreCurrentRefLocator({ click });
const result = await mod.executeActViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
action: { kind: "click", ref: "1" },
ssrfPolicy: { allowPrivateNetwork: false },
});
const result = await runWithVirtualNavigationGrace(() =>
mod.executeActViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
action: { kind: "click", ref: "1" },
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(result.downloads).toEqual([
{
@@ -1395,12 +1431,14 @@ describe("pw-tools-core interaction navigation guard", () => {
setPwToolsCoreCurrentPage(page);
setPwToolsCoreCurrentRefLocator(locator);
await mod.executeActViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
action,
ssrfPolicy: { allowPrivateNetwork: false },
});
await runWithVirtualNavigationGrace(() =>
mod.executeActViaPlaywright({
cdpUrl: "http://127.0.0.1:18792",
targetId: "T1",
action,
ssrfPolicy: { allowPrivateNetwork: false },
}),
);
expect(drain).toHaveBeenCalledWith({
firstEventGraceMs: 0,
+3 -1
View File
@@ -122,7 +122,9 @@ function runPnpmSpecCommand(spec, pnpmArgs, label) {
async function runVitestSpec(spec) {
if (spec.includeFilePath && spec.includePatterns) {
writeVitestIncludeFile(spec.includeFilePath, spec.includePatterns);
writeVitestIncludeFile(spec.includeFilePath, spec.includePatterns, {
expandGlobs: !spec.watchMode,
});
}
try {
if (spec.preflightPnpmArgs) {
+5 -1
View File
@@ -156,7 +156,11 @@ export function shouldAcquireLocalHeavyCheckLock(
env?: Record<string, string | undefined>,
): boolean;
export function writeVitestIncludeFile(filePath: string, includePatterns: string[]): void;
export function writeVitestIncludeFile(
filePath: string,
includePatterns: string[],
options?: { cwd?: string; expandGlobs?: boolean },
): void;
export function formatFailedShardDigest(
failures: FailedVitestShard[],
+23 -3
View File
@@ -2618,7 +2618,7 @@ function isExistingDirectoryTarget(arg, cwd) {
}
function isGlobTarget(arg) {
return /[*?[\]{}]/u.test(arg);
return /[*?[\]{}]|[@+!]\(/u.test(arg);
}
function isFileLikeTarget(arg) {
@@ -4554,8 +4554,28 @@ export function shouldAcquireLocalHeavyCheckLock(runSpecs, env = process.env) {
);
}
export function writeVitestIncludeFile(filePath, includePatterns) {
fs.writeFileSync(filePath, `${JSON.stringify(includePatterns, null, 2)}\n`);
function expandVitestIncludePatterns(includePatterns, cwd) {
const candidateFiles = includePatterns.some(isGlobTarget)
? listExplicitTestTargetFilesForCwd(cwd)
: [];
return uniqueOrdered(
includePatterns.flatMap((pattern) => {
if (!isGlobTarget(pattern)) {
return [pattern];
}
return candidateFiles.filter((file) => path.matchesGlob(file, pattern));
}),
);
}
export function writeVitestIncludeFile(filePath, includePatterns, options = {}) {
// Shared Vitest projects intersect this file with their ownership globs.
// One-shot runs emit concrete paths; watch runs retain globs for new files.
const expandedPatterns =
options.expandGlobs === false
? includePatterns
: expandVitestIncludePatterns(includePatterns, options.cwd ?? process.cwd());
fs.writeFileSync(filePath, `${JSON.stringify(expandedPatterns, null, 2)}\n`);
}
function shellQuote(value) {
+45
View File
@@ -27,6 +27,7 @@ import {
resolveChangedTargetArgs,
resolveParallelFullSuiteConcurrency,
shouldRetryVitestNoOutputTimeout,
writeVitestIncludeFile,
} from "../../scripts/test-projects.test-support.mjs";
import { captureReaddirSyncCallsDuring } from "../../src/test-utils/fs-scan-assertions.js";
import { toRepoPath } from "../../src/test-utils/repo-files.js";
@@ -3570,6 +3571,50 @@ describe("scripts/test-projects changed-target routing", () => {
expect(spec?.includeFilePath).not.toMatch(new RegExp(`${process.pid}-\\d+-0\\.json$`, "u"));
});
it("expands routed glob targets to literal include-file paths", () => {
withTinyGitRepo(
{
"src/gateway/core.test.ts": "",
"src/gateway/server-methods/ping.test.ts": "",
"src/gateway/server-startup.test.ts": "",
},
(cwd) => {
const includeFile = path.join(cwd, "include.json");
writeVitestIncludeFile(
includeFile,
[
"src/gateway/**/*.test.ts",
"src/gateway/server-*.test.ts",
"src/gateway/@(core|server-startup).test.ts",
],
{ cwd },
);
expect(JSON.parse(fs.readFileSync(includeFile, "utf8"))).toEqual([
"src/gateway/core.test.ts",
"src/gateway/server-methods/ping.test.ts",
"src/gateway/server-startup.test.ts",
]);
},
);
});
it("retains routed glob targets in watch-mode include files", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-test-projects-watch-"));
try {
const includeFile = path.join(tempDir, "include.json");
writeVitestIncludeFile(includeFile, ["src/gateway/**/*.test.ts"], {
expandGlobs: false,
});
expect(JSON.parse(fs.readFileSync(includeFile, "utf8"))).toEqual([
"src/gateway/**/*.test.ts",
]);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("preflights targeted UI E2E specs with Playwright browser assets", () => {
const [spec] = createVitestRunSpecs(["ui/src/pages/tasks/tasks.e2e.test.ts"], {
baseEnv: {},
+105
View File
@@ -45,6 +45,10 @@ import { createExtensionVoiceCallVitestConfig } from "./vitest/vitest.extension-
import { createExtensionWhatsAppVitestConfig } from "./vitest/vitest.extension-whatsapp.config.ts";
import { createExtensionZaloVitestConfig } from "./vitest/vitest.extension-zalo.config.ts";
import { createExtensionsVitestConfig } from "./vitest/vitest.extensions.config.ts";
import { createGatewayClientVitestConfig } from "./vitest/vitest.gateway-client.config.ts";
import { createGatewayCoreVitestConfig } from "./vitest/vitest.gateway-core.config.ts";
import { createGatewayMethodsVitestConfig } from "./vitest/vitest.gateway-methods.config.ts";
import { createGatewayServerVitestConfig } from "./vitest/vitest.gateway-server.config.ts";
import { createGatewayVitestConfig } from "./vitest/vitest.gateway.config.ts";
import { createHooksVitestConfig } from "./vitest/vitest.hooks.config.ts";
import { createInfraVitestConfig } from "./vitest/vitest.infra.config.ts";
@@ -386,6 +390,107 @@ describe("createScopedVitestConfig", () => {
}
});
it("keeps include-file targets inside the scoped project's ownership", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-vitest-scoped-"));
try {
const includeFile = path.join(tempDir, "include.json");
fs.writeFileSync(
includeFile,
JSON.stringify(["src/gateway/server.node-pairing-ssh-verify.test.ts"]),
"utf8",
);
const config = createScopedVitestConfig(["src/gateway/server-methods/**/*.test.ts"], {
dir: "src/gateway",
env: {
OPENCLAW_VITEST_INCLUDE_FILE: includeFile,
},
intersectIncludeFile: true,
});
const testConfig = requireTestConfig(config);
expect(testConfig.include).toEqual([]);
expect(testConfig.passWithNoTests).toBe(true);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it.each([
"src/gateway/**/*{server,client}*.test.ts",
"src/gateway/@(server|core).test.ts",
"src/gateway/nested/**/*.test.ts",
])(
"rejects ambiguous watch-mode include-file target %s at an ownership boundary",
(candidate) => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-vitest-scoped-"));
try {
const includeFile = path.join(tempDir, "include.json");
fs.writeFileSync(includeFile, JSON.stringify([candidate]), "utf8");
expect(() =>
createScopedVitestConfig(["src/gateway/**/*server*.test.ts"], {
dir: "src/gateway",
env: {
OPENCLAW_VITEST_INCLUDE_FILE: includeFile,
},
intersectIncludeFile: true,
}),
).toThrow(`cannot safely intersect non-literal include path: ${candidate}`);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
},
);
it("intersects a watch-mode directory target with project ownership", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-vitest-scoped-"));
try {
const includeFile = path.join(tempDir, "include.json");
fs.writeFileSync(includeFile, JSON.stringify(["src/gateway/**/*.test.ts"]), "utf8");
const config = createScopedVitestConfig(["src/gateway/**/*server*.test.ts"], {
dir: "src/gateway",
env: {
OPENCLAW_VITEST_INCLUDE_FILE: includeFile,
},
intersectIncludeFile: true,
});
expect(requireTestConfig(config).include).toEqual(["**/*server*.test.ts"]);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("keeps shared gateway include files inside their actual child projects", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-vitest-scoped-"));
try {
const includeFile = path.join(tempDir, "include.json");
fs.writeFileSync(
includeFile,
JSON.stringify(["src/gateway/server.node-pairing-ssh-verify.test.ts"]),
"utf8",
);
const env = { OPENCLAW_VITEST_INCLUDE_FILE: includeFile };
expect(requireTestConfig(createGatewayServerVitestConfig(env)).include).toEqual([
"server.node-pairing-ssh-verify.test.ts",
]);
const coreConfig = requireTestConfig(createGatewayCoreVitestConfig(env));
expect(coreConfig.include).toEqual(["server.node-pairing-ssh-verify.test.ts"]);
expect(coreConfig.passWithNoTests).toBe(true);
const clientConfig = requireTestConfig(createGatewayClientVitestConfig(env));
expect(clientConfig.include).toEqual([]);
expect(clientConfig.passWithNoTests).toBe(true);
const methodsConfig = requireTestConfig(createGatewayMethodsVitestConfig(env));
expect(methodsConfig.include).toEqual([]);
expect(methodsConfig.passWithNoTests).toBe(true);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("overrides setup files when a scoped config requests them", () => {
const config = createScopedVitestConfig(["src/example.test.ts"], {
env: {},
+6 -22
View File
@@ -1,7 +1,10 @@
// Vitest contract shared helpers build contract test project configuration.
import path from "node:path";
import { defineConfig } from "vitest/config";
import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts";
import {
intersectIncludePatterns,
loadPatternListFromEnv,
narrowIncludePatternsForCli,
} from "./vitest.pattern-file.ts";
import { nonIsolatedRunnerPath, sharedVitestConfig } from "./vitest.shared.config.ts";
const base = sharedVitestConfig as Record<string, unknown>;
@@ -53,25 +56,6 @@ function loadContractsIncludePatternsFromEnv(
return loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
}
function narrowContractIncludePatterns(
includePatterns: string[],
candidatePatterns: string[] | null,
): string[] | null {
if (!candidatePatterns) {
return null;
}
return [
...new Set(
candidatePatterns.filter((candidate) =>
includePatterns.some(
(pattern) => path.matchesGlob(candidate, pattern) || path.matchesGlob(pattern, candidate),
),
),
),
];
}
export function createContractsVitestConfig(
includePatterns: string[],
env: Record<string, string | undefined> = process.env,
@@ -79,7 +63,7 @@ export function createContractsVitestConfig(
options: { name?: string } = {},
) {
const cliIncludePatterns = narrowIncludePatternsForCli(includePatterns, argv);
const envIncludePatterns = narrowContractIncludePatterns(
const envIncludePatterns = intersectIncludePatterns(
includePatterns,
loadContractsIncludePatternsFromEnv(env),
);
+3 -1
View File
@@ -1,7 +1,7 @@
// Vitest gateway client config wires the gateway client test shard.
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
function createGatewayClientVitestConfig(env?: Record<string, string | undefined>) {
export function createGatewayClientVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(
[
"packages/gateway-client/src/**/*.test.ts",
@@ -14,6 +14,8 @@ function createGatewayClientVitestConfig(env?: Record<string, string | undefined
{
env,
exclude: ["src/gateway/**/*server*.test.ts"],
// Gateway child projects share one include file; preserve this project's ownership.
intersectIncludeFile: true,
isolate: true,
name: "gateway-client",
},
+3 -1
View File
@@ -19,11 +19,13 @@ const nonCoreGatewayTestExclude = [
"src/gateway/sessions-history-http.test.ts",
];
function createGatewayCoreVitestConfig(env?: Record<string, string | undefined>) {
export function createGatewayCoreVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(["src/gateway/**/*.test.ts"], {
dir: "src/gateway",
env,
exclude: nonCoreGatewayTestExclude,
// Gateway child projects share one include file; preserve this project's ownership.
intersectIncludeFile: true,
name: "gateway-core",
});
}
+3 -1
View File
@@ -1,10 +1,12 @@
// Vitest gateway methods config wires the gateway methods test shard.
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
function createGatewayMethodsVitestConfig(env?: Record<string, string | undefined>) {
export function createGatewayMethodsVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(["src/gateway/server-methods/**/*.test.ts"], {
dir: "src/gateway",
env,
// Gateway child projects share one include file; preserve this project's ownership.
intersectIncludeFile: true,
name: "gateway-methods",
});
}
+3 -1
View File
@@ -9,7 +9,7 @@ const gatewayServerBackedHttpTests = [
"src/gateway/probe.auth.integration.test.ts",
];
function createGatewayServerVitestConfig(env?: Record<string, string | undefined>) {
export function createGatewayServerVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(
["src/gateway/**/*server*.test.ts", ...gatewayServerBackedHttpTests],
{
@@ -22,6 +22,8 @@ function createGatewayServerVitestConfig(env?: Record<string, string | undefined
"src/gateway/sessions-history-http.test.ts",
],
fileParallelism: false,
// Gateway child projects share one include file; preserve this project's ownership.
intersectIncludeFile: true,
isolate: false,
name: "gateway-server",
},
+106 -5
View File
@@ -124,6 +124,111 @@ function patternsCouldOverlap(value: string, pattern: string): boolean {
);
}
function narrowIncludePatterns(
includePatterns: string[],
candidatePatterns: string[] | null,
): string[] | null {
if (!candidatePatterns) {
return null;
}
return [
...new Set(
candidatePatterns.filter((value) =>
includePatterns.some((pattern) => patternsCouldOverlap(value, pattern)),
),
),
];
}
function isPlainRepoRelativePath(value: string): boolean {
if (!/^[A-Za-z0-9_./-]+$/u.test(value) || path.isAbsolute(value)) {
return false;
}
return value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
}
function directoryTestPatternRoot(value: string): string | null {
const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\//u, "");
if (normalized === "**/*.test.ts") {
return "";
}
const suffix = "/**/*.test.ts";
if (!normalized.endsWith(suffix)) {
return null;
}
const root = normalized.slice(0, -suffix.length);
return isPlainRepoRelativePath(root) ? root : null;
}
function isAtOrUnder(value: string, root: string): boolean {
return root === "" || value === root || value.startsWith(`${root}/`);
}
function patternIsFullyUnderDirectory(pattern: string, root: string): boolean {
const normalized = pattern.trim().replaceAll("\\", "/").replace(/^\.\//u, "");
if (!normalized.endsWith(".test.ts")) {
return false;
}
const literalPrefix = literalPrefixForGlobPattern(normalized).replace(/\/+$/u, "");
return isAtOrUnder(literalPrefix, root);
}
function intersectDirectoryTestPattern(
includePatterns: string[],
candidatePattern: string,
): string[] | null {
const candidateRoot = directoryTestPatternRoot(candidatePattern);
if (candidateRoot === null) {
return null;
}
const result: string[] = [];
let hasAmbiguousOverlap = false;
for (const includePattern of includePatterns) {
const includeRoot = directoryTestPatternRoot(includePattern);
if (includeRoot !== null && isAtOrUnder(candidateRoot, includeRoot)) {
return [candidatePattern];
} else if (patternIsFullyUnderDirectory(includePattern, candidateRoot)) {
result.push(includePattern);
} else if (patternsCouldOverlap(candidatePattern, includePattern)) {
hasAmbiguousOverlap = true;
}
}
if (hasAmbiguousOverlap) {
return null;
}
return [...new Set(result)];
}
export function intersectIncludePatterns(
includePatterns: string[],
candidatePatterns: string[] | null,
): string[] | null {
if (!candidatePatterns) {
return null;
}
const result: string[] = [];
for (const candidate of candidatePatterns) {
if (!isPlainRepoRelativePath(candidate)) {
// Watch directory targets retain their glob so newly added tests appear.
// Only generated directory globs have a provable ownership intersection.
const intersection = intersectDirectoryTestPattern(includePatterns, candidate);
if (!intersection) {
throw new Error(`cannot safely intersect non-literal include path: ${candidate}`);
}
result.push(...intersection);
continue;
}
if (includePatterns.some((include) => path.matchesGlob(candidate, include))) {
result.push(candidate);
}
}
return [...new Set(result)];
}
function loadPatternListFile(filePath: string, label: string): string[] {
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")) as unknown;
if (!Array.isArray(parsed)) {
@@ -186,9 +291,5 @@ export function narrowIncludePatternsForCli(
return null;
}
const matched = cliPatterns.filter((value) =>
includePatterns.some((pattern) => patternsCouldOverlap(value, pattern)),
);
return [...new Set(matched)];
return narrowIncludePatterns(includePatterns, cliPatterns);
}
+18 -4
View File
@@ -1,7 +1,11 @@
// Vitest scoped config helper builds test configs for scoped file patterns.
import path from "node:path";
import { defineConfig } from "vitest/config";
import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts";
import {
intersectIncludePatterns,
loadPatternListFromEnv,
narrowIncludePatternsForCli,
} from "./vitest.pattern-file.ts";
import {
nonIsolatedRunnerPath,
repoRoot,
@@ -203,6 +207,7 @@ export function createScopedVitestConfig(
isolate?: boolean;
name?: string;
fileParallelism?: boolean;
intersectIncludeFile?: boolean;
pool?: "forks" | "threads";
passWithNoTests?: boolean;
excludeUnitFastTests?: boolean;
@@ -216,7 +221,10 @@ export function createScopedVitestConfig(
const scopedDir = options?.dir;
const resolvedScopedDir = scopedDir ? path.join(repoRoot, scopedDir) : undefined;
const env = options?.env;
const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
const externalIncludePatterns = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
const includeFromEnv = options?.intersectIncludeFile
? intersectIncludePatterns(include, externalIncludePatterns)
: externalIncludePatterns;
const cliInclude = narrowIncludePatternsForCli(include, options?.argv, {
scopedDir,
});
@@ -230,6 +238,9 @@ export function createScopedVitestConfig(
[...(baseTest.exclude ?? []), ...unitFastExcludePatterns, ...(options?.exclude ?? [])],
scopedDir,
);
const scopedEnvInclude = includeFromEnv
? relativizeScopedPatterns(includeFromEnv, scopedDir)
: includeFromEnv;
const scopedCliInclude = cliInclude ? relativizeScopedPatterns(cliInclude, scopedDir) : null;
const isolate = options?.isolate ?? resolveVitestIsolation(options?.env);
const setupFiles = [
@@ -270,9 +281,12 @@ export function createScopedVitestConfig(
}),
...(options?.passWithNoTests !== undefined
? { passWithNoTests: options.passWithNoTests }
: shouldPassWithNoTestsForCliIncludes(scopedCliInclude, exclude)
: externalIncludePatterns !== null &&
shouldPassWithNoTestsForCliIncludes(scopedEnvInclude, exclude)
? { passWithNoTests: true }
: {}),
: shouldPassWithNoTestsForCliIncludes(scopedCliInclude, exclude)
? { passWithNoTests: true }
: {}),
},
});
}
+17 -6
View File
@@ -370,6 +370,22 @@ async function raceWithMacrotask(promise: Promise<unknown>): Promise<"resolved"
]);
}
async function completesWithin(promise: Promise<unknown>, timeoutMs: number): Promise<boolean> {
let timeout: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise.then(() => true),
new Promise<boolean>((resolve) => {
timeout = setTimeout(() => resolve(false), timeoutMs);
}),
]);
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
}
describe("refreshChat", () => {
beforeAll(async () => {
await loadChatHelpers();
@@ -4259,12 +4275,7 @@ describe("handleSendChat", () => {
});
admitHostQueueItems(host);
const completed = await Promise.race([
retryReconnectableQueuedChatSends(host).then(() => true),
new Promise<boolean>((resolve) => {
setTimeout(() => resolve(false), 500);
}),
]);
const completed = await completesWithin(retryReconnectableQueuedChatSends(host), 500);
expect(completed).toBe(true);
expect(executeSlashCommandMock).toHaveBeenCalledTimes(1);