mocha
Version:
Classic, reliable, trusted test framework for Node.js and the browser
165 lines (142 loc) • 4.67 kB
JavaScript
/**
* This wrapper executable checks for known node flags and appends them when found,
* before invoking the "real" executable (`lib/cli/cli.js`)
*
* @module bin/mocha
* @private
*/
import d from "debug";
import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import { unparseMochaArgs } from "../lib/cli/unparse-args.js";
import { main } from "../lib/cli/cli.js";
import { loadOptions } from "../lib/cli/options.cjs";
import {
unparseNodeFlags,
isNodeFlag,
impliesNoTimeouts,
} from "../lib/cli/node-flags.cjs";
import { fileURLToPath } from "node:url";
const debug = d.debug("mocha:cli:mocha");
const mochaArgs = {};
const nodeArgs = {};
const SIGNAL_OFFSET = 128;
let hasInspect = false;
const opts = loadOptions(process.argv.slice(2));
debug("loaded opts", opts);
/**
* Given option/command `value`, disable timeouts if applicable
* @param {string} [value] - Value to check
* @ignore
*/
const disableTimeouts = (value) => {
if (impliesNoTimeouts(value)) {
debug("option %s disabled timeouts", value);
mochaArgs.timeout = 0;
}
};
/**
* If `value` begins with `v8-` and is not explicitly `v8-options`, remove prefix
* @param {string} [value] - Value to check
* @returns {string} `value` with prefix (maybe) removed
* @ignore
*/
const trimV8Option = (value) =>
value !== "v8-options" && /^v8-/.test(value) ? value.slice(3) : value;
// sort options into "node" and "mocha" buckets
Object.keys(opts).forEach((opt) => {
if (isNodeFlag(opt)) {
nodeArgs[trimV8Option(opt)] = opts[opt];
} else {
mochaArgs[opt] = opts[opt];
}
});
// disable 'timeout' for debugFlags
Object.keys(nodeArgs).forEach((opt) => disableTimeouts(opt));
mochaArgs["node-option"] &&
mochaArgs["node-option"].forEach((opt) => disableTimeouts(opt));
// Native debugger handling
// see https://nodejs.org/api/debugger.html#debugger_debugger
// look for 'inspect' that would launch this debugger,
// remove it from Mocha's opts and prepend it to Node's opts.
// A deprecation warning will be printed by node, if applicable.
// (mochaArgs._ are "positional" arguments, not prefixed with - or --)
if (mochaArgs._) {
const i = mochaArgs._.findIndex((val) => val === "inspect");
if (i > -1) {
mochaArgs._.splice(i, 1);
disableTimeouts("inspect");
hasInspect = true;
}
}
if (mochaArgs["node-option"] || Object.keys(nodeArgs).length || hasInspect) {
const mochaPath = fileURLToPath(import.meta.resolve("../lib/cli/cli.js"));
const nodeArgv =
(mochaArgs["node-option"] &&
mochaArgs["node-option"].map((v) => "--" + v)) ||
unparseNodeFlags(nodeArgs);
if (hasInspect) nodeArgv.unshift("inspect");
delete mochaArgs["node-option"];
debug("final node argv", nodeArgv);
const stdio = ["inherit", "inherit", "inherit"];
mochaArgs._ = mochaArgs._?.map((arg) => {
const match = /^\/dev\/fd\/(\d+)$/.exec(arg);
if (!match || !fs.existsSync(arg)) {
return arg;
}
stdio.push(Number(match[1]));
return `/dev/fd/${stdio.length - 1}`;
});
const args = [].concat(
nodeArgv,
mochaPath,
unparseMochaArgs(mochaArgs),
);
debug(
"forking child process via command: %s %s",
process.execPath,
args.join(" "),
);
const proc = spawn(process.execPath, args, {
stdio,
});
proc.on("exit", (code, signal) => {
process.on("exit", () => {
if (signal) {
signal =
typeof signal === "string" ? os.constants.signals[signal] : signal;
if (mochaArgs["posix-exit-codes"] === true) {
process.exitCode = SIGNAL_OFFSET + signal;
}
process.kill(process.pid, signal);
} else {
process.exit(Math.min(code, mochaArgs["posix-exit-codes"] ? 1 : 255));
}
});
});
// terminate children.
process.on("SIGINT", () => {
// XXX: a previous comment said this would abort the runner, but I can't see that it does
// anything with the default runner.
debug("main process caught SIGINT");
proc.kill("SIGINT");
// if running in parallel mode, we will have a proper SIGINT handler, so the below won't
// be needed.
if (!args.parallel || args.jobs < 2) {
// win32 does not support SIGTERM, so use next best thing.
if (os.platform() === "win32") {
proc.kill("SIGKILL");
} else {
// using SIGKILL won't cleanly close the output streams, which can result
// in cut-off text or a befouled terminal.
debug("sending SIGTERM to child process");
proc.kill("SIGTERM");
}
}
});
} else {
debug("running Mocha in-process");
main([], mochaArgs);
}