'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

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'
      );
    }
  };
})();

var _chalk;

function _load_chalk() {
  return (_chalk = _interopRequireDefault(require('chalk')));
}

var _jestMessageUtil;

function _load_jestMessageUtil() {
  return (_jestMessageUtil = require('jest-message-util'));
}

var _testResultHelpers;

function _load_testResultHelpers() {
  return (_testResultHelpers = require('./testResultHelpers'));
}

var _coverage_reporter;

function _load_coverage_reporter() {
  return (_coverage_reporter = _interopRequireDefault(
    require('./reporters/coverage_reporter')
  ));
}

var _default_reporter;

function _load_default_reporter() {
  return (_default_reporter = _interopRequireDefault(
    require('./reporters/default_reporter')
  ));
}

var _exit;

function _load_exit() {
  return (_exit = _interopRequireDefault(require('exit')));
}

var _notify_reporter;

function _load_notify_reporter() {
  return (_notify_reporter = _interopRequireDefault(
    require('./reporters/notify_reporter')
  ));
}

var _ReporterDispatcher;

function _load_ReporterDispatcher() {
  return (_ReporterDispatcher = _interopRequireDefault(
    require('./ReporterDispatcher')
  ));
}

var _jestSnapshot;

function _load_jestSnapshot() {
  return (_jestSnapshot = _interopRequireDefault(require('jest-snapshot')));
}

var _summary_reporter;

function _load_summary_reporter() {
  return (_summary_reporter = _interopRequireDefault(
    require('./reporters/summary_reporter')
  ));
}

var _jestRunner;

function _load_jestRunner() {
  return (_jestRunner = _interopRequireDefault(require('jest-runner')));
}

var _TestWatcher;

function _load_TestWatcher() {
  return (_TestWatcher = _interopRequireDefault(require('./TestWatcher')));
}

var _verbose_reporter;

function _load_verbose_reporter() {
  return (_verbose_reporter = _interopRequireDefault(
    require('./reporters/verbose_reporter')
  ));
}

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : {default: obj};
}

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');
    });
  };
}
/**
 * Copyright (c) 2014-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.
 *
 *
 */

const SLOW_TEST_TIME = 1000;

// The default jest-runner is required because it is the default test runner
// and required implicitly through the `runner` ProjectConfig option.
(_jestRunner || _load_jestRunner()).default;

class TestScheduler {
  constructor(globalConfig, options, context) {
    this._dispatcher = new (
      _ReporterDispatcher || _load_ReporterDispatcher()
    ).default();
    this._globalConfig = globalConfig;
    this._options = options;
    this._context = context;
    this._setupReporters();
  }

  addReporter(reporter) {
    this._dispatcher.register(reporter);
  }

  removeReporter(ReporterClass) {
    this._dispatcher.unregister(ReporterClass);
  }

  scheduleTests(tests, watcher) {
    var _this = this;

    return _asyncToGenerator(function*() {
      const onStart = _this._dispatcher.onTestStart.bind(_this._dispatcher);
      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._globalConfig.maxWorkers) / 1000
      );

      // Run in band if we only have one test or one worker available, unless we
      // are using the watch mode, in which case the TTY has to be responsive and
      // we cannot schedule anything in the main thread. Same logic applies to
      // watchAll.
      //
      // 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 areFastTests = timings.every(function(timing) {
        return timing < SLOW_TEST_TIME;
      });

      const runInBandWatch = tests.length <= 1 && areFastTests;
      const runInBandNonWatch =
        _this._globalConfig.maxWorkers <= 1 ||
        tests.length <= 1 ||
        (tests.length <= 20 && timings.length > 0 && areFastTests);

      const runInBand =
        _this._globalConfig.watch || _this._globalConfig.watchAll
          ? runInBandWatch
          : runInBandNonWatch;

      const onResult = (() => {
        var _ref = _asyncToGenerator(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.';

            return onFailure(test, {
              message: message,
              stack: new Error(message).stack
            });
          }

          // Throws when the context is leaked after executing a test.
          if (testResult.leaks) {
            const message =
              (_chalk || _load_chalk()).default.red.bold(
                'EXPERIMENTAL FEATURE!\n'
              ) +
              'Your test suite is leaking memory. Please ensure all references are cleaned.\n' +
              '\n' +
              'There is a number of things that can leak memory:\n' +
              '  - Async operations that have not finished (e.g. fs.readFile).\n' +
              '  - Timers not properly mocked (e.g. setInterval, setTimeout).\n' +
              '  - Keeping references to the global scope.';

            return onFailure(test, {
              message: message,
              stack: new Error(message).stack
            });
          }

          (0,
          (_testResultHelpers || _load_testResultHelpers())
            .addResult)(aggregatedResults, testResult);
          yield _this._dispatcher.onTestResult(
            test,
            testResult,
            aggregatedResults
          );
          return _this._bailIfNeeded(contexts, aggregatedResults, watcher);
        });

        return function onResult(_x, _x2) {
          return _ref.apply(this, arguments);
        };
      })();

      const onFailure = (() => {
        var _ref2 = _asyncToGenerator(function*(test, error) {
          if (watcher.isInterrupted()) {
            return;
          }
          const testResult = (0,
          (_testResultHelpers || _load_testResultHelpers())
            .buildFailureTestResult)(test.path, error);
          testResult.failureMessage = (0,
          (_jestMessageUtil || _load_jestMessageUtil()).formatExecError)(
            testResult.testExecError,
            test.context.config,
            _this._globalConfig,
            test.path
          );
          (0,
          (_testResultHelpers || _load_testResultHelpers())
            .addResult)(aggregatedResults, testResult);
          yield _this._dispatcher.onTestResult(
            test,
            testResult,
            aggregatedResults
          );
        });

        return function onFailure(_x3, _x4) {
          return _ref2.apply(this, arguments);
        };
      })();

      const updateSnapshotState = function() {
        contexts.forEach(function(context) {
          const status = (
            _jestSnapshot || _load_jestSnapshot()
          ).default.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)
        );
      };

      yield _this._dispatcher.onRunStart(aggregatedResults, {
        estimatedTime: estimatedTime,
        showStatus: !runInBand
      });

      const testRunners = Object.create(null);
      contexts.forEach(function(_ref3) {
        let config = _ref3.config;

        if (!testRunners[config.runner]) {
          // $FlowFixMe
          testRunners[config.runner] = new (require(config.runner))(
            _this._globalConfig
          );
        }
      });

      const testsByRunner = _this._partitionTests(testRunners, tests);

      if (testsByRunner) {
        try {
          for (const runner of Object.keys(testRunners)) {
            yield testRunners[runner].runTests(
              testsByRunner[runner],
              watcher,
              onStart,
              onResult,
              onFailure,
              {
                serial: runInBand || Boolean(testRunners[runner].isSerial)
              }
            );
          }
        } 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;
    })();
  }

  _partitionTests(testRunners, tests) {
    if (Object.keys(testRunners).length > 1) {
      return tests.reduce((testRuns, test) => {
        const runner = test.context.config.runner;
        if (!testRuns[runner]) {
          testRuns[runner] = [];
        }
        testRuns[runner].push(test);
        return testRuns;
      }, Object.create(null));
    } else if (tests.length > 0 && tests[0] != null) {
      // If there is only one runner, don't partition the tests.
      return Object.assign(Object.create(null), {
        [tests[0].context.config.runner]: tests
      });
    } else {
      return null;
    }
  }

  _shouldAddDefaultReporters(reporters) {
    return (
      !reporters ||
      !!reporters.find(
        reporter => this._getReporterProps(reporter).path === '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(collectCoverage);
    }

    if (!isDefault && collectCoverage) {
      this.addReporter(
        new (_coverage_reporter || _load_coverage_reporter()).default(
          this._globalConfig
        )
      );
    }

    if (notify) {
      this.addReporter(
        new (_notify_reporter || _load_notify_reporter()).default(
          this._globalConfig,
          this._options.startRun,
          this._context
        )
      );
    }

    if (reporters && Array.isArray(reporters)) {
      this._addCustomReporters(reporters);
    }
  }

  _setupDefaultReporters(collectCoverage) {
    this.addReporter(
      this._globalConfig.verbose
        ? new (_verbose_reporter || _load_verbose_reporter()).default(
            this._globalConfig
          )
        : new (_default_reporter || _load_default_reporter()).default(
            this._globalConfig
          )
    );

    if (collectCoverage) {
      this.addReporter(
        new (_coverage_reporter || _load_coverage_reporter()).default(
          this._globalConfig
        )
      );
    }

    this.addReporter(
      new (_summary_reporter || _load_summary_reporter()).default(
        this._globalConfig
      )
    );
  }

  _addCustomReporters(reporters) {
    reporters.forEach((reporter, index) => {
      var _getReporterProps = this._getReporterProps(reporter);

      const options = _getReporterProps.options,
        path = _getReporterProps.path;

      if (path === 'default') return;

      try {
        // $FlowFixMe
        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 {options: this._options, path: reporter};
    } else if (Array.isArray(reporter)) {
      var _reporter = _slicedToArray(reporter, 2);

      const path = _reporter[0],
        options = _reporter[1];

      return {options: options, path: path};
    }

    throw new Error('Reporter 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 failureExit = () => (0, (_exit || _load_exit()).default)(1);

        return this._dispatcher
          .onRunComplete(contexts, aggregatedResults)
          .then(failureExit)
          .catch(failureExit);
      }
    }
    return Promise.resolve();
  }
}

exports.default = TestScheduler;
const createAggregatedResults = numTotalTestSuites => {
  const result = (0,
  (_testResultHelpers || _load_testResultHelpers())
    .makeEmptyAggregatedTestResult)();
  result.numTotalTestSuites = numTotalTestSuites;
  result.startTime = Date.now();
  result.success = false;
  return result;
};

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);
};
