@japa/runner
Version:
A simple yet powerful testing framework for Node.js
285 lines (280 loc) • 9.29 kB
JavaScript
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 {
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.
this.
} else {
this.
await this.
process.exitCode = 1;
}
});
process.on("unhandledRejection", async (error) => {
debug_default("received unhandled rejection %O", error);
this.hasErrors = true;
if (this.
this.
} else {
this.
await this.
process.exitCode = 1;
}
});
}
async report() {
if (this.
return;
}
this.
if (this.
let exceptionsCount = this.
let exceptionsIndex = this.
this.
for (let exception of this.
await this.
this.
}
this.
}
if (this.
let rejectionsCount = this.
let rejectionsIndex = this.
this.
for (let rejection of this.
await this.
this.
}
this.
}
}
};
// 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
};