mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
* test: harden the test strategy (triggers, runner speed, release guards) Follow-ups from an end-to-end testing strategy review: - Suite triggers are now auto-generated from each suite's own file list, so change-based CI can never miss a test file again (four files were unreachable by their own edits, and tests/lib/detector-bundle.test.js triggered core while running in detector). Two new meta-tests pin the invariant. Hand-written trigger patterns now carry only source paths and fixture dirs; palette dropped from the live triggers since no suite tests it. - The node runner batches all files into one node --test invocation at concurrency 4 instead of spawning per file. Default suite drops from ~159s to ~100s; the live suite soaked clean three times. - scripts/release.mjs gets its first tests: 12 scenarios spawning the real script inside a disposable git repo with a local bare origin, covering every refusal guard plus notes/tweet rendering, all under --dry-run. - skill/scripts/live/ui-core.mjs deleted: zero references repo-wide, superseded by the July live rewrite, yet still shipping to users. cli/lib/download-providers.js annotated with its cross-repo consumers (impeccable-site Pages Functions) so it is not mistaken for dead code. - CLAUDE.md gains an area-to-suite table for the opt-in suites a change owes; AGENTS.md syncs the plugin-e2e commands and obligations. AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: exclude peeled tag lines from release-test origin cleanup Copilot: git ls-remote --tags emits ^{} peel lines for annotated tags, which are not deletable refs; --refs filters them so the cleanup loop survives a future scenario that pushes an annotated tag. AI-assisted via Claude Code under maintainer direction. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
100 lines
2.9 KiB
JavaScript
100 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
import { spawnSync } from 'node:child_process';
|
|
import { DEFAULT_SUITES, OPT_IN_SUITES, SUITES, expandSuites } from './test-suites.mjs';
|
|
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.includes('--help') || args.includes('-h')) {
|
|
printHelp();
|
|
process.exit(0);
|
|
}
|
|
|
|
if (args.includes('--list')) {
|
|
printSuites();
|
|
process.exit(0);
|
|
}
|
|
|
|
const requestedSuites = args.filter((arg) => !arg.startsWith('-'));
|
|
let suites;
|
|
try {
|
|
suites = expandSuites(requestedSuites);
|
|
} catch (err) {
|
|
console.error(err.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
for (const suiteName of suites) {
|
|
const suite = SUITES[suiteName];
|
|
console.log(`\n## test:${suiteName}`);
|
|
console.log(suite.description);
|
|
for (const command of suite.commands) {
|
|
runCommand(command);
|
|
}
|
|
}
|
|
|
|
function runCommand(command) {
|
|
const env = { ...process.env, ...(command.env || {}) };
|
|
if (command.runner === 'bun') {
|
|
runProcess('bun', ['test', ...command.files], { env });
|
|
return;
|
|
}
|
|
|
|
if (command.runner === 'node') {
|
|
// One invocation for the whole file list: node --test runs each file in
|
|
// its own child process regardless, so isolation is unchanged, but the
|
|
// runner-per-file spawn overhead is gone and files execute concurrently.
|
|
// Measured on the live suite (38 files): 52s serial-per-file vs 18s
|
|
// batched at concurrency 4. Suites can pin `concurrency: 1` if their
|
|
// tests ever contend for a shared resource.
|
|
const args = ['--test', `--test-concurrency=${command.concurrency ?? 4}`];
|
|
if (command.timeoutMs) args.push(`--test-timeout=${command.timeoutMs}`);
|
|
if (command.forceExit) args.push('--test-force-exit');
|
|
args.push(...command.files);
|
|
runProcess(process.execPath, args, { env });
|
|
return;
|
|
}
|
|
|
|
throw new Error(`Unsupported test runner "${command.runner}"`);
|
|
}
|
|
|
|
function runProcess(cmd, args, { env }) {
|
|
console.log(`$ ${formatCommand(cmd, args)}`);
|
|
const result = spawnSync(cmd, args, {
|
|
stdio: 'inherit',
|
|
env,
|
|
});
|
|
if (result.error) {
|
|
console.error(result.error.message);
|
|
process.exit(1);
|
|
}
|
|
if (result.status !== 0) process.exit(result.status || 1);
|
|
}
|
|
|
|
function formatCommand(cmd, args) {
|
|
const bin = cmd === process.execPath ? 'node' : cmd;
|
|
return [bin, ...args].join(' ');
|
|
}
|
|
|
|
function printHelp() {
|
|
console.log(`Usage: node scripts/run-tests.mjs [suite...]
|
|
|
|
Aliases:
|
|
default ${DEFAULT_SUITES.join(', ')}
|
|
all-local ${DEFAULT_SUITES.join(', ')}
|
|
all ${[...DEFAULT_SUITES, ...OPT_IN_SUITES].join(', ')}
|
|
|
|
Run with --list to see suite contents.`);
|
|
}
|
|
|
|
function printSuites() {
|
|
for (const [name, suite] of Object.entries(SUITES)) {
|
|
const marker = suite.optIn ? ' (opt-in)' : '';
|
|
console.log(`\n${name}${marker}`);
|
|
console.log(` ${suite.description}`);
|
|
for (const command of suite.commands) {
|
|
console.log(` ${command.runner}:`);
|
|
for (const file of command.files) console.log(` ${file}`);
|
|
}
|
|
}
|
|
}
|