diff --git a/src/selftest/run.ts b/src/selftest/run.ts index 75ee331..0496163 100644 --- a/src/selftest/run.ts +++ b/src/selftest/run.ts @@ -5,24 +5,64 @@ import { createSelfTestContext, type SelfTestCase, type SelfTestResult } from ". const root = path.resolve(import.meta.dirname, "../.."); const casesDir = path.join(root, "src/selftest/cases"); -const context = await createSelfTestContext(root); + +const args = process.argv.slice(2); +const listOnly = args.includes("--list"); +const helpRequested = args.some((arg) => arg === "--help" || arg === "-h"); +const requestedCases = args.filter((arg) => arg !== "--list" && arg !== "--help" && arg !== "-h"); + +function caseName(file: string): string { + return file.replace(/\.ts$/u, ""); +} + +function normalizeSelector(value: string): string { + return caseName(path.basename(value)); +} try { const caseFiles = (await readdir(casesDir)) .filter((file) => file.endsWith(".ts") && !file.endsWith(".d.ts")) .sort(); - const results: SelfTestResult[] = []; + const availableCases = caseFiles.map(caseName); - for (const file of caseFiles) { - const moduleUrl = pathToFileURL(path.join(casesDir, file)).href; - const imported = await import(moduleUrl) as { default?: SelfTestCase; selfTest?: SelfTestCase }; - const selfTest = imported.default ?? imported.selfTest; - if (typeof selfTest !== "function") throw new Error(`self-test case ${file} must export default or selfTest function`); - const result = await selfTest(context); - results.push({ name: result.name ?? file.replace(/\.ts$/u, ""), tests: result.tests ?? [] }); + if (helpRequested) { + console.log(JSON.stringify({ ok: true, usage: "bun run src/selftest/run.ts [--list] [case ...]", availableCases })); + process.exit(0); } - console.log(JSON.stringify({ ok: true, cases: results, tests: results.flatMap((result) => result.tests) })); -} finally { - await context.cleanup(); + if (listOnly) { + console.log(JSON.stringify({ ok: true, cases: availableCases })); + process.exit(0); + } + + const requestedSet = new Set(requestedCases.map(normalizeSelector)); + const selectedCaseFiles = requestedSet.size === 0 + ? caseFiles + : caseFiles.filter((file) => requestedSet.has(caseName(file))); + const missingCases = [...requestedSet].filter((name) => !availableCases.includes(name)); + if (missingCases.length > 0) { + console.error(JSON.stringify({ ok: false, error: "unknown-selftest-case", requestedCases: [...requestedSet], missingCases, availableCases })); + process.exit(1); + } + + const context = await createSelfTestContext(root); + const results: SelfTestResult[] = []; + + try { + for (const file of selectedCaseFiles) { + const moduleUrl = pathToFileURL(path.join(casesDir, file)).href; + const imported = await import(moduleUrl) as { default?: SelfTestCase; selfTest?: SelfTestCase }; + const selfTest = imported.default ?? imported.selfTest; + if (typeof selfTest !== "function") throw new Error(`self-test case ${file} must export default or selfTest function`); + const result = await selfTest(context); + results.push({ name: result.name ?? caseName(file), tests: result.tests ?? [] }); + } + } finally { + await context.cleanup(); + } + + console.log(JSON.stringify({ ok: true, selectedCases: selectedCaseFiles.map(caseName), cases: results, tests: results.flatMap((result) => result.tests) })); +} catch (error) { + console.error(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) })); + process.exit(1); }