UNPKG

mocha

Version:

simple, flexible, fun test framework

157 lines (136 loc) 4.6 kB
/** * A worker process. Consumes {@link module:reporters/parallel-buffered} reporter. * @module worker * @private */ "use strict"; /** * @typedef {import('../types.d.ts').BufferedEvent} BufferedEvent * @typedef {import('../types.d.ts').MochaOptions} MochaOptions */ const { createInvalidArgumentTypeError, createInvalidArgumentValueError, } = require("../errors"); const workerpool = require("workerpool"); const Mocha = require("../mocha"); const { handleRequires, validateLegacyPlugin } = require("../cli/run-helpers"); const d = require("debug"); const debug = d.debug(`mocha:parallel:worker:${process.pid}`); const isDebugEnabled = d.enabled(`mocha:parallel:worker:${process.pid}`); const { serialize } = require("./serializer"); const { setInterval, clearInterval } = global; let rootHooks; if (workerpool.isMainThread) { throw new Error( "This script is intended to be run as a worker (by the `workerpool` package).", ); } /** * Initializes some stuff on the first call to {@link run}. * * Handles `--require` and `--ui`. Does _not_ handle `--reporter`, * as only the `Buffered` reporter is used. * * **This function only runs once per worker**; it overwrites itself with a no-op * before returning. * * @param {MochaOptions} argv - Command-line options */ let bootstrap = async (argv) => { // globalSetup and globalTeardown do not run in workers const plugins = await handleRequires(argv.require, { ignoredPlugins: ["mochaGlobalSetup", "mochaGlobalTeardown"], }); validateLegacyPlugin(argv, "ui", Mocha.interfaces); rootHooks = plugins.rootHooks; bootstrap = () => {}; debug("bootstrap(): finished with args: %O", argv); }; /** * Runs a single test file in a worker thread. * @param {string} filepath - Filepath of test file * @param {string} [serializedOptions] - **Serialized** options. This string will be eval'd! * @see https://npm.im/serialize-javascript * @returns {Promise<{failures: number, events: BufferedEvent[]}>} - Test * failure count and list of events. */ async function run(filepath, serializedOptions = "{}") { if (!filepath) { throw createInvalidArgumentTypeError( 'Expected a non-empty "filepath" argument', "file", "string", ); } debug("run(): running test file %s", filepath); if (typeof serializedOptions !== "string") { throw createInvalidArgumentTypeError( "run() expects second parameter to be a string which was serialized by the `serialize-javascript` module", "serializedOptions", "string", ); } let argv; try { // eslint-disable-next-line no-eval argv = eval("(" + serializedOptions + ")"); } catch (err) { throw createInvalidArgumentValueError( "run() was unable to deserialize the options", "serializedOptions", serializedOptions, ); } const opts = Object.assign({ ui: "bdd" }, argv, { // if this was true, it would cause infinite recursion. parallel: false, // this doesn't work in parallel mode forbidOnly: true, // it's useful for a Mocha instance to know if it's running in a worker process. isWorker: true, }); await bootstrap(opts); opts.rootHooks = rootHooks; const mocha = new Mocha(opts).addFile(filepath); try { await mocha.loadFilesAsync(); } catch (err) { debug("run(): could not load file %s: %s", filepath, err); throw err; } return new Promise((resolve, reject) => { let debugInterval; /* istanbul ignore next */ if (isDebugEnabled) { debugInterval = setInterval(() => { debug("run(): still running %s...", filepath); }, 5000).unref(); } mocha.run((result) => { // Runner adds these; if we don't remove them, we'll get a leak. process.removeAllListeners("uncaughtException"); process.removeAllListeners("unhandledRejection"); try { const serialized = serialize(result); debug( "run(): completed run with %d test failures; returning to main process", typeof result.failures === "number" ? result.failures : 0, ); resolve(serialized); } catch (err) { // TODO: figure out exactly what the sad path looks like here. // rejection should only happen if an error is "unrecoverable" debug("run(): serialization failed; rejecting: %O", err); reject(err); } finally { clearInterval(debugInterval); } }); }); } // this registers the `run` function. workerpool.worker({ run }); debug("started worker process"); // for testing exports.run = run;