UNPKG

@japa/runner

Version:

A simple yet powerful testing framework for Node.js

285 lines (280 loc) 9.29 kB
import { CliParser, ConfigManager, GlobalHooks, Planner, createTest, createTestGroup, debug_default, validator_default } from "./chunk-Y57JXJ7G.js"; import "./chunk-U3BSXCEH.js"; import { Emitter, Runner, Suite, colors } from "./chunk-PCBL2VZP.js"; import "./chunk-2KG3PWR4.js"; // index.ts import { fileURLToPath } from "url"; import { ErrorsPrinter as ErrorsPrinter2 } from "@japa/errors-printer"; // src/plugins/retry.ts import { join } from "path"; import findCacheDirectory from "find-cache-dir"; import { mkdir, readFile, unlink, writeFile } from "fs/promises"; var CACHE_DIR = findCacheDirectory({ name: "@japa/runner" }); var SUMMARY_FILE = CACHE_DIR ? join(CACHE_DIR, "summary.json") : void 0; async function getFailedTests() { try { const summary = await readFile(SUMMARY_FILE, "utf-8"); return JSON.parse(summary); } catch (error) { if (error.code === "ENOENT") { return {}; } throw new Error("Unable to read failed tests cache file", { cause: error }); } } async function cacheFailedTests(tests) { await mkdir(CACHE_DIR, { recursive: true }); await writeFile(SUMMARY_FILE, JSON.stringify({ tests })); } var retryPlugin = async function retry({ config, cliArgs: cliArgs2 }) { if (!SUMMARY_FILE) { return; } config.teardown.push(async (runner) => { const summary = runner.getSummary(); await cacheFailedTests(summary.failedTestsTitles); }); if (cliArgs2.failed) { try { const { tests } = await getFailedTests(); if (!tests || !tests.length) { console.log(colors.bgYellow().black(" No failing tests found. Running all the tests ")); return; } config.filters.tests = tests; } catch (error) { console.log(colors.bgRed().black(" Unable to read failed tests. Running all the tests ")); console.log(colors.red(error)); } } }; // src/exceptions_manager.ts import { ErrorsPrinter } from "@japa/errors-printer"; var ExceptionsManager = class { #exceptionsBuffer = []; #rejectionsBuffer = []; #state = "watching"; #errorsPrinter = new ErrorsPrinter({ stackLinesCount: 2, framesMaxLimit: 4 }); hasErrors = false; /** * Monitors unhandled exceptions and rejections. The exceptions * are stacked in a buffer, so that we do not clutter the * tests output and once the tests are over, we will * print them to the console. * * In case the tests are completed, we will print errors as they * happen. */ monitor() { process.on("uncaughtException", async (error) => { debug_default("received uncaught exception %O", error); this.hasErrors = true; if (this.#state === "watching") { this.#exceptionsBuffer.push(error); } else { this.#errorsPrinter.printSectionBorder("[Unhandled Error]"); await this.#errorsPrinter.printError(error); process.exitCode = 1; } }); process.on("unhandledRejection", async (error) => { debug_default("received unhandled rejection %O", error); this.hasErrors = true; if (this.#state === "watching") { this.#rejectionsBuffer.push(error); } else { this.#errorsPrinter.printSectionBorder("[Unhandled Rejection]"); await this.#errorsPrinter.printError(error); process.exitCode = 1; } }); } async report() { if (this.#state === "reporting") { return; } this.#state = "reporting"; if (this.#exceptionsBuffer.length) { let exceptionsCount = this.#exceptionsBuffer.length; let exceptionsIndex = this.#exceptionsBuffer.length; this.#errorsPrinter.printSectionHeader("Unhandled Errors"); for (let exception of this.#exceptionsBuffer) { await this.#errorsPrinter.printError(exception); this.#errorsPrinter.printSectionBorder(`[${++exceptionsIndex}/${exceptionsCount}]`); } this.#exceptionsBuffer = []; } if (this.#rejectionsBuffer.length) { let rejectionsCount = this.#exceptionsBuffer.length; let rejectionsIndex = this.#exceptionsBuffer.length; this.#errorsPrinter.printSectionBorder("Unhandled Rejections"); for (let rejection of this.#rejectionsBuffer) { await this.#errorsPrinter.printError(rejection); this.#errorsPrinter.printSectionBorder(`[${++rejectionsIndex}/${rejectionsCount}]`); } this.#rejectionsBuffer = []; } } }; // index.ts var emitter = new Emitter(); var activeTest; var cliArgs = {}; var runnerConfig; var executionPlanState = { phase: "idle" }; function test(title, callback) { validator_default.ensureIsInPlanningPhase(executionPlanState.phase); const testInstance = createTest(title, emitter, runnerConfig.refiner, executionPlanState); testInstance.setup((t) => { activeTest = t; return () => { activeTest = void 0; }; }); if (callback) { testInstance.run(callback, new Error()); } return testInstance; } test.group = function(title, callback) { validator_default.ensureIsInPlanningPhase(executionPlanState.phase); const group = createTestGroup(title, emitter, runnerConfig.refiner, executionPlanState); executionPlanState.group = group; if (cliArgs.bail && cliArgs.bailLayer === "group") { executionPlanState.group.bail(true); } callback(executionPlanState.group); executionPlanState.group = void 0; return group; }; test.macro = function(callback) { return (...args) => { if (!activeTest) { throw new Error("Cannot invoke macro outside of the test callback"); } return callback(activeTest, ...args); }; }; function getActiveTest() { return activeTest; } function getActiveTestOrFail() { if (!activeTest) throw new Error("Cannot access active test outside of a test callback"); return activeTest; } function processCLIArgs(argv) { cliArgs = new CliParser().parse(argv); } function configure(options) { runnerConfig = new ConfigManager(options, cliArgs).hydrate(); } async function run() { if (cliArgs.help) { console.log(new CliParser().getHelp()); return; } validator_default.ensureIsConfigured(runnerConfig); executionPlanState.phase = "planning"; const runner = new Runner(emitter); if (cliArgs.bail && cliArgs.bailLayer === "") { runner.bail(true); } const globalHooks = new GlobalHooks(); const exceptionsManager = new ExceptionsManager(); try { await retryPlugin({ config: runnerConfig, runner, emitter, cliArgs }); for (let plugin of runnerConfig.plugins) { debug_default('executing "%s" plugin', plugin.name || "anonymous"); await plugin({ runner, emitter, cliArgs, config: runnerConfig }); } const { config, reporters, suites, refinerFilters } = await new Planner(runnerConfig).plan(); reporters.forEach((reporter) => { debug_default('registering "%s" reporter', reporter.name); runner.registerReporter(reporter); }); refinerFilters.forEach((filter) => { debug_default('apply %s filters "%O" ', filter.layer, filter.filters); config.refiner.add(filter.layer, filter.filters); }); config.refiner.matchAllTags(cliArgs.matchAll ?? false); runner.onSuite(config.configureSuite); debug_default("executing global hooks"); globalHooks.apply(config); await globalHooks.setup(runner); for (let suite of suites) { debug_default("initiating suite %s", suite.name); executionPlanState.suite = new Suite(suite.name, emitter, config.refiner); executionPlanState.retries = suite.retries; executionPlanState.timeout = suite.timeout; if (typeof suite.configure === "function") { suite.configure(executionPlanState.suite); } if (cliArgs.bail && cliArgs.bailLayer === "suite") { debug_default("enabling bail mode for the suite %s", suite.name); executionPlanState.suite.bail(true); } runner.add(executionPlanState.suite); for (let fileURL of suite.filesURLs) { executionPlanState.file = fileURLToPath(fileURL); debug_default("importing test file %s", executionPlanState.file); await config.importer(fileURL); } executionPlanState.suite = void 0; } executionPlanState.phase = "executing"; exceptionsManager.monitor(); await runner.start(); await runner.exec(); await globalHooks.teardown(null, runner); await runner.end(); await exceptionsManager.report(); const summary = runner.getSummary(); if (summary.hasError || exceptionsManager.hasErrors) { debug_default( "updating exit code to 1. summary.hasError %s, process.hasError", summary.hasError, exceptionsManager.hasErrors ); process.exitCode = 1; } if (config.forceExit) { debug_default("force exiting process"); process.exit(); } } catch (error) { debug_default("error running tests %O", error); await globalHooks.teardown(error, runner); const printer = new ErrorsPrinter2(); await printer.printError(error); await exceptionsManager.report(); process.exitCode = 1; if (runnerConfig.forceExit) { debug_default("force exiting process"); process.exit(); } } } export { configure, getActiveTest, getActiveTestOrFail, processCLIArgs, run, test };