diff --git a/packages/jest-changed-files/src/git.js b/packages/jest-changed-files/src/git.js index 97f3335b7643..f725bf5881cd 100644 --- a/packages/jest-changed-files/src/git.js +++ b/packages/jest-changed-files/src/git.js @@ -9,7 +9,7 @@ */ import type {Path} from 'types/Config'; -import type {Options, SCMAdapter} from '../types'; +import type {Options, SCMAdapter} from 'types/ChangedFiles'; import path from 'path'; import childProcess from 'child_process'; diff --git a/packages/jest-changed-files/src/hg.js b/packages/jest-changed-files/src/hg.js index a6d0eed96e73..767e03eb3053 100644 --- a/packages/jest-changed-files/src/hg.js +++ b/packages/jest-changed-files/src/hg.js @@ -9,7 +9,7 @@ */ import type {Path} from 'types/Config'; -import type {Options, SCMAdapter} from '../types'; +import type {Options, SCMAdapter} from 'types/ChangedFiles'; import path from 'path'; import childProcess from 'child_process'; diff --git a/packages/jest-changed-files/src/index.js b/packages/jest-changed-files/src/index.js index a808f25c62a3..973887636538 100644 --- a/packages/jest-changed-files/src/index.js +++ b/packages/jest-changed-files/src/index.js @@ -9,7 +9,7 @@ */ import type {Path} from 'types/Config'; -import type {Options, Repos} from '../types'; +import type {ChangedFilesPromise, Options, Repos} from 'types/ChangedFiles'; import git from './git'; import hg from './hg'; @@ -25,8 +25,9 @@ const findHgRoot = dir => mutex(() => hg.getRoot(dir)); const getChangedFilesForRoots = async ( roots: Array, options: Options, -): Promise<{changedFiles: Set, repos: Repos}> => { +): ChangedFilesPromise => { const repos = await findRepos(roots); + const gitPromises = Array.from(repos.git).map(repo => git.findChangedFiles(repo, options), ); diff --git a/packages/jest-cli/src/__tests__/watch.test.js b/packages/jest-cli/src/__tests__/watch.test.js index a544e6fb24b2..a34fe7b8ab45 100644 --- a/packages/jest-cli/src/__tests__/watch.test.js +++ b/packages/jest-cli/src/__tests__/watch.test.js @@ -65,6 +65,7 @@ describe('Watch mode flows', () => { pipe, new TestWatcher({isWatchMode: true}), expect.any(Function), + undefined, expect.any(Function), ); }); @@ -82,6 +83,7 @@ describe('Watch mode flows', () => { pipe, new TestWatcher({isWatchMode: true}), expect.any(Function), + undefined, expect.any(Function), ); }); @@ -95,6 +97,7 @@ describe('Watch mode flows', () => { pipe, new TestWatcher({isWatchMode: true}), expect.any(Function), + undefined, expect.any(Function), ); expect(pipe.write.mock.calls.reverse()[0]).toMatchSnapshot(); diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index 71b0d6571d33..c27b9fa7a149 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -23,6 +23,7 @@ import {version as VERSION} from '../../package.json'; import args from './args'; import chalk from 'chalk'; import createContext from '../lib/create_context'; +import getChangedFilesPromise from '../get_changed_files_promise'; import getJest from './get_jest'; import getMaxWorkers from '../lib/get_max_workers'; import handleDeprecationWarnings from '../lib/handle_deprecation_warnings'; @@ -251,6 +252,9 @@ const _run = async ( argv, onComplete, ) => { + // Queries to hg/git can take a while, so we need to start the process + // as soon as possible, so by the time we need the result it's already there. + const changedFilesPromise = getChangedFilesPromise(argv, configs); const {contexts, hasteMapInstances} = await _buildContextsAndHasteMaps( configs, globalConfig, @@ -267,8 +271,16 @@ const _run = async ( globalConfig, outputStream, hasteMapInstances, + changedFilesPromise, ) - : _runWithoutWatch(globalConfig, contexts, argv, outputStream, onComplete); + : _runWithoutWatch( + globalConfig, + contexts, + argv, + outputStream, + onComplete, + changedFilesPromise, + ); }; const _runWatch = async ( @@ -279,6 +291,7 @@ const _runWatch = async ( globalConfig, outputStream, hasteMapInstances, + changedFilesPromise, ) => { if (hasDeprecationWarnings) { try { @@ -304,6 +317,7 @@ const _runWithoutWatch = async ( argv, outputStream, onComplete, + changedFilesPromise, ) => { const startRun = () => { if (!argv.listTests) { @@ -316,6 +330,7 @@ const _runWithoutWatch = async ( outputStream, new TestWatcher({isWatchMode: false}), startRun, + changedFilesPromise, onComplete, ); }; diff --git a/packages/jest-cli/src/get_changed_files_promise.js b/packages/jest-cli/src/get_changed_files_promise.js new file mode 100644 index 000000000000..8f9c0857933e --- /dev/null +++ b/packages/jest-cli/src/get_changed_files_promise.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014, 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. + * + * @flow + */ + +import type {Argv} from 'types/Argv'; +import type {ProjectConfig} from 'types/Config'; +import type {ChangedFilesPromise} from 'types/ChangedFiles'; +import {getChangedFilesForRoots} from 'jest-changed-files'; + +module.exports = ( + argv: Argv, + configs: Array, +): ?ChangedFilesPromise => { + if (argv.onlyChanged) { + const allRootsForAllProjects = configs.reduce( + (roots, config) => roots.concat(config.roots || []), + [], + ); + return getChangedFilesForRoots(allRootsForAllProjects, { + lastCommit: argv.lastCommit, + }); + } + + return undefined; +}; diff --git a/packages/jest-cli/src/lib/get_test_path_pattern.js b/packages/jest-cli/src/lib/get_test_path_pattern.js index c6cf3505f960..c4071bddfe63 100644 --- a/packages/jest-cli/src/lib/get_test_path_pattern.js +++ b/packages/jest-cli/src/lib/get_test_path_pattern.js @@ -36,7 +36,6 @@ module.exports = (argv: Argv): TestSelectionConfig => { if (argv.onlyChanged) { return { input: '', - lastCommit: argv.lastCommit, onlyChanged: true, watch: argv.watch, }; diff --git a/packages/jest-cli/src/run_jest.js b/packages/jest-cli/src/run_jest.js index 0abe8b54876c..89ca21105cf2 100644 --- a/packages/jest-cli/src/run_jest.js +++ b/packages/jest-cli/src/run_jest.js @@ -10,6 +10,7 @@ import type {Argv} from 'types/Argv'; import type {Context} from 'types/Context'; +import type {ChangedFilesPromise} from 'types/ChangedFiles'; import type {GlobalConfig} from 'types/Config'; import type {TestSelectionConfig} from './search_source'; import type {AggregatedResult} from 'types/TestResult'; @@ -102,19 +103,23 @@ const getNoTestsFoundMessage = (testRunData, pattern) => { const getTestPaths = async ( globalConfig, context, - pattern, + testSelectionConfig, argv, outputStream, + changedFilesPromise, ) => { const source = new SearchSource(context); - let data = await source.getTestPaths(pattern); + let data = await source.getTestPaths( + testSelectionConfig, + changedFilesPromise, + ); if (!data.tests.length) { - if (pattern.onlyChanged && data.noSCM) { + if (testSelectionConfig.onlyChanged && data.noSCM) { if (globalConfig.watch) { // Run all the tests updateArgv(argv, 'watchAll', {noSCM: true}); - pattern = getTestPathPattern(argv); - data = await source.getTestPaths(pattern); + testSelectionConfig = getTestPathPattern(argv); + data = await source.getTestPaths(testSelectionConfig); } else { new Console(outputStream, outputStream).log( 'Jest can only find uncommitted changed files in a git or hg ' + @@ -160,6 +165,7 @@ const runJest = async ( outputStream: stream$Writable | tty$WriteStream, testWatcher: TestWatcher, startRun: () => *, + changedFilesPromise: ?ChangedFilesPromise, onComplete: (testResults: AggregatedResult) => any, // We use this internaly at FB. Since we run multiple processes and most // of them don't match any tests, we don't want to print 'no tests found' @@ -184,6 +190,7 @@ const runJest = async ( testSelectionConfig, argv, outputStream, + changedFilesPromise, ); allTests = allTests.concat(matches.tests); return {context, matches}; diff --git a/packages/jest-cli/src/search_source.js b/packages/jest-cli/src/search_source.js index 281303fbd0bb..e396baf4fea7 100644 --- a/packages/jest-cli/src/search_source.js +++ b/packages/jest-cli/src/search_source.js @@ -12,11 +12,11 @@ import type {Context} from 'types/Context'; import type {Glob, Path} from 'types/Config'; import type {ResolveModuleConfig} from 'types/Resolve'; import type {Test} from 'types/TestRunner'; +import type {ChangedFilesPromise} from 'types/ChangedFiles'; import path from 'path'; import micromatch from 'micromatch'; import DependencyResolver from 'jest-resolve-dependencies'; -import {getChangedFilesForRoots} from 'jest-changed-files'; import {escapePathForRegex, replacePathSepForRegex} from 'jest-regex-util'; type SearchResult = {| @@ -28,15 +28,9 @@ type SearchResult = {| type StrOrRegExpPattern = RegExp | string; -type Options = {| - lastCommit?: boolean, - withAncestor?: boolean, -|}; - export type TestSelectionConfig = {| input?: string, findRelatedTests?: boolean, - lastCommit?: boolean, onlyChanged?: boolean, paths?: Array, shouldTreatInputAsPattern?: boolean, @@ -183,27 +177,28 @@ class SearchSource { return {tests: []}; } - async findChangedTests(options: Options): Promise { - const {repos, changedFiles} = await getChangedFilesForRoots( - this._context.config.roots, - options, - ); + async findTestRelatedToChangedFiles( + changedFilesPromise: ChangedFilesPromise, + ) { + const {repos, changedFiles} = await changedFilesPromise; // no SCM (git/hg/...) is found in any of the roots. const noSCM = Object.keys(repos).every(scm => repos[scm].size === 0); - return noSCM ? {noSCM: true, tests: []} : this.findRelatedTests(changedFiles); } - getTestPaths( + async getTestPaths( testSelectionConfig: TestSelectionConfig, + changedFilesPromise: ?ChangedFilesPromise, ): Promise { if (testSelectionConfig.onlyChanged) { - return this.findChangedTests({ - lastCommit: testSelectionConfig.lastCommit, - }); + if (!changedFilesPromise) { + throw new Error('This promise must be present when running with -o.'); + } + + return this.findTestRelatedToChangedFiles(changedFilesPromise); } else if ( testSelectionConfig.findRelatedTests && testSelectionConfig.paths diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index b888ca8bc34f..c73c8cfb6e49 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -14,6 +14,7 @@ import type {Context} from 'types/Context'; import ansiEscapes from 'ansi-escapes'; import chalk from 'chalk'; +import getChangedFilesPromise from './get_changed_files_promise'; import {replacePathSepForRegex} from 'jest-regex-util'; import HasteMap from 'jest-haste-map'; import isCI from 'is-ci'; @@ -115,6 +116,8 @@ const watch = ( testPathPattern: argv.testPathPattern, }), ); + const configs = contexts.map(context => context.config); + const changedFilesPromise = getChangedFilesPromise(argv, configs); return runJest( // $FlowFixMe globalConfig, @@ -123,6 +126,7 @@ const watch = ( pipe, testWatcher, startRun, + changedFilesPromise, results => { isRunning = false; hasSnapshotFailure = !!results.snapshot.failure; diff --git a/types/ChangedFiles.js b/types/ChangedFiles.js new file mode 100644 index 000000000000..c00e94609bfd --- /dev/null +++ b/types/ChangedFiles.js @@ -0,0 +1,28 @@ +/** + * 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. + * + * @flow + */ + +import type {Path} from 'types/Config'; + +export type Options = {| + lastCommit?: boolean, + withAncestor?: boolean, +|}; + +export type ChangedFiles = Set; +export type Repos = {|git: Set, hg: Set|}; +export type ChangedFilesPromise = Promise<{| + repos: Repos, + changedFiles: ChangedFiles, +|}>; + +export type SCMAdapter = {| + findChangedFiles: (cwd: Path, options: Options) => Promise>, + getRoot: (cwd: Path) => Promise, +|};