Speed up slow test suites

This commit is contained in:
murashit 2026-06-24 17:03:40 +09:00
parent 75937b332a
commit f475fc4491
6 changed files with 95 additions and 77 deletions

View file

@ -14,7 +14,7 @@
"*.mjs",
"scripts/**/*.mjs",
"src/**/*.{css,ts,tsx}",
"tests/**/*.{ts,tsx}",
"tests/**/*.{mjs,ts,tsx}",
"!src/generated"
]
},

View file

@ -4,7 +4,7 @@ import tseslint from "typescript-eslint";
import codexPanelEslintPlugin from "./scripts/lint/eslint-plugin-codex-panel.mjs";
const typeScriptFiles = ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}"];
const nodeJavaScriptFiles = ["*.mjs", "scripts/**/*.mjs"];
const nodeJavaScriptFiles = ["*.mjs", "scripts/**/*.mjs", "tests/**/*.mjs"];
const typeScriptConfigFiles = ["*.config.ts"];
const lintedTypeScriptFiles = [...typeScriptFiles, ...typeScriptConfigFiles];
const unsafeAnyTypeScriptRules = {

46
scripts/check-plan.mjs Normal file
View file

@ -0,0 +1,46 @@
export const biomeLintCommand = "biome lint --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings";
export const biomeCheckCommand = "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings";
export function commandPlan({ ciMode, lintOnly }) {
if (lintOnly) {
return {
phases: [{ name: "lint", commands: lintCommands({ ciMode }) }],
};
}
return {
phases: [
{ name: "check", commands: checkCommands({ ciMode }) },
{ name: "build", commands: [{ name: "build", command: "node esbuild.config.mjs" }] },
],
};
}
function checkCommands({ ciMode }) {
const suffix = ciMode ? ":ci" : "";
return [
{ name: "typecheck", command: `npm run --silent typecheck${suffix}` },
{ name: "test", command: `npm run --silent test${suffix}` },
{ name: "lint:biome", command: biomeCheckCommand },
...nonBiomeLintCommands({ ciMode, namePrefix: "lint:" }),
];
}
function lintCommands({ ciMode, namePrefix = "" }) {
return [{ name: `${namePrefix}biome`, command: biomeLintCommand }, ...nonBiomeLintCommands({ ciMode, namePrefix })];
}
function nonBiomeLintCommands({ ciMode, namePrefix = "" }) {
const eslintCacheArgs = ciMode ? "" : " --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache";
return [
{
name: `${namePrefix}eslint`,
command: `eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0${eslintCacheArgs}`,
},
{ name: `${namePrefix}css`, command: 'stylelint "src/**/*.css" --max-warnings=0' },
{ name: `${namePrefix}css-usage`, command: "node scripts/lint/check-css-usage.mjs" },
{ name: `${namePrefix}unused`, command: "knip --no-progress" },
];
}

View file

@ -1,4 +1,5 @@
import concurrently from "concurrently";
import { commandPlan } from "./check-plan.mjs";
const args = process.argv.slice(2);
const validArgs = new Set(["--ci", "--lint"]);
@ -16,15 +17,11 @@ const concurrentOptions = {
padPrefix: true,
prefix: "name",
};
const biomeLintCommand = "biome lint --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings";
const biomeCheckCommand = "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings";
try {
if (lintOnly) {
await runConcurrently(lintCommands({ ciMode }));
} else {
await runConcurrently(checkCommands({ ciMode }));
await runConcurrently([{ name: "build", command: "node esbuild.config.mjs" }]);
const plan = commandPlan({ ciMode, lintOnly });
for (const phase of plan.phases) {
await runConcurrently(phase.commands);
}
} catch {
process.exit(1);
@ -33,32 +30,3 @@ try {
async function runConcurrently(commands) {
await concurrently(commands, concurrentOptions).result;
}
function checkCommands({ ciMode }) {
const suffix = ciMode ? ":ci" : "";
return [
{ name: "typecheck", command: `npm run --silent typecheck${suffix}` },
{ name: "test", command: `npm run --silent test${suffix}` },
{ name: "lint:biome", command: biomeCheckCommand },
...nonBiomeLintCommands({ ciMode, namePrefix: "lint:" }),
];
}
function lintCommands({ ciMode, namePrefix = "" }) {
return [{ name: `${namePrefix}biome`, command: biomeLintCommand }, ...nonBiomeLintCommands({ ciMode, namePrefix })];
}
function nonBiomeLintCommands({ ciMode, namePrefix = "" }) {
const eslintCacheArgs = ciMode ? "" : " --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache";
return [
{
name: `${namePrefix}eslint`,
command: `eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0${eslintCacheArgs}`,
},
{ name: `${namePrefix}css`, command: 'stylelint "src/**/*.css" --max-warnings=0' },
{ name: `${namePrefix}css-usage`, command: "node scripts/lint/check-css-usage.mjs" },
{ name: `${namePrefix}unused`, command: "knip --no-progress" },
];
}

View file

@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { commandPlan } from "../../scripts/check-plan.mjs";
describe("check command plan", () => {
it("runs the local preflight checks before the build", () => {
expect(commandPlan({ ciMode: false, lintOnly: false }).phases).toEqual([
{
name: "check",
commands: [
{ name: "typecheck", command: "npm run --silent typecheck" },
{ name: "test", command: "npm run --silent test" },
{ name: "lint:biome", command: "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings" },
{
name: "lint:eslint",
command:
'eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0 --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache',
},
{ name: "lint:css", command: 'stylelint "src/**/*.css" --max-warnings=0' },
{ name: "lint:css-usage", command: "node scripts/lint/check-css-usage.mjs" },
{ name: "lint:unused", command: "knip --no-progress" },
],
},
{
name: "build",
commands: [{ name: "build", command: "node esbuild.config.mjs" }],
},
]);
});
it("keeps lint-only mode on Biome lint", () => {
expect(commandPlan({ ciMode: false, lintOnly: true }).phases[0]?.commands).toContainEqual({
name: "biome",
command: "biome lint --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings",
});
});
it("omits cache-only local flags in CI mode", () => {
expect(commandPlan({ ciMode: true, lintOnly: false }).phases[0]?.commands).toContainEqual({
name: "lint:eslint",
command: 'eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0',
});
});
});

View file

@ -116,28 +116,6 @@ describe("development scripts", () => {
expect(report.failures).toEqual([]);
});
it("runs Biome lint with warning-level diagnostics", async () => {
const cwd = await tempWorkspace();
const binDir = path.join(cwd, "bin");
await mkdir(binDir, { recursive: true });
await mkdir(path.join(cwd, "scripts", "lint"), { recursive: true });
await writeFile(path.join(cwd, "scripts", "lint", "check-css-usage.mjs"), "");
await writeCaptureBin(path.join(binDir, "biome"), "biome-args.txt");
await writeExitZeroBin(path.join(binDir, "eslint"));
await writeExitZeroBin(path.join(binDir, "stylelint"));
await writeExitZeroBin(path.join(binDir, "knip"));
const result = runNodeScript("scripts/check.mjs", ["--lint"], cwd, {
PATH: `${binDir}${path.delimiter}${process.env["PATH"] ?? ""}`,
});
expect(result.status).toBe(0);
await expect(readFile(path.join(cwd, "biome-args.txt"), "utf8")).resolves.toBe(
"lint\n--no-errors-on-unmatched\n--diagnostic-level=warn\n--error-on-warnings\n",
);
});
it("keeps CSS usage checks quiet when every class is used", async () => {
const cwd = await cssUsageFixture({
"src/styles/10-component.css": ".codex-panel__used { display: block; }\n",
@ -272,20 +250,3 @@ async function writeJson(file: string, value: unknown): Promise<void> {
async function readJson(file: string): Promise<unknown> {
return JSON.parse(await readFile(file, "utf8"));
}
async function writeCaptureBin(file: string, outputFile: string): Promise<void> {
await writeFile(
file,
[
"#!/usr/bin/env node",
"const { appendFileSync } = require('node:fs');",
`appendFileSync(${JSON.stringify(outputFile)}, \`\${process.argv.slice(2).join("\\n")}\\n\`);`,
].join("\n"),
);
await chmod(file, 0o755);
}
async function writeExitZeroBin(file: string): Promise<void> {
await writeFile(file, "#!/usr/bin/env node\n");
await chmod(file, 0o755);
}