Tests: align unit sharding with unit config
This commit is contained in:
parent
e6911f0448
commit
e9903c9133
@ -3,6 +3,7 @@ import fs from "node:fs";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { channelTestPrefixes } from "../vitest.channel-paths.mjs";
|
import { channelTestPrefixes } from "../vitest.channel-paths.mjs";
|
||||||
|
import { isUnitConfigTestFile } from "../vitest.unit-paths.mjs";
|
||||||
import {
|
import {
|
||||||
loadTestRunnerBehavior,
|
loadTestRunnerBehavior,
|
||||||
loadUnitTimingManifest,
|
loadUnitTimingManifest,
|
||||||
@ -16,10 +17,11 @@ const pnpm = "pnpm";
|
|||||||
const behaviorManifest = loadTestRunnerBehavior();
|
const behaviorManifest = loadTestRunnerBehavior();
|
||||||
const existingFiles = (entries) =>
|
const existingFiles = (entries) =>
|
||||||
entries.map((entry) => entry.file).filter((file) => fs.existsSync(file));
|
entries.map((entry) => entry.file).filter((file) => fs.existsSync(file));
|
||||||
const unitBehaviorIsolatedFiles = existingFiles(behaviorManifest.unit.isolated);
|
const existingUnitConfigFiles = (entries) => existingFiles(entries).filter(isUnitConfigTestFile);
|
||||||
const unitSingletonIsolatedFiles = existingFiles(behaviorManifest.unit.singletonIsolated);
|
const unitBehaviorIsolatedFiles = existingUnitConfigFiles(behaviorManifest.unit.isolated);
|
||||||
const unitThreadSingletonFiles = existingFiles(behaviorManifest.unit.threadSingleton);
|
const unitSingletonIsolatedFiles = existingUnitConfigFiles(behaviorManifest.unit.singletonIsolated);
|
||||||
const unitVmForkSingletonFiles = existingFiles(behaviorManifest.unit.vmForkSingleton);
|
const unitThreadSingletonFiles = existingUnitConfigFiles(behaviorManifest.unit.threadSingleton);
|
||||||
|
const unitVmForkSingletonFiles = existingUnitConfigFiles(behaviorManifest.unit.vmForkSingleton);
|
||||||
const unitBehaviorOverrideSet = new Set([
|
const unitBehaviorOverrideSet = new Set([
|
||||||
...unitBehaviorIsolatedFiles,
|
...unitBehaviorIsolatedFiles,
|
||||||
...unitSingletonIsolatedFiles,
|
...unitSingletonIsolatedFiles,
|
||||||
@ -237,10 +239,7 @@ const parseEnvNumber = (name, fallback) => {
|
|||||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
||||||
};
|
};
|
||||||
const allKnownUnitFiles = allKnownTestFiles.filter((file) => {
|
const allKnownUnitFiles = allKnownTestFiles.filter((file) => {
|
||||||
if (file.endsWith(".live.test.ts") || file.endsWith(".e2e.test.ts")) {
|
return isUnitConfigTestFile(file);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return inferTarget(file).owner !== "gateway";
|
|
||||||
});
|
});
|
||||||
const defaultHeavyUnitFileLimit =
|
const defaultHeavyUnitFileLimit =
|
||||||
testProfile === "serial" ? 0 : testProfile === "low" ? 20 : highMemLocalHost ? 80 : 60;
|
testProfile === "serial" ? 0 : testProfile === "low" ? 20 : highMemLocalHost ? 80 : 60;
|
||||||
@ -730,10 +729,12 @@ const runOnce = (entry, extraArgs = []) =>
|
|||||||
|
|
||||||
const run = async (entry, extraArgs = []) => {
|
const run = async (entry, extraArgs = []) => {
|
||||||
const explicitFilterCount = countExplicitEntryFilters(entry.args);
|
const explicitFilterCount = countExplicitEntryFilters(entry.args);
|
||||||
// Wrapper-generated singleton/small-file lanes should not ask Vitest to shard
|
// Vitest requires the shard count to stay strictly below the number of
|
||||||
// into more buckets than there are explicit test filters.
|
// resolved test files, so explicit-filter lanes need a `< fileCount` cap.
|
||||||
const effectiveShardCount =
|
const effectiveShardCount =
|
||||||
explicitFilterCount === null ? shardCount : Math.min(shardCount, explicitFilterCount);
|
explicitFilterCount === null
|
||||||
|
? shardCount
|
||||||
|
: Math.min(shardCount, Math.max(1, explicitFilterCount - 1));
|
||||||
|
|
||||||
if (effectiveShardCount <= 1) {
|
if (effectiveShardCount <= 1) {
|
||||||
if (shardIndexOverride !== null && shardIndexOverride > effectiveShardCount) {
|
if (shardIndexOverride !== null && shardIndexOverride > effectiveShardCount) {
|
||||||
|
|||||||
21
test/vitest-unit-paths.test.ts
Normal file
21
test/vitest-unit-paths.test.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { isUnitConfigTestFile } from "../vitest.unit-paths.mjs";
|
||||||
|
|
||||||
|
describe("isUnitConfigTestFile", () => {
|
||||||
|
it("accepts unit-config src, test, and whitelisted ui tests", () => {
|
||||||
|
expect(isUnitConfigTestFile("src/infra/git-commit.test.ts")).toBe(true);
|
||||||
|
expect(isUnitConfigTestFile("test/format-error.test.ts")).toBe(true);
|
||||||
|
expect(isUnitConfigTestFile("ui/src/ui/views/chat.test.ts")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects files excluded from the unit config", () => {
|
||||||
|
expect(
|
||||||
|
isUnitConfigTestFile("extensions/imessage/src/monitor.shutdown.unhandled-rejection.test.ts"),
|
||||||
|
).toBe(false);
|
||||||
|
expect(isUnitConfigTestFile("src/agents/pi-embedded-runner.test.ts")).toBe(false);
|
||||||
|
expect(isUnitConfigTestFile("src/commands/onboard.test.ts")).toBe(false);
|
||||||
|
expect(isUnitConfigTestFile("ui/src/ui/views/other.test.ts")).toBe(false);
|
||||||
|
expect(isUnitConfigTestFile("src/infra/git-commit.live.test.ts")).toBe(false);
|
||||||
|
expect(isUnitConfigTestFile("src/infra/git-commit.e2e.test.ts")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
46
vitest.unit-paths.mjs
Normal file
46
vitest.unit-paths.mjs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
export const unitTestIncludePatterns = [
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"test/**/*.test.ts",
|
||||||
|
"ui/src/ui/app-chat.test.ts",
|
||||||
|
"ui/src/ui/views/agents-utils.test.ts",
|
||||||
|
"ui/src/ui/views/chat.test.ts",
|
||||||
|
"ui/src/ui/views/usage-render-details.test.ts",
|
||||||
|
"ui/src/ui/controllers/agents.test.ts",
|
||||||
|
"ui/src/ui/controllers/chat.test.ts",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const unitTestAdditionalExcludePatterns = [
|
||||||
|
"src/gateway/**",
|
||||||
|
"extensions/**",
|
||||||
|
"src/browser/**",
|
||||||
|
"src/line/**",
|
||||||
|
"src/agents/**",
|
||||||
|
"src/auto-reply/**",
|
||||||
|
"src/commands/**",
|
||||||
|
];
|
||||||
|
|
||||||
|
const sharedBaseExcludePatterns = [
|
||||||
|
"dist/**",
|
||||||
|
"apps/macos/**",
|
||||||
|
"apps/macos/.build/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/vendor/**",
|
||||||
|
"dist/OpenClaw.app/**",
|
||||||
|
"**/*.live.test.ts",
|
||||||
|
"**/*.e2e.test.ts",
|
||||||
|
];
|
||||||
|
|
||||||
|
const normalizeRepoPath = (value) => value.split(path.sep).join("/");
|
||||||
|
|
||||||
|
const matchesAny = (file, patterns) => patterns.some((pattern) => path.matchesGlob(file, pattern));
|
||||||
|
|
||||||
|
export function isUnitConfigTestFile(file) {
|
||||||
|
const normalizedFile = normalizeRepoPath(file);
|
||||||
|
return (
|
||||||
|
matchesAny(normalizedFile, unitTestIncludePatterns) &&
|
||||||
|
!matchesAny(normalizedFile, sharedBaseExcludePatterns) &&
|
||||||
|
!matchesAny(normalizedFile, unitTestAdditionalExcludePatterns)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,27 +1,19 @@
|
|||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
import baseConfig from "./vitest.config.ts";
|
import baseConfig from "./vitest.config.ts";
|
||||||
|
import {
|
||||||
|
unitTestAdditionalExcludePatterns,
|
||||||
|
unitTestIncludePatterns,
|
||||||
|
} from "./vitest.unit-paths.mjs";
|
||||||
|
|
||||||
const base = baseConfig as unknown as Record<string, unknown>;
|
const base = baseConfig as unknown as Record<string, unknown>;
|
||||||
const baseTest = (baseConfig as { test?: { include?: string[]; exclude?: string[] } }).test ?? {};
|
const baseTest = (baseConfig as { test?: { include?: string[]; exclude?: string[] } }).test ?? {};
|
||||||
const include = (
|
|
||||||
baseTest.include ?? ["src/**/*.test.ts", "extensions/**/*.test.ts", "test/format-error.test.ts"]
|
|
||||||
).filter((pattern) => !pattern.includes("extensions/"));
|
|
||||||
const exclude = baseTest.exclude ?? [];
|
const exclude = baseTest.exclude ?? [];
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
...base,
|
...base,
|
||||||
test: {
|
test: {
|
||||||
...baseTest,
|
...baseTest,
|
||||||
include,
|
include: unitTestIncludePatterns,
|
||||||
exclude: [
|
exclude: [...exclude, ...unitTestAdditionalExcludePatterns],
|
||||||
...exclude,
|
|
||||||
"src/gateway/**",
|
|
||||||
"extensions/**",
|
|
||||||
"src/browser/**",
|
|
||||||
"src/line/**",
|
|
||||||
"src/agents/**",
|
|
||||||
"src/auto-reply/**",
|
|
||||||
"src/commands/**",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user