UNPKG

@sondr3/minitest

Version:

A low-feature, dependency-free and performant test runner inspired by Rust and Deno

158 lines 5.44 kB
import { promises as fs } from "node:fs"; import { join, parse } from "node:path"; import { performance } from "node:perf_hooks"; import { pathToFileURL } from "node:url"; import { parseCli, printVersionHelp } from "./cli.js"; import { color, mapSize } from "./utils.js"; export const TESTS = []; export const ignoreDir = (dir) => dir === "node_modules" || dir.startsWith("."); const testExt = (ext) => ext === ".js" || ext === ".mjs"; export const testFile = (file) => { const { name, ext } = parse(file); return testExt(ext) && (name === "test" || name.endsWith(".test") || name.endsWith("_test")); }; export async function* walkDir(dir) { for await (const d of await fs.opendir(dir)) { const entry = join(dir, d.name); if (d.isDirectory() && !ignoreDir(d.name)) { yield* walkDir(entry); } else if (d.isFile() && testFile(d.name)) { yield entry; } } } class Runner { quiet; failFast; filterFn; filter = false; failures = []; tests = new Map(); only = false; ok = 0; failed = 0; ignored = 0; filtered = 0; constructor(quiet, failFast, filter) { this.quiet = quiet; this.failFast = failFast; if (filter) { this.filterFn = filter; this.filter = true; } } async run(entry) { await this.collect(entry); this.filterTestsMarkedOnly(); if (this.filter) { this.filterTests(); } await this.runTests(); } report() { const time = performance.now().toFixed(0); const success = this.failed > 0 ? color("FAILED", "red") : color("ok", "green"); if (this.failed) { process.stdout.write(`\n\nfailures:\n`); this.failures .filter(([, err]) => err) .forEach(([name, err]) => { process.stdout.write(`\n---- ${name} message ----\n`); if (err?.stack) { process.stdout.write(err.stack); } else { process.stdout.write(err?.message ?? ""); } process.stdout.write(`\n`); }); process.stdout.write(`\n\nfailures:\n`); this.failures.forEach(([name]) => process.stdout.write(`\t${name}\n`)); process.stdout.write(`\n`); } process.stdout.write(`\ntest result: ${success}. ${this.ok} passed; ${this.failed} failed; ${this.ignored} ignored; ${this.filtered} filtered out; finished in ${time}ms\n\n`); if (this.failed > 0 || this.only) { process.exit(1); } } async collect(entry) { if ((await fs.stat(entry)).isFile()) { await import(pathToFileURL(entry).toString()); this.addTests(entry); } else { for await (const file of walkDir(entry)) { await import(pathToFileURL(file).toString()); this.addTests(file); } } } addTests(entry) { this.tests.set(entry, TESTS.splice(0).map((t) => t.toTestRunner())); } filterTestsMarkedOnly() { const onlyTests = new Map(); for (const [file, tests] of this.tests.entries()) { if (tests.some((t) => t.only)) { const onlies = tests.filter((t) => t.only); this.only = true; onlyTests.set(file, onlies); } } if (this.only) { this.tests = onlyTests; } } async runTests() { const count = Array.from(this.tests.values()).reduce((prev, it) => prev + it.length, 0); process.stdout.write(`running ${count} ${count === 1 ? "test" : "tests"}\n`); for (const [file, xs] of this.tests.entries()) { if (!this.quiet) { process.stdout.write(`running ${xs.length} ${xs.length === 1 ? "test" : "tests"} in ${file}\n`); } for (const test of xs) { const res = await test.run(); if (test.ignore) { this.ignored += 1; } else if (res) { this.ok += 1; } else { this.failed += 1; this.failures.push([test.name, test.error]); } test.result(this.quiet); if (this.shouldFail()) { return; } } } } shouldFail() { if (this.failFast === undefined) return false; return this.failed > this.failFast; } filterTests() { const filtered = Array.from(this.tests.entries()).flatMap(([file, xs]) => { const filtered = xs.filter((t) => this.filterFn(t.name)); return filtered.length > 0 ? [[file, filtered]] : []; }); const tests = new Map(filtered); this.filtered = mapSize(this.tests) - mapSize(tests); this.tests = tests; } } export async function run(argv) { const { dir, quiet, filter, failFast, version, help } = parseCli(argv); if (version || help) { printVersionHelp(version, help); process.exit(0); } const runner = new Runner(quiet, failFast, filter); await runner.run(dir); runner.report(); } //# sourceMappingURL=runner.js.map