| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- /**
- * 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;
|