UNPKG

data-provider-temporary

Version:

Library that helps with server-to-client synchronization of data

514 lines (400 loc) 15.4 kB
'use strict';var _slicedToArray = function () {function sliceIterator(arr, i) {var _arr = [];var _n = true;var _d = false;var _e = undefined;try {for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {_arr.push(_s.value);if (i && _arr.length === i) break;}} catch (err) {_d = true;_e = err;} finally {try {if (!_n && _i["return"]) _i["return"]();} finally {if (_d) throw _e;}}return _arr;}return function (arr, i) {if (Array.isArray(arr)) {return arr;} else if (Symbol.iterator in Object(arr)) {return sliceIterator(arr, i);} else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();function _asyncToGenerator(fn) {return function () {var gen = fn.apply(this, arguments);return new Promise(function (resolve, reject) {function step(key, arg) {try {var info = gen[key](arg);var value = info.value;} catch (error) {reject(error);return;}if (info.done) {resolve(value);} else {return Promise.resolve(value).then(function (value) {step("next", value);}, function (err) {step("throw", err);});}}return step("next");});};}var _require = require('jest-message-util');const formatExecError = _require.formatExecError; /** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * */const snapshot = require('jest-snapshot');const pify = require('pify');const throat = require('throat');const workerFarm = require('worker-farm');const DefaultReporter = require('./reporters/DefaultReporter');const NotifyReporter = require('./reporters/NotifyReporter');const SummaryReporter = require('./reporters/SummaryReporter'); const VerboseReporter = require('./reporters/VerboseReporter'); const runTest = require('./runTest'); const TestWatcher = require('./TestWatcher'); const ReporterDispatcher = require('./ReporterDispatcher'); const SLOW_TEST_TIME = 3000; class CancelRun extends Error { constructor(message) { super(message); this.name = 'CancelRun'; }} const TEST_WORKER_PATH = require.resolve('./TestWorker'); class TestRunner { constructor(globalConfig, options) { this._globalConfig = globalConfig; this._dispatcher = new ReporterDispatcher(); this._options = options; this._setupReporters(); } addReporter(reporter) { this._dispatcher.register(reporter); } removeReporter(ReporterClass) { this._dispatcher.unregister(ReporterClass); } runTests(tests, watcher) {var _this = this;return _asyncToGenerator(function* () { const timings = []; const contexts = new Set(); tests.forEach(function (test) { contexts.add(test.context); if (test.duration) { timings.push(test.duration); } }); const aggregatedResults = createAggregatedResults(tests.length); const estimatedTime = Math.ceil( getEstimatedTime(timings, _this._options.maxWorkers) / 1000); // Run in band if we only have one test or one worker available. // If we are confident from previous runs that the tests will finish quickly // we also run in band to reduce the overhead of spawning workers. const runInBand = _this._options.maxWorkers <= 1 || tests.length <= 1 || tests.length <= 20 && timings.length > 0 && timings.every(function (timing) {return timing < SLOW_TEST_TIME;}); const onResult = function (test, testResult) { if (watcher.isInterrupted()) { return Promise.resolve(); } if (testResult.testResults.length === 0) { const message = 'Your test suite must contain at least one test.'; onFailure(test, { message, stack: new Error(message).stack }); return Promise.resolve(); } addResult(aggregatedResults, testResult); _this._dispatcher.onTestResult(test, testResult, aggregatedResults); return _this._bailIfNeeded(contexts, aggregatedResults, watcher); }; const onFailure = function (test, error) { if (watcher.isInterrupted()) { return; } const testResult = buildFailureTestResult(test.path, error); testResult.failureMessage = formatExecError( testResult, test.context.config, _this._globalConfig, test.path); addResult(aggregatedResults, testResult); _this._dispatcher.onTestResult(test, testResult, aggregatedResults); }; const updateSnapshotState = function () { contexts.forEach(function (context) { const status = snapshot.cleanup( context.hasteFS, _this._globalConfig.updateSnapshot); aggregatedResults.snapshot.filesRemoved += status.filesRemoved; }); const updateAll = _this._globalConfig.updateSnapshot === 'all'; aggregatedResults.snapshot.didUpdate = updateAll; aggregatedResults.snapshot.failure = !!(!updateAll && ( aggregatedResults.snapshot.unchecked || aggregatedResults.snapshot.unmatched || aggregatedResults.snapshot.filesRemoved)); }; _this._dispatcher.onRunStart(aggregatedResults, { estimatedTime, showStatus: !runInBand }); try { yield runInBand ? _this._createInBandTestRun(tests, watcher, onResult, onFailure) : _this._createParallelTestRun(tests, watcher, onResult, onFailure); } catch (error) { if (!watcher.isInterrupted()) { throw error; } } updateSnapshotState(); aggregatedResults.wasInterrupted = watcher.isInterrupted(); yield _this._dispatcher.onRunComplete(contexts, aggregatedResults); const anyTestFailures = !(aggregatedResults.numFailedTests === 0 && aggregatedResults.numRuntimeErrorTestSuites === 0); const anyReporterErrors = _this._dispatcher.hasErrors(); aggregatedResults.success = !(anyTestFailures || aggregatedResults.snapshot.failure || anyReporterErrors); return aggregatedResults;})(); } _createInBandTestRun( tests, watcher, onResult, onFailure) { const mutex = throat(1); return tests.reduce( (promise, test) => mutex(() => promise. then(() => { if (watcher.isInterrupted()) { throw new CancelRun(); } this._dispatcher.onTestStart(test); return runTest( test.path, this._globalConfig, test.context.config, test.context.resolver); }). then(result => onResult(test, result)). catch(err => onFailure(test, err))), Promise.resolve()); } _createParallelTestRun( tests, watcher, onResult, onFailure) { const farm = workerFarm( { autoStart: true, maxConcurrentCallsPerWorker: 1, maxConcurrentWorkers: this._options.maxWorkers, maxRetries: 2 }, TEST_WORKER_PATH); const mutex = throat(this._options.maxWorkers); const worker = pify(farm); // Send test suites to workers continuously instead of all at once to track // the start time of individual tests. const runTestInWorker = test => mutex(() => { if (watcher.isInterrupted()) { return Promise.reject(); } this._dispatcher.onTestStart(test); return worker({ config: test.context.config, globalConfig: this._globalConfig, path: test.path, rawModuleMap: watcher.isWatchMode() ? test.context.moduleMap.getRawModuleMap() : null }); }); const onError = (err, test) => { onFailure(test, err); if (err.type === 'ProcessTerminatedError') { console.error( 'A worker process has quit unexpectedly! ' + 'Most likely this is an initialization error.'); process.exit(1); } }; 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 = () => workerFarm.end(farm); return Promise.race([runAllTests, onInterrupt]).then(cleanup, cleanup); } _shouldAddDefaultReporters(reporters) { return ( !reporters || !!reporters.find(reporterConfig => reporterConfig[0] === 'default')); } _setupReporters() {var _globalConfig = this._globalConfig;const collectCoverage = _globalConfig.collectCoverage,notify = _globalConfig.notify,reporters = _globalConfig.reporters; const isDefault = this._shouldAddDefaultReporters(reporters); if (isDefault) { this._setupDefaultReporters(); } if (reporters && Array.isArray(reporters)) { this._addCustomReporters(reporters); } if (collectCoverage) { // coverage reporter dependency graph is pretty big and we don't // want to require it if we're not in the `--coverage` mode const CoverageReporter = require('./reporters/CoverageReporter'); this.addReporter( new CoverageReporter(this._globalConfig, { maxWorkers: this._options.maxWorkers })); } if (notify) { this.addReporter(new NotifyReporter(this._options.startRun)); } } _setupDefaultReporters() { this.addReporter( this._globalConfig.verbose ? new VerboseReporter(this._globalConfig) : new DefaultReporter(this._globalConfig)); this.addReporter( new SummaryReporter(this._globalConfig, { pattern: this._options.pattern, testNamePattern: this._options.testNamePattern, testPathPattern: this._options.testPathPattern })); } _addCustomReporters(reporters) { const customReporters = reporters.filter( reporterConfig => reporterConfig[0] !== 'default'); customReporters.forEach((reporter, index) => {var _getReporterProps = this._getReporterProps(reporter);const options = _getReporterProps.options,path = _getReporterProps.path; try { const Reporter = require(path); this.addReporter(new Reporter(this._globalConfig, options)); } catch (error) { throw new Error( 'An error occurred while adding the reporter at path "' + path + '".' + error.message); } }); } /** * Get properties of a reporter in an object * to make dealing with them less painful. */ _getReporterProps( reporter) { if (typeof reporter === 'string') { return { path: reporter }; } else if (Array.isArray(reporter)) {var _reporter = _slicedToArray( reporter, 2);const path = _reporter[0],options = _reporter[1]; return { options, path }; } throw new Error('Reproter should be either a string or an array'); } _bailIfNeeded( contexts, aggregatedResults, watcher) { if (this._globalConfig.bail && aggregatedResults.numFailedTests !== 0) { if (watcher.isWatchMode()) { watcher.setState({ interrupted: true }); } else { const exit = () => process.exit(1); return this._dispatcher. onRunComplete(contexts, aggregatedResults). then(exit). catch(exit); } } return Promise.resolve(); }} const createAggregatedResults = numTotalTestSuites => { return { numFailedTestSuites: 0, numFailedTests: 0, numPassedTestSuites: 0, numPassedTests: 0, numPendingTestSuites: 0, numPendingTests: 0, numRuntimeErrorTestSuites: 0, numTotalTestSuites, numTotalTests: 0, snapshot: { added: 0, didUpdate: false, // is set only after the full run failure: false, filesAdded: 0, // combines individual test results + results after full run filesRemoved: 0, filesUnmatched: 0, filesUpdated: 0, matched: 0, total: 0, unchecked: 0, unmatched: 0, updated: 0 }, startTime: Date.now(), success: false, testResults: [], wasInterrupted: false }; }; const addResult = ( aggregatedResults, testResult) => { aggregatedResults.testResults.push(testResult); aggregatedResults.numTotalTests += testResult.numPassingTests + testResult.numFailingTests + testResult.numPendingTests; aggregatedResults.numFailedTests += testResult.numFailingTests; aggregatedResults.numPassedTests += testResult.numPassingTests; aggregatedResults.numPendingTests += testResult.numPendingTests; if (testResult.testExecError) { aggregatedResults.numRuntimeErrorTestSuites++; } if (testResult.skipped) { aggregatedResults.numPendingTestSuites++; } else if (testResult.numFailingTests > 0 || testResult.testExecError) { aggregatedResults.numFailedTestSuites++; } else { aggregatedResults.numPassedTestSuites++; } // Snapshot data if (testResult.snapshot.added) { aggregatedResults.snapshot.filesAdded++; } if (testResult.snapshot.fileDeleted) { aggregatedResults.snapshot.filesRemoved++; } if (testResult.snapshot.unmatched) { aggregatedResults.snapshot.filesUnmatched++; } if (testResult.snapshot.updated) { aggregatedResults.snapshot.filesUpdated++; } aggregatedResults.snapshot.added += testResult.snapshot.added; aggregatedResults.snapshot.matched += testResult.snapshot.matched; aggregatedResults.snapshot.unchecked += testResult.snapshot.unchecked; aggregatedResults.snapshot.unmatched += testResult.snapshot.unmatched; aggregatedResults.snapshot.updated += testResult.snapshot.updated; aggregatedResults.snapshot.total += testResult.snapshot.added + testResult.snapshot.matched + testResult.snapshot.unmatched + testResult.snapshot.updated; }; const buildFailureTestResult = ( testPath, err) => { return { console: null, failureMessage: null, numFailingTests: 0, numPassingTests: 0, numPendingTests: 0, perfStats: { end: 0, start: 0 }, skipped: false, snapshot: { added: 0, fileDeleted: false, matched: 0, unchecked: 0, unmatched: 0, updated: 0 }, sourceMaps: {}, testExecError: err, testFilePath: testPath, testResults: [] }; }; const getEstimatedTime = (timings, workers) => { if (!timings.length) { return 0; } const max = Math.max.apply(null, timings); return timings.length <= workers ? max : Math.max(timings.reduce((sum, time) => sum + time) / workers, max); }; module.exports = TestRunner;