async scheduleTests()

in packages/jest-core/src/TestScheduler.ts [90:314]


  async scheduleTests(
    tests: Array<Test>,
    watcher: TestWatcher,
  ): Promise<AggregatedResult> {
    const onTestFileStart = this._dispatcher.onTestFileStart.bind(
      this._dispatcher,
    );
    const timings: Array<number> = [];
    const contexts = new Set<Context>();
    tests.forEach(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,
    );

    const runInBand = shouldRunInBand(tests, timings, this._globalConfig);

    const onResult = async (test: Test, testResult: 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,
          stack: new Error(message).stack,
        });
      }

      // Throws when the context is leaked after executing a test.
      if (testResult.leaks) {
        const message =
          `${chalk.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,
          stack: new Error(message).stack,
        });
      }

      addResult(aggregatedResults, testResult);
      await this._dispatcher.onTestFileResult(
        test,
        testResult,
        aggregatedResults,
      );
      return this._bailIfNeeded(contexts, aggregatedResults, watcher);
    };

    const onFailure = async (test: Test, error: SerializableError) => {
      if (watcher.isInterrupted()) {
        return;
      }
      const testResult = buildFailureTestResult(test.path, error);
      testResult.failureMessage = formatExecError(
        testResult.testExecError,
        test.context.config,
        this._globalConfig,
        test.path,
      );
      addResult(aggregatedResults, testResult);
      await this._dispatcher.onTestFileResult(
        test,
        testResult,
        aggregatedResults,
      );
    };

    const updateSnapshotState = async () => {
      const contextsWithSnapshotResolvers = await Promise.all(
        Array.from(contexts).map(
          async context =>
            [context, await buildSnapshotResolver(context.config)] as const,
        ),
      );

      contextsWithSnapshotResolvers.forEach(([context, snapshotResolver]) => {
        const status = cleanupSnapshots(
          context.hasteFS,
          this._globalConfig.updateSnapshot,
          snapshotResolver,
          context.config.testPathIgnorePatterns,
        );

        aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
        aggregatedResults.snapshot.filesRemovedList = (
          aggregatedResults.snapshot.filesRemovedList || []
        ).concat(status.filesRemovedList);
      });
      const updateAll = this._globalConfig.updateSnapshot === 'all';
      aggregatedResults.snapshot.didUpdate = updateAll;
      aggregatedResults.snapshot.failure = !!(
        !updateAll &&
        (aggregatedResults.snapshot.unchecked ||
          aggregatedResults.snapshot.unmatched ||
          aggregatedResults.snapshot.filesRemoved)
      );
    };

    await this._dispatcher.onRunStart(aggregatedResults, {
      estimatedTime,
      showStatus: !runInBand,
    });

    const testRunners: {[key: string]: TestRunner} = Object.create(null);
    const contextsByTestRunner = new WeakMap<TestRunner, Context>();
    await Promise.all(
      Array.from(contexts).map(async context => {
        const {config} = context;
        if (!testRunners[config.runner]) {
          const transformer = await createScriptTransformer(config);
          const Runner: typeof TestRunner =
            await transformer.requireAndTranspileModule(config.runner);
          const runner = new Runner(this._globalConfig, {
            changedFiles: this._context?.changedFiles,
            sourcesRelatedToTestsInChangedFiles:
              this._context?.sourcesRelatedToTestsInChangedFiles,
          });
          testRunners[config.runner] = runner;
          contextsByTestRunner.set(runner, context);
        }
      }),
    );

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

    if (testsByRunner) {
      try {
        for (const runner of Object.keys(testRunners)) {
          const testRunner = testRunners[runner];
          const context = contextsByTestRunner.get(testRunner);

          invariant(context);

          const tests = testsByRunner[runner];

          const testRunnerOptions = {
            serial: runInBand || Boolean(testRunner.isSerial),
          };

          /**
           * Test runners with event emitters are still not supported
           * for third party test runners.
           */
          if (testRunner.__PRIVATE_UNSTABLE_API_supportsEventEmitters__) {
            const unsubscribes = [
              testRunner.on('test-file-start', ([test]) =>
                onTestFileStart(test),
              ),
              testRunner.on('test-file-success', ([test, testResult]) =>
                onResult(test, testResult),
              ),
              testRunner.on('test-file-failure', ([test, error]) =>
                onFailure(test, error),
              ),
              testRunner.on(
                'test-case-result',
                ([testPath, testCaseResult]) => {
                  const test: Test = {context, path: testPath};
                  this._dispatcher.onTestCaseResult(test, testCaseResult);
                },
              ),
            ];

            await testRunner.runTests(
              tests,
              watcher,
              undefined,
              undefined,
              undefined,
              testRunnerOptions,
            );

            unsubscribes.forEach(sub => sub());
          } else {
            await testRunner.runTests(
              tests,
              watcher,
              onTestFileStart,
              onResult,
              onFailure,
              testRunnerOptions,
            );
          }
        }
      } catch (error) {
        if (!watcher.isInterrupted()) {
          throw error;
        }
      }
    }

    await updateSnapshotState();
    aggregatedResults.wasInterrupted = watcher.isInterrupted();
    await 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;
  }