diff --git a/packages/jest-runner/build/index.js b/packages/jest-runner/build/index.js new file mode 100644 index 000000000000..d9b5092bd803 --- /dev/null +++ b/packages/jest-runner/build/index.js @@ -0,0 +1,220 @@ +'use strict'; + +function _exit() { + const data = _interopRequireDefault(require('exit')); + + _exit = function _exit() { + return data; + }; + + return data; +} + +function _throat() { + const data = _interopRequireDefault(require('throat')); + + _throat = function _throat() { + return data; + }; + + return data; +} + +function _jestWorker() { + const data = _interopRequireDefault(require('jest-worker')); + + _jestWorker = function _jestWorker() { + return data; + }; + + return data; +} + +var _runTest = _interopRequireDefault(require('./runTest')); + +var _testWorker = require('./testWorker'); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; +} + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} + +function _asyncToGenerator(fn) { + return function() { + var self = this, + args = arguments; + return new Promise(function(resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); + } + _next(undefined); + }); + }; +} + +const TEST_WORKER_PATH = require.resolve('./testWorker'); + +class TestRunner { + constructor(globalConfig, context) { + this._globalConfig = globalConfig; + this._context = context || {}; + } + + runTests(tests, watcher, onStart, onResult, onFailure, options) { + var _this = this; + + return _asyncToGenerator(function*() { + return yield options.serial + ? _this._createInBandTestRun( + tests, + watcher, + onStart, + onResult, + onFailure + ) + : _this._createParallelTestRun( + tests, + watcher, + onStart, + onResult, + onFailure + ); + })(); + } + + _createInBandTestRun(tests, watcher, onStart, onResult, onFailure) { + var _this2 = this; + + return _asyncToGenerator(function*() { + process.env.JEST_WORKER_ID = '1'; + const mutex = (0, _throat().default)(1); + return tests.reduce( + (promise, test) => + mutex(() => + promise + .then( + /*#__PURE__*/ + _asyncToGenerator(function*() { + if (watcher.isInterrupted()) { + throw new CancelRun(); + } + + yield onStart(test); + return (0, + _runTest.default)(test.path, _this2._globalConfig, test.context.config, test.context.resolver, _this2._context); + }) + ) + .then(result => onResult(test, result)) + .catch(err => onFailure(test, err)) + ), + Promise.resolve() + ); + })(); + } + + _createParallelTestRun(tests, watcher, onStart, onResult, onFailure) { + var _this3 = this; + + return _asyncToGenerator(function*() { + const worker = new (_jestWorker()).default(TEST_WORKER_PATH, { + exposedMethods: ['worker'], + forkOptions: { + stdio: 'pipe' + }, + maxRetries: 3, + numWorkers: _this3._globalConfig.maxWorkers + }); + if (worker.getStdout()) worker.getStdout().pipe(process.stdout); + if (worker.getStderr()) worker.getStderr().pipe(process.stderr); + const mutex = (0, _throat().default)(_this3._globalConfig.maxWorkers); // Send test suites to workers continuously instead of all at once to track + // the start time of individual tests. + + const runTestInWorker = test => + mutex( + /*#__PURE__*/ + _asyncToGenerator(function*() { + if (watcher.isInterrupted()) { + return Promise.reject(); + } + + yield onStart(test); + return worker.worker({ + config: test.context.config, + context: _this3._context, + globalConfig: _this3._globalConfig, + path: test.path, + serializableModuleMap: watcher.isWatchMode() + ? test.context.moduleMap.toJSON() + : null + }); + }) + ); + + const onError = + /*#__PURE__*/ + (function() { + var _ref3 = _asyncToGenerator(function*(err, test) { + yield onFailure(test, err); + + if (err.type === 'ProcessTerminatedError') { + console.error( + 'A worker process has quit unexpectedly! ' + + 'Most likely this is an initialization error.' + ); + (0, _exit().default)(1); + } + }); + + return function onError(_x, _x2) { + return _ref3.apply(this, arguments); + }; + })(); + + const onInterrupt = new Promise((_, reject) => { + watcher.on('change', state => { + if (state.interrupted) { + reject(new CancelRun()); + } + }); + }); + const runAllTests = Promise.all( + tests.map(test => + runTestInWorker(test) + .then(testResult => onResult(test, testResult)) + .catch(error => onError(error, test)) + ) + ); + + const cleanup = () => worker.end(); + + return Promise.race([runAllTests, onInterrupt]).then(cleanup, cleanup); + })(); + } +} + +class CancelRun extends Error { + constructor(message) { + super(message); + this.name = 'CancelRun'; + } +} + +module.exports = TestRunner; diff --git a/packages/jest-runner/build/runTest.js b/packages/jest-runner/build/runTest.js new file mode 100644 index 000000000000..5439bd4fe7d9 --- /dev/null +++ b/packages/jest-runner/build/runTest.js @@ -0,0 +1,455 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports.default = runTest; + +function _gracefulFs() { + const data = _interopRequireDefault(require('graceful-fs')); + + _gracefulFs = function _gracefulFs() { + return data; + }; + + return data; +} + +function _jestUtil() { + const data = require('jest-util'); + + _jestUtil = function _jestUtil() { + return data; + }; + + return data; +} + +function _jestLeakDetector() { + const data = _interopRequireDefault(require('jest-leak-detector')); + + _jestLeakDetector = function _jestLeakDetector() { + return data; + }; + + return data; +} + +function _jestConfig() { + const data = require('jest-config'); + + _jestConfig = function _jestConfig() { + return data; + }; + + return data; +} + +function docblock() { + const data = _interopRequireWildcard(require('jest-docblock')); + + docblock = function docblock() { + return data; + }; + + return data; +} + +function _jestMessageUtil() { + const data = require('jest-message-util'); + + _jestMessageUtil = function _jestMessageUtil() { + return data; + }; + + return data; +} + +function _sourceMapSupport() { + const data = _interopRequireDefault(require('source-map-support')); + + _sourceMapSupport = function _sourceMapSupport() { + return data; + }; + + return data; +} + +function _chalk() { + const data = _interopRequireDefault(require('chalk')); + + _chalk = function _chalk() { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + var desc = + Object.defineProperty && Object.getOwnPropertyDescriptor + ? Object.getOwnPropertyDescriptor(obj, key) + : {}; + if (desc.get || desc.set) { + Object.defineProperty(newObj, key, desc); + } else { + newObj[key] = obj[key]; + } + } + } + } + newObj.default = obj; + return newObj; + } +} + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; +} + +function _objectSpread(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + var ownKeys = Object.keys(source); + if (typeof Object.getOwnPropertySymbols === 'function') { + ownKeys = ownKeys.concat( + Object.getOwnPropertySymbols(source).filter(function(sym) { + return Object.getOwnPropertyDescriptor(source, sym).enumerable; + }) + ); + } + ownKeys.forEach(function(key) { + _defineProperty(target, key, source[key]); + }); + } + return target; +} + +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} + +function _asyncToGenerator(fn) { + return function() { + var self = this, + args = arguments; + return new Promise(function(resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); + } + _next(undefined); + }); + }; +} + +function freezeConsole(testConsole, config) { // @ts-ignore: Correct types when `jest-util` is ESM + testConsole._log = function fakeConsolePush(_type, message) { + const error = new (_jestUtil()).ErrorWithStack( + `${_chalk().default.red( + `${_chalk().default.bold( + 'Cannot log after tests are done.' + )} Did you forget to wait for something async in your test?` + )}\nAttempted to log "${message}".`, + fakeConsolePush + ); + const formattedError = (0, _jestMessageUtil().formatExecError)( + error, + config, + { + noStackTrace: false + }, + undefined, + true + ); + process.stderr.write('\n' + formattedError + '\n'); // TODO: set exit code in Jest 25 + // process.exitCode = 1; + }; +} // Keeping the core of "runTest" as a separate function (as "runTestInternal") +// is key to be able to detect memory leaks. Since all variables are local to +// the function, when "runTestInternal" finishes its execution, they can all be +// freed, UNLESS something else is leaking them (and that's why we can detect +// the leak!). +// +// If we had all the code in a single function, we should manually nullify all +// references to verify if there is a leak, which is not maintainable and error +// prone. That's why "runTestInternal" CANNOT be inlined inside "runTest". + +function runTestInternal(_x, _x2, _x3, _x4, _x5) { + return _runTestInternal.apply(this, arguments); +} + +function _runTestInternal() { + _runTestInternal = _asyncToGenerator(function*( + path, + globalConfig, + config, + resolver, + context + ) { + const testSource = _gracefulFs().default.readFileSync(path, 'utf8'); + + const parsedDocblock = docblock().parse(docblock().extract(testSource)); + const customEnvironment = parsedDocblock['jest-environment']; + let testEnvironment = config.testEnvironment; + + if (customEnvironment) { + testEnvironment = (0, _jestConfig().getTestEnvironment)( + _objectSpread({}, config, { + testEnvironment: customEnvironment + }) + ); + } + + const TestEnvironment = require(testEnvironment); + + const testFramework = + process.env.JEST_CIRCUS === '1' + ? require('jest-circus/runner') // eslint-disable-line import/no-extraneous-dependencies + : require(config.testRunner); + const Runtime = config.moduleLoader + ? require(config.moduleLoader) + : require('jest-runtime'); + let runtime = undefined; + const consoleOut = globalConfig.useStderr ? process.stderr : process.stdout; + + const consoleFormatter = (type, message) => + (0, _jestUtil().getConsoleOutput)( + config.cwd, + !!globalConfig.verbose, // 4 = the console call is buried 4 stack frames deep + _jestUtil().BufferedConsole.write( + [], + type, + message, + 4, + runtime && runtime.getSourceMaps() + ) + ); + + let testConsole; + + if (globalConfig.silent) { + testConsole = new (_jestUtil()).NullConsole( + consoleOut, + process.stderr, + consoleFormatter + ); + } else if (globalConfig.verbose) { + testConsole = new (_jestUtil()).Console( + consoleOut, + process.stderr, + consoleFormatter + ); + } else { + testConsole = new (_jestUtil()).BufferedConsole( + () => runtime && runtime.getSourceMaps() + ); + } + + const environment = new TestEnvironment(config, { + console: testConsole, + testPath: path + }); + const leakDetector = config.detectLeaks + ? new (_jestLeakDetector()).default(environment) + : null; + const cacheFS = { + [path]: testSource + }; + (0, _jestUtil().setGlobal)(environment.global, 'console', testConsole); + runtime = new Runtime(config, environment, resolver, cacheFS, { + changedFiles: context && context.changedFiles, + collectCoverage: globalConfig.collectCoverage, + collectCoverageFrom: globalConfig.collectCoverageFrom, + collectCoverageOnlyFrom: globalConfig.collectCoverageOnlyFrom + }); + const start = Date.now(); + const sourcemapOptions = { + environment: 'node', + handleUncaughtExceptions: false, + retrieveSourceMap: source => { + const sourceMaps = runtime && runtime.getSourceMaps(); + const sourceMapSource = sourceMaps && sourceMaps[source]; + + if (sourceMapSource) { + try { + return { + map: JSON.parse( + _gracefulFs().default.readFileSync(sourceMapSource, 'utf8') + ), + url: source + }; + } catch (e) {} + } + + return null; + } + }; // For tests + + runtime + .requireInternalModule( + require.resolve('source-map-support'), + 'source-map-support' + ) + .install(sourcemapOptions); // For runtime errors + + _sourceMapSupport().default.install(sourcemapOptions); + + if ( + environment.global && + environment.global.process && + environment.global.process.exit + ) { + const realExit = environment.global.process.exit; + + environment.global.process.exit = function exit(...args) { + const error = new (_jestUtil()).ErrorWithStack( + `process.exit called with "${args.join(', ')}"`, + exit + ); + const formattedError = (0, _jestMessageUtil().formatExecError)( + error, + config, + { + noStackTrace: false + }, + undefined, + true + ); + process.stderr.write(formattedError); + return realExit(...args); + }; + } + + try { + yield environment.setup(); + let result; + + try { + result = yield testFramework( + globalConfig, + config, + environment, + runtime, + path + ); + } catch (err) { + // Access stack before uninstalling sourcemaps + err.stack; + throw err; + } + + freezeConsole(testConsole, config); + const testCount = + result.numPassingTests + + result.numFailingTests + + result.numPendingTests + + result.numTodoTests; + result.perfStats = { + end: Date.now(), + start + }; + result.testFilePath = path; + result.coverage = runtime.getAllCoverageInfoCopy(); + result.sourceMaps = runtime.getSourceMapInfo( + new Set(Object.keys(result.coverage || {})) + ); + result.console = testConsole.getBuffer(); + result.skipped = testCount === result.numPendingTests; + result.displayName = config.displayName; + + if (globalConfig.logHeapUsage) { + if (global.gc) { + global.gc(); + } + + result.memoryUsage = process.memoryUsage().heapUsed; + } // Delay the resolution to allow log messages to be output. + + return new Promise(resolve => { + setImmediate(() => + resolve({ + leakDetector, + result + }) + ); + }); + } finally { + yield environment.teardown(); // @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/33351 + + _sourceMapSupport().default.resetRetrieveHandlers(); + } + }); + return _runTestInternal.apply(this, arguments); +} + +function runTest(_x6, _x7, _x8, _x9, _x10) { + return _runTest.apply(this, arguments); +} + +function _runTest() { + _runTest = _asyncToGenerator(function*( + path, + globalConfig, + config, + resolver, + context + ) { + const _ref = yield runTestInternal( + path, + globalConfig, + config, + resolver, + context + ), + leakDetector = _ref.leakDetector, + result = _ref.result; + + if (leakDetector) { + // We wanna allow a tiny but time to pass to allow last-minute cleanup + yield new Promise(resolve => setTimeout(resolve, 100)); // Resolve leak detector, outside the "runTestInternal" closure. + + result.leaks = leakDetector.isLeaking(); + } else { + result.leaks = false; + } + + return result; + }); + return _runTest.apply(this, arguments); +} diff --git a/packages/jest-runner/build/testWorker.js b/packages/jest-runner/build/testWorker.js new file mode 100644 index 000000000000..ba8bc6c58596 --- /dev/null +++ b/packages/jest-runner/build/testWorker.js @@ -0,0 +1,167 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports.worker = worker; + +function _jestHasteMap() { + const data = _interopRequireDefault(require('jest-haste-map')); + + _jestHasteMap = function _jestHasteMap() { + return data; + }; + + return data; +} + +function _exit() { + const data = _interopRequireDefault(require('exit')); + + _exit = function _exit() { + return data; + }; + + return data; +} + +function _jestMessageUtil() { + const data = require('jest-message-util'); + + _jestMessageUtil = function _jestMessageUtil() { + return data; + }; + + return data; +} + +function _jestRuntime() { + const data = _interopRequireDefault(require('jest-runtime')); + + _jestRuntime = function _jestRuntime() { + return data; + }; + + return data; +} + +var _runTest = _interopRequireDefault(require('./runTest')); + +function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; +} + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} + +function _asyncToGenerator(fn) { + return function() { + var self = this, + args = arguments; + return new Promise(function(resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); + } + _next(undefined); + }); + }; +} + +// Make sure uncaught errors are logged before we exit. +process.on('uncaughtException', err => { + console.error(err.stack); + (0, _exit().default)(1); +}); + +const formatError = error => { + if (typeof error === 'string') { + const _separateMessageFromS = (0, + _jestMessageUtil().separateMessageFromStack)(error), + message = _separateMessageFromS.message, + stack = _separateMessageFromS.stack; + + return { + message, + stack, + type: 'Error' + }; + } + + return { + code: error.code || undefined, + message: error.message, + stack: error.stack, + type: 'Error' + }; +}; + +const resolvers = Object.create(null); + +const getResolver = (config, moduleMap) => { + // In watch mode, the raw module map with all haste modules is passed from + // the test runner to the watch command. This is because jest-haste-map's + // watch mode does not persist the haste map on disk after every file change. + // To make this fast and consistent, we pass it from the TestRunner. + if (moduleMap) { + return _jestRuntime().default.createResolver(config, moduleMap); + } else { + const name = config.name; + + if (!resolvers[name]) { + resolvers[name] = _jestRuntime().default.createResolver( + config, + _jestRuntime() + .default.createHasteMap(config) + .readModuleMap() + ); + } + + return resolvers[name]; + } +}; + +function worker(_x) { + return _worker.apply(this, arguments); +} + +function _worker() { + _worker = _asyncToGenerator(function*({ + config, + globalConfig, + path, + serializableModuleMap, + context + }) { + try { + const moduleMap = serializableModuleMap + ? _jestHasteMap().default.ModuleMap.fromJSON(serializableModuleMap) + : null; + return yield (0, _runTest.default)( + path, + globalConfig, + config, + getResolver(config, moduleMap), + context + ); + } catch (error) { + throw formatError(error); + } + }); + return _worker.apply(this, arguments); +} diff --git a/packages/jest-runner/build/types.js b/packages/jest-runner/build/types.js new file mode 100644 index 000000000000..ad9a93a7c160 --- /dev/null +++ b/packages/jest-runner/build/types.js @@ -0,0 +1 @@ +'use strict';