/** * Copyright (c) 2017-present, Facebook, Inc. All rights reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * */ 'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _mergeStream; function _load_mergeStream() {return _mergeStream = _interopRequireDefault(require('merge-stream'));}var _os; function _load_os() {return _os = _interopRequireDefault(require('os'));}var _path; function _load_path() {return _path = _interopRequireDefault(require('path'));}var _types; function _load_types() {return _types = require('./types');}var _worker; function _load_worker() {return _worker = _interopRequireDefault(require('./worker'));}function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };} /* istanbul ignore next */ const emptyMethod = () => {}; /** * The Jest farm (publicly called "Worker") is a class that allows you to queue * methods across multiple child processes, in order to parallelize work. This * is done by providing an absolute path to a module that will be loaded on each * of the child processes, and bridged to the main process. * * Bridged methods are specified by using the "exposedMethods" property of the * options "object". This is an array of strings, where each of them corresponds * to the exported name in the loaded module. * * You can also control the amount of workers by using the "numWorkers" property * of the "options" object, and the settings passed to fork the process through * the "forkOptions" property. The amount of workers defaults to the amount of * CPUS minus one. * * Queueing calls can be done in two ways: * - Standard method: calls will be redirected to the first available worker, * so they will get executed as soon as they can. * * - Sticky method: if a "computeWorkerKey" method is provided within the * config, the resulting string of this method will be used as a key. * Everytime this key is returned, it is guaranteed that your job will be * processed by the same worker. This is specially useful if your workers are * caching results. */exports.default = class { constructor(workerPath) {let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const numWorkers = options.numWorkers || (_os || _load_os()).default.cpus().length - 1; const workers = new Array(numWorkers); const stdout = (0, (_mergeStream || _load_mergeStream()).default)(); const stderr = (0, (_mergeStream || _load_mergeStream()).default)(); if (!(_path || _load_path()).default.isAbsolute(workerPath)) { workerPath = require.resolve(workerPath); } // Build the options once for all workers to avoid allocating extra objects. const workerOptions = { forkOptions: options.forkOptions || {}, maxRetries: options.maxRetries || 3, workerPath }; for (let i = 0; i < numWorkers; i++) { const worker = new (_worker || _load_worker()).default(workerOptions); const workerStdout = worker.getStdout(); const workerStderr = worker.getStderr(); if (workerStdout) { stdout.add(workerStdout); } if (workerStderr) { stderr.add(workerStderr); } workers[i] = worker; } let exposedMethods = options.exposedMethods; // If no methods list is given, try getting it by auto-requiring the module. if (!exposedMethods) { // $FlowFixMe: This has to be a dynamic require. const child = require(workerPath); exposedMethods = Object.keys(child).filter( name => typeof child[name] === 'function'); if (typeof child === 'function') { exposedMethods.push('default'); } } exposedMethods.forEach(name => { if (name.startsWith('_')) { return; } if (this.constructor.prototype.hasOwnProperty(name)) { throw new TypeError('Cannot define a method called ' + name); } // $FlowFixMe: dynamic extension of the class instance is expected. this[name] = this._makeCall.bind(this, name); }); this._stdout = stdout; this._stderr = stderr; this._ending = false; this._cacheKeys = Object.create(null); this._options = options; this._workers = workers; this._offset = 0; } getStdout() { return this._stdout; } getStderr() { return this._stderr; } end() { if (this._ending) { throw new Error('Farm is ended, no more calls can be done to it'); } const workers = this._workers; // We do not cache the request object here. If so, it would only be only // processed by one of the workers, and we want them all to close. for (let i = 0; i < workers.length; i++) { workers[i].send([(_types || _load_types()).CHILD_MESSAGE_END, false], emptyMethod); } this._ending = true; } // eslint-disable-next-line no-unclear-flowtypes _makeCall(method) {for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {args[_key - 1] = arguments[_key];} if (this._ending) { throw new Error('Farm is ended, no more calls can be done to it'); } return new Promise((resolve, reject) => {const computeWorkerKey = this._options.computeWorkerKey; const workers = this._workers; const length = workers.length; const cacheKeys = this._cacheKeys; const request = [(_types || _load_types()).CHILD_MESSAGE_CALL, false, method, args]; let worker = null; let hash = null; if (computeWorkerKey) { hash = computeWorkerKey.apply(this, [method].concat(args)); worker = hash == null ? null : cacheKeys[hash]; } // Do not use a fat arrow since we need the "this" value, which points to // the worker that executed the call. function callback(error, result) { if (hash != null) { cacheKeys[hash] = this; } if (error) { reject(error); } else { resolve(result); } } // If a worker is pre-selected, use it... if (worker) { worker.send(request, callback); return; } // ... otherwise use all workers, so the first one available will pick it. for (let i = 0; i < length; i++) { workers[(i + this._offset) % length].send(request, callback); } this._offset++; }); }};