From 9a13fd62443847f806eb9dc476577c50c75e9602 Mon Sep 17 00:00:00 2001 From: cpojer Date: Thu, 16 Mar 2017 16:58:30 +0900 Subject: [PATCH 01/11] =?UTF-8?q?Add=20`=E2=80=94experimentalProjects`=20t?= =?UTF-8?q?o=20run=20multiple=20projects=20within=20the=20same=20jest-cli?= =?UTF-8?q?=20test=20run.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/jest-cli/src/TestRunner.js | 12 +- .../watch-filename-pattern-mode-test.js | 24 +-- .../watch-test-name-pattern-mode-test.js | 24 +-- packages/jest-cli/src/__tests__/watch-test.js | 85 ++-------- packages/jest-cli/src/cli/args.js | 5 + packages/jest-cli/src/cli/index.js | 8 +- packages/jest-cli/src/cli/runCLI.js | 66 +++++--- packages/jest-cli/src/runJest.js | 150 ++++++++++-------- packages/jest-cli/src/watch.js | 73 ++++----- 9 files changed, 199 insertions(+), 248 deletions(-) diff --git a/packages/jest-cli/src/TestRunner.js b/packages/jest-cli/src/TestRunner.js index d1a0242cb499..c9a2b432c7d3 100644 --- a/packages/jest-cli/src/TestRunner.js +++ b/packages/jest-cli/src/TestRunner.js @@ -61,17 +61,17 @@ class TestRunner { _dispatcher: ReporterDispatcher; constructor( - hasteContext: Context, + context: Context, config: Config, options: Options, startRun: () => *, ) { this._config = config; this._dispatcher = new ReporterDispatcher( - hasteContext.hasteFS, + context.hasteFS, options.getTestSummary, ); - this._context = hasteContext; + this._context = context; this._options = options; this._startRun = startRun; this._setupReporters(); @@ -93,7 +93,6 @@ class TestRunner { } }); - const config = this._config; const aggregatedResults = createAggregatedResults(tests.length); const estimatedTime = Math.ceil( getEstimatedTime(timings, this._options.maxWorkers) / 1000, @@ -140,6 +139,7 @@ class TestRunner { }; const updateSnapshotState = () => { + const config = this._config; const status = snapshot.cleanup( this._context.hasteFS, config.updateSnapshot, @@ -152,7 +152,7 @@ class TestRunner { aggregatedResults.snapshot.filesRemoved)); }; - this._dispatcher.onRunStart(config, aggregatedResults, { + this._dispatcher.onRunStart(this._config, aggregatedResults, { estimatedTime, showStatus: !runInBand, }); @@ -170,7 +170,7 @@ class TestRunner { updateSnapshotState(); aggregatedResults.wasInterrupted = watcher.isInterrupted(); - this._dispatcher.onRunComplete(config, aggregatedResults); + this._dispatcher.onRunComplete(this._config, aggregatedResults); const anyTestFailures = !(aggregatedResults.numFailedTests === 0 && aggregatedResults.numRuntimeErrorTestSuites === 0); diff --git a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js index 0c460b23894c..f4729f5defdf 100644 --- a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js @@ -82,7 +82,7 @@ describe('Watch mode flows', () => { let pipe; let hasteMap; let argv; - let hasteContext; + let context; let config; let hasDeprecationWarnings; let stdin; @@ -92,7 +92,7 @@ describe('Watch mode flows', () => { pipe = {write: jest.fn()}; hasteMap = {on: () => {}}; argv = {}; - hasteContext = {}; + context = {}; config = {}; hasDeprecationWarnings = false; stdin = new MockStdin(); @@ -100,15 +100,7 @@ describe('Watch mode flows', () => { it('Pressing "P" enters pattern mode', () => { config = {rootDir: ''}; - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); // Write a enter pattern mode stdin.emit(KEYS.P); @@ -145,15 +137,7 @@ describe('Watch mode flows', () => { it('Results in pattern mode get truncated appropriately', () => { config = {rootDir: ''}; - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); stdin.emit(KEYS.P); diff --git a/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js index c16b7bb2f6f6..2b85748c2b76 100644 --- a/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js @@ -106,7 +106,7 @@ describe('Watch mode flows', () => { let pipe; let hasteMap; let argv; - let hasteContext; + let context; let config; let hasDeprecationWarnings; let stdin; @@ -116,7 +116,7 @@ describe('Watch mode flows', () => { pipe = {write: jest.fn()}; hasteMap = {on: () => {}}; argv = {}; - hasteContext = {}; + context = {}; config = {}; hasDeprecationWarnings = false; stdin = new MockStdin(); @@ -124,15 +124,7 @@ describe('Watch mode flows', () => { it('Pressing "T" enters pattern mode', () => { config = {rootDir: ''}; - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); // Write a enter pattern mode stdin.emit(KEYS.T); @@ -169,15 +161,7 @@ describe('Watch mode flows', () => { it('Results in pattern mode get truncated appropriately', () => { config = {rootDir: ''}; - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); stdin.emit(KEYS.T); diff --git a/packages/jest-cli/src/__tests__/watch-test.js b/packages/jest-cli/src/__tests__/watch-test.js index 3c10e7d21b6a..3390a985abcd 100644 --- a/packages/jest-cli/src/__tests__/watch-test.js +++ b/packages/jest-cli/src/__tests__/watch-test.js @@ -39,7 +39,7 @@ describe('Watch mode flows', () => { let pipe; let hasteMap; let argv; - let hasteContext; + let context; let config; let hasDeprecationWarnings; let stdin; @@ -48,7 +48,7 @@ describe('Watch mode flows', () => { pipe = {write: jest.fn()}; hasteMap = {on: () => {}}; argv = {}; - hasteContext = {}; + context = {}; config = {roots: [], testPathIgnorePatterns: [], testRegex: ''}; hasDeprecationWarnings = false; stdin = new MockStdin(); @@ -58,19 +58,10 @@ describe('Watch mode flows', () => { argv.testPathPattern = 'test-*'; config.testPathPattern = 'test-*'; - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); expect(runJestMock).toBeCalledWith( - hasteContext, - config, + [context], argv, pipe, new TestWatcher({isWatchMode: true}), @@ -83,19 +74,10 @@ describe('Watch mode flows', () => { argv.testNamePattern = 'test-*'; config.testNamePattern = 'test-*'; - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); expect(runJestMock).toBeCalledWith( - hasteContext, - config, + [context], argv, pipe, new TestWatcher({isWatchMode: true}), @@ -105,18 +87,9 @@ describe('Watch mode flows', () => { }); it('Runs Jest once by default and shows usage', () => { - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); expect(runJestMock).toBeCalledWith( - hasteContext, - config, + [context], argv, pipe, new TestWatcher({isWatchMode: true}), @@ -127,15 +100,7 @@ describe('Watch mode flows', () => { }); it('Pressing "o" runs test in "only changed files" mode', () => { - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); runJestMock.mockReset(); stdin.emit(KEYS.O); @@ -149,15 +114,7 @@ describe('Watch mode flows', () => { }); it('Pressing "a" runs test in "watch all" mode', () => { - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); runJestMock.mockReset(); stdin.emit(KEYS.A); @@ -171,35 +128,19 @@ describe('Watch mode flows', () => { }); it('Pressing "ENTER" reruns the tests', () => { - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); expect(runJestMock).toHaveBeenCalledTimes(1); stdin.emit(KEYS.ENTER); expect(runJestMock).toHaveBeenCalledTimes(2); }); it('Pressing "u" reruns the tests in "update snapshot" mode', () => { - watch( - config, - pipe, - argv, - hasteMap, - hasteContext, - hasDeprecationWarnings, - stdin, - ); + watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); runJestMock.mockReset(); stdin.emit(KEYS.U); - expect(runJestMock.mock.calls[0][1]).toEqual({ + expect(runJestMock.mock.calls[0][0][0].config).toEqual({ roots: [], testPathIgnorePatterns: [], testRegex: '', diff --git a/packages/jest-cli/src/cli/args.js b/packages/jest-cli/src/cli/args.js index 884e8441e033..e08cdba10d9d 100644 --- a/packages/jest-cli/src/cli/args.js +++ b/packages/jest-cli/src/cli/args.js @@ -113,6 +113,11 @@ const options = { description: 'Use this flag to show full diffs instead of a patch.', type: 'boolean', }, + experimentalProjects: { + description: 'A list of projects that use Jest to run all tests in a ' + + 'single run.', + type: 'array', + }, findRelatedTests: { description: 'Find related tests for a list of source files that were ' + 'passed in as arguments. Useful for pre-commit hook integration to run ' + diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index 8819bf08b236..b438957acff2 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -40,7 +40,13 @@ function run(argv?: Object, root?: Path) { root = pkgDir.sync(); } - getJest(root).runCLI(argv, root, result => { + argv.projects = argv.experimentalProjects; + if (!argv.projects) { + argv.projects = [root]; + } + + const execute = argv.projects.length === 1 ? getJest(root).runCLI : runCLI; + execute(argv, argv.projects, result => { const code = !result || result.success ? 0 : 1; process.on('exit', () => process.exit(code)); if (argv && argv.forceExit) { diff --git a/packages/jest-cli/src/cli/runCLI.js b/packages/jest-cli/src/cli/runCLI.js index d0f5526d0dcb..1680be085d7a 100644 --- a/packages/jest-cli/src/cli/runCLI.js +++ b/packages/jest-cli/src/cli/runCLI.js @@ -10,7 +10,7 @@ 'use strict'; import type {AggregatedResult} from 'types/TestResult'; -import type {Path} from 'types/Config'; +import type {Config, Path} from 'types/Config'; const Runtime = require('jest-runtime'); @@ -28,9 +28,9 @@ const watch = require('../watch'); const VERSION = require('../../package.json').version; -module.exports = ( +module.exports = async ( argv: Object, - root: Path, + roots: Array, onComplete: (results: ?AggregatedResult) => void, ) => { const realFs = require('fs'); @@ -38,51 +38,67 @@ module.exports = ( fs.gracefulify(realFs); const pipe = argv.json ? process.stderr : process.stdout; - argv = argv || {}; if (argv.version) { pipe.write(`v${VERSION}\n`); onComplete && onComplete(); return; } - const _run = async ({config, hasDeprecationWarnings}) => { + const _run = async ( + configs: Array<{config: Config, hasDeprecationWarnings: boolean}>, + ) => { if (argv.debug || argv.showConfig) { - logDebugMessages(config, pipe); + logDebugMessages(configs[0].config, pipe); } if (argv.showConfig) { process.exit(0); } - createDirectory(config.cacheDirectory); - const hasteMapInstance = Runtime.createHasteMap(config, { - console: new Console(pipe, pipe), - maxWorkers: getMaxWorkers(argv), - resetCache: !config.cache, - watch: config.watch, - }); - - const hasteMap = await hasteMapInstance.build(); - const context = createContext(config, hasteMap); if (argv.watch || argv.watchAll) { + const {config} = configs[0]; + createDirectory(config.cacheDirectory); + const hasteMapInstance = Runtime.createHasteMap(config, { + console: new Console(pipe, pipe), + maxWorkers: getMaxWorkers(argv), + resetCache: !config.cache, + watch: config.watch, + }); + + const hasteMap = await hasteMapInstance.build(); + const context = createContext(config, hasteMap); return watch( config, pipe, argv, hasteMapInstance, context, - hasDeprecationWarnings, + // TODO + configs[0].hasDeprecationWarnings, ); } else { + const contexts = await Promise.all( + configs.map(async ({config}) => { + createDirectory(config.cacheDirectory); + return createContext( + config, + await Runtime.createHasteMap(config, { + console: new Console(pipe, pipe), + maxWorkers: getMaxWorkers(argv), + resetCache: !config.cache, + watch: config.watch, + }).build(), + ); + }), + ); + const startRun = () => { preRunMessage.print(pipe); - const testWatcher = new TestWatcher({isWatchMode: false}); - return runJest( - context, - config, + runJest( + contexts, argv, pipe, - testWatcher, + new TestWatcher({isWatchMode: false}), startRun, onComplete, ); @@ -91,10 +107,12 @@ module.exports = ( } }; - readConfig(argv, root).then(_run).catch(error => { + try { + await _run(await Promise.all(roots.map(root => readConfig(argv, root)))); + } catch (error) { clearLine(process.stderr); clearLine(process.stdout); console.error(chalk.red(error.stack)); process.exit(1); - }); + } }; diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index 3340775fbfaa..ef42a9e5ff1a 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -9,9 +9,10 @@ */ 'use strict'; -import type {Config} from 'types/Config'; import type {Context} from 'types/Context'; +import type {Test} from 'types/TestRunner'; import type {PatternInfo} from './SearchSource'; +import type TestWatcher from './TestWatcher'; const fs = require('graceful-fs'); @@ -42,74 +43,70 @@ const getTestSummary = (argv: Object, patternInfo: PatternInfo) => { chalk.dim('.'); }; +const getTestPaths = async (context, patternInfo, argv, pipe) => { + const source = new SearchSource(context, context.config); + let data = await source.getTestPaths(patternInfo); + if (!data.paths.length) { + const localConsole = new Console(pipe, pipe); + if (patternInfo.onlyChanged && data.noSCM) { + if (context.config.watch) { + // Run all the tests + setState(argv, 'watchAll', { + noSCM: true, + }); + patternInfo = getTestPathPatternInfo(argv); + data = await source.getTestPaths(patternInfo); + } else { + localConsole.log( + 'Jest can only find uncommitted changed files in a git or hg ' + + 'repository. If you make your project a git or hg ' + + 'repository (`git init` or `hg init`), Jest will be able ' + + 'to only run tests related to files changed since the last ' + + 'commit.', + ); + } + } + + localConsole.log( + source.getNoTestsFoundMessage(patternInfo, context.config, data), + ); + } + + return { + data, + patternInfo, + }; +}; + const runJest = async ( - hasteContext: Context, - config: Config, + contexts: Array, argv: Object, pipe: stream$Writable | tty$WriteStream, - testWatcher: any, + testWatcher: TestWatcher, startRun: () => *, onComplete: (testResults: any) => void, ) => { const maxWorkers = getMaxWorkers(argv); - const source = new SearchSource(hasteContext, config); - const testRunnerOptions = { - getTestSummary: () => getTestSummary(argv, patternInfo), - maxWorkers, - }; - let patternInfo = getTestPathPatternInfo(argv); - - const processTests = data => { - if (!data.paths.length) { - const localConsole = new Console(pipe, pipe); - if (patternInfo.onlyChanged && data.noSCM) { - if (config.watch) { - // Run all the tests - setState(argv, 'watchAll', { - noSCM: true, - }); - patternInfo = getTestPathPatternInfo(argv); - return source.getTestPaths(patternInfo); - } else { - localConsole.log( - 'Jest can only find uncommitted changed files in a git or hg ' + - 'repository. If you make your project a git or hg ' + - 'repository (`git init` or `hg init`), Jest will be able ' + - 'to only run tests related to files changed since the last ' + - 'commit.', - ); - } - } - - localConsole.log( - source.getNoTestsFoundMessage(patternInfo, config, data), + const context = contexts[0]; + const testRunData = await Promise.all( + contexts.map(async context => { + const {data, patternInfo} = await getTestPaths( + context, + getTestPathPatternInfo(argv), + argv, + pipe, ); - } - - if ( - data.paths.length === 1 && - hasteContext.config.silent !== true && - hasteContext.config.verbose !== false - ) { - // $FlowFixMe - config = (hasteContext.config = Object.assign({}, hasteContext.config, { - verbose: true, - })); - } - return data; - }; - - const runTests = async tests => - new TestRunner(hasteContext, config, testRunnerOptions, startRun).runTests( - tests, - testWatcher, - ); + const sequencer = new TestSequencer(context); + const tests = sequencer.sort(data.paths); + return {context, patternInfo, sequencer, tests}; + }), + ); const processResults = runResults => { - if (config.testResultsProcessor) { + if (context.config.testResultsProcessor) { /* $FlowFixMe */ - runResults = require(config.testResultsProcessor)(runResults); + runResults = require(context.config.testResultsProcessor)(runResults); } if (argv.json) { if (argv.outputFile) { @@ -130,17 +127,40 @@ const runJest = async ( return onComplete && onComplete(runResults); }; - const data = await source.getTestPaths(patternInfo); - if (config.updateSnapshot === true) { - hasteContext.config = Object.assign({}, hasteContext.config, { + const allTests = testRunData + .reduce((tests, testRun) => tests.concat(testRun.tests), []) + .sort((a: Test, b: Test) => { + if (a.duration != null && b.duration != null) { + return a.duration < b.duration ? 1 : -1; + } + return a.duration == null ? 1 : 0; + }); + + if ( + allTests.length === 1 && + context.config.silent !== true && + context.config.verbose !== false + ) { + context.config = Object.assign({}, context.config, {verbose: true}); + } + + if (context.config.updateSnapshot === true) { + context.config = Object.assign({}, context.config, { updateSnapshot: true, }); } - processTests(data); - const sequencer = new TestSequencer(hasteContext); - const tests = sequencer.sort(data.paths); - const results = await runTests(tests); - sequencer.cacheResults(tests, results); + + const results = await new TestRunner( + context, + context.config, + { + getTestSummary: () => getTestSummary(argv, testRunData[0].patternInfo), + maxWorkers, + }, + startRun, + ).runTests(allTests, testWatcher); + testRunData.forEach(({sequencer, tests}) => + sequencer.cacheResults(tests, results)); return processResults(results); }; diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index 137c5acf989b..e97e70b46f17 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -33,14 +33,14 @@ const watch = ( pipe: stream$Writable | tty$WriteStream, argv: Object, hasteMap: HasteMap, - hasteContext: Context, + context: Context, hasDeprecationWarnings?: boolean, stdin?: stream$Readable | tty$ReadStream = process.stdin, ) => { if (hasDeprecationWarnings) { return handleDeprecatedWarnings(pipe, stdin) .then(() => { - watch(config, pipe, argv, hasteMap, hasteContext); + watch(config, pipe, argv, hasteMap, context); }) .catch(() => process.exit(0)); } @@ -60,7 +60,7 @@ const watch = ( let shouldDisplayWatchUsage = true; let isWatchUsageDisplayed = false; - testPathPatternPrompt.updateSearchSource(hasteContext); + testPathPatternPrompt.updateSearchSource(context); hasteMap.on('change', ({eventsQueue, hasteFS, moduleMap}) => { const validPaths = eventsQueue.filter(({filePath}) => { @@ -68,9 +68,9 @@ const watch = ( }); if (validPaths.length) { - hasteContext = createContext(config, {hasteFS, moduleMap}); + context = createContext(config, {hasteFS, moduleMap}); prompt.abort(); - testPathPatternPrompt.updateSearchSource(hasteContext); + testPathPatternPrompt.updateSearchSource(context); startRun(); } }); @@ -91,44 +91,37 @@ const watch = ( isInteractive && pipe.write(CLEAR); preRunMessage.print(pipe); isRunning = true; - return runJest( - hasteContext, + // $FlowFixMe + context.config = Object.freeze( // $FlowFixMe - Object.freeze( - // $FlowFixMe - Object.assign( - { - testNamePattern: argv.testNamePattern, - testPathPattern: argv.testPathPattern, - }, - config, - overrideConfig, - ), + Object.assign( + { + testNamePattern: argv.testNamePattern, + testPathPattern: argv.testPathPattern, + }, + config, + overrideConfig, ), - argv, - pipe, - testWatcher, - startRun, - results => { - isRunning = false; - hasSnapshotFailure = !!results.snapshot.failure; - // Create a new testWatcher instance so that re-runs won't be blocked. - // The old instance that was passed to Jest will still be interrupted - // and prevent test runs from the previous run. - testWatcher = new TestWatcher({isWatchMode: true}); - if (shouldDisplayWatchUsage) { - pipe.write(usage(argv, hasSnapshotFailure)); - shouldDisplayWatchUsage = false; // hide Watch Usage after first run - isWatchUsageDisplayed = true; - } else { - pipe.write(showToggleUsagePrompt()); - shouldDisplayWatchUsage = false; - isWatchUsageDisplayed = false; - } + ); + return runJest([context], argv, pipe, testWatcher, startRun, results => { + isRunning = false; + hasSnapshotFailure = !!results.snapshot.failure; + // Create a new testWatcher instance so that re-runs won't be blocked. + // The old instance that was passed to Jest will still be interrupted + // and prevent test runs from the previous run. + testWatcher = new TestWatcher({isWatchMode: true}); + if (shouldDisplayWatchUsage) { + pipe.write(usage(argv, hasSnapshotFailure)); + shouldDisplayWatchUsage = false; // hide Watch Usage after first run + isWatchUsageDisplayed = true; + } else { + pipe.write(showToggleUsagePrompt()); + shouldDisplayWatchUsage = false; + isWatchUsageDisplayed = false; + } - testNamePatternPrompt.updateCachedTestResults(results.testResults); - }, - ).then(() => {}, error => console.error(chalk.red(error.stack))); + testNamePatternPrompt.updateCachedTestResults(results.testResults); + }).then(() => {}, error => console.error(chalk.red(error.stack))); }; const onKeypress = (key: string) => { From e339ff68f773214f73929835e07f4197b65395b0 Mon Sep 17 00:00:00 2001 From: cpojer Date: Sun, 19 Mar 2017 08:09:32 +0900 Subject: [PATCH 02/11] =?UTF-8?q?Improve=20the=20=E2=80=9Cno=20tests=20fou?= =?UTF-8?q?nd=E2=80=9D=20message=20for=20multiple=20projects.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/jest-cli/src/SearchSource.js | 46 ------------ packages/jest-cli/src/runJest.js | 102 ++++++++++++++++++-------- 2 files changed, 73 insertions(+), 75 deletions(-) diff --git a/packages/jest-cli/src/SearchSource.js b/packages/jest-cli/src/SearchSource.js index c7af5d95fe31..3ec85913e0b6 100644 --- a/packages/jest-cli/src/SearchSource.js +++ b/packages/jest-cli/src/SearchSource.js @@ -10,7 +10,6 @@ 'use strict'; -import type {Config} from 'types/Config'; import type {Context} from 'types/Context'; import type {Glob, Path} from 'types/Config'; import type {ResolveModuleConfig} from 'types/Resolve'; @@ -19,7 +18,6 @@ const micromatch = require('micromatch'); const DependencyResolver = require('jest-resolve-dependencies'); -const chalk = require('chalk'); const changedFiles = require('jest-changed-files'); const path = require('path'); const { @@ -64,8 +62,6 @@ const hg = changedFiles.hg; const determineSCM = path => Promise.all([git.isGitRepository(path), hg.isHGRepository(path)]); const pathToRegex = p => replacePathSepForRegex(p); -const pluralize = (word: string, count: number, ending: string) => - `${count} ${word}${count === 1 ? '' : ending}`; const globsToMatcher = (globs: ?Array) => { if (globs == null || globs.length === 0) { @@ -223,48 +219,6 @@ class SearchSource { }); } - getNoTestsFoundMessage( - patternInfo: PatternInfo, - config: Config, - data: SearchResult, - ): string { - if (patternInfo.onlyChanged) { - return chalk.bold( - 'No tests found related to files changed since last commit.\n', - ) + - chalk.dim( - patternInfo.watch - ? 'Press `a` to run all tests, or run Jest with `--watchAll`.' - : 'Run Jest without `-o` to run all tests.', - ); - } - - const testPathPattern = SearchSource.getTestPathPattern(patternInfo); - const stats = data.stats || {}; - const statsMessage = Object.keys(stats) - .map(key => { - const value = key === 'testPathPattern' ? testPathPattern : config[key]; - if (value) { - const matches = pluralize('match', stats[key], 'es'); - return ` ${key}: ${chalk.yellow(value)} - ${matches}`; - } - return null; - }) - .filter(line => line) - .join('\n'); - - return chalk.bold('No tests found') + - '\n' + - (data.total - ? ` ${pluralize('file', data.total || 0, 's')} checked.\n` + - statsMessage - : `No files found in ${config.rootDir}.\n` + - `Make sure Jest's configuration does not exclude this directory.` + - `\nTo set up Jest, make sure a package.json file exists.\n` + - `Jest Documentation: ` + - `facebook.github.io/jest/docs/configuration.html`); - } - getTestPaths(patternInfo: PatternInfo): Promise { if (patternInfo.onlyChanged) { return this.findChangedTests({lastCommit: patternInfo.lastCommit}); diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index ef42a9e5ff1a..bcd4261225a1 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -27,11 +27,11 @@ const getMaxWorkers = require('./lib/getMaxWorkers'); const path = require('path'); const setState = require('./lib/setState'); -const getTestSummary = (argv: Object, patternInfo: PatternInfo) => { - const testPathPattern = SearchSource.getTestPathPattern(patternInfo); - const testInfo = patternInfo.onlyChanged +const getTestSummary = (argv: Object, pattern: PatternInfo) => { + const testPathPattern = SearchSource.getTestPathPattern(pattern); + const testInfo = pattern.onlyChanged ? chalk.dim(' related to changed files') - : patternInfo.input !== '' ? chalk.dim(' matching ') + testPathPattern : ''; + : pattern.input !== '' ? chalk.dim(' matching ') + testPathPattern : ''; const nameInfo = argv.testNamePattern ? chalk.dim(' with tests matching ') + `"${argv.testNamePattern}"` @@ -43,19 +43,69 @@ const getTestSummary = (argv: Object, patternInfo: PatternInfo) => { chalk.dim('.'); }; -const getTestPaths = async (context, patternInfo, argv, pipe) => { +const getNoTestsFoundMessage = (testRunData, pattern) => { + if (pattern.onlyChanged) { + return chalk.bold( + 'No tests found related to files changed since last commit.\n', + ) + + chalk.dim( + pattern.watch + ? 'Press `a` to run all tests, or run Jest with `--watchAll`.' + : 'Run Jest without `-o` to run all tests.', + ); + } + + const pluralize = (word: string, count: number, ending: string) => + `${count} ${word}${count === 1 ? '' : ending}`; + const testPathPattern = SearchSource.getTestPathPattern(pattern); + const individualResults = testRunData.map(testRun => { + const stats = testRun.matches.stats || {}; + const config = testRun.context.config; + const statsMessage = Object.keys(stats) + .map(key => { + if (key === 'roots' && config.roots.length === 1) { + return null; + } + const value = config[key]; + if (value) { + const matches = pluralize('match', stats[key], 'es'); + return ` ${key}: ${chalk.yellow(value)} - ${matches}`; + } + return null; + }) + .filter(line => line) + .join('\n'); + + return testRun.matches.total + ? `In ${chalk.bold(config.rootDir)}\n` + + ` ${pluralize('file', testRun.matches.total || 0, 's')} checked.\n` + + statsMessage + : `No files found in ${config.rootDir}.\n` + + `Make sure Jest's configuration does not exclude this directory.` + + `\nTo set up Jest, make sure a package.json file exists.\n` + + `Jest Documentation: ` + + `facebook.github.io/jest/docs/configuration.html`; + }); + return chalk.bold('No tests found') + + '\n' + + individualResults.join('\n') + + '\n' + + `Pattern: ${chalk.yellow(testPathPattern)} - 0 matches`; +}; + +const getTestPaths = async (context, pattern, argv, pipe) => { const source = new SearchSource(context, context.config); - let data = await source.getTestPaths(patternInfo); + let data = await source.getTestPaths(pattern); if (!data.paths.length) { const localConsole = new Console(pipe, pipe); - if (patternInfo.onlyChanged && data.noSCM) { + if (pattern.onlyChanged && data.noSCM) { if (context.config.watch) { // Run all the tests setState(argv, 'watchAll', { noSCM: true, }); - patternInfo = getTestPathPatternInfo(argv); - data = await source.getTestPaths(patternInfo); + pattern = getTestPathPatternInfo(argv); + data = await source.getTestPaths(pattern); } else { localConsole.log( 'Jest can only find uncommitted changed files in a git or hg ' + @@ -66,16 +116,9 @@ const getTestPaths = async (context, patternInfo, argv, pipe) => { ); } } - - localConsole.log( - source.getNoTestsFoundMessage(patternInfo, context.config, data), - ); } - return { - data, - patternInfo, - }; + return data; }; const runJest = async ( @@ -90,16 +133,10 @@ const runJest = async ( const context = contexts[0]; const testRunData = await Promise.all( contexts.map(async context => { - const {data, patternInfo} = await getTestPaths( - context, - getTestPathPatternInfo(argv), - argv, - pipe, - ); - + const matches = await getTestPaths(context, pattern, argv, pipe); const sequencer = new TestSequencer(context); - const tests = sequencer.sort(data.paths); - return {context, patternInfo, sequencer, tests}; + const tests = sequencer.sort(matches.paths); + return {context, matches, sequencer, tests}; }), ); @@ -136,12 +173,20 @@ const runJest = async ( return a.duration == null ? 1 : 0; }); + if (!allTests.length) { + const localConsole = new Console(pipe, pipe); + localConsole.log(getNoTestsFoundMessage(testRunData, pattern)); + } + if ( allTests.length === 1 && context.config.silent !== true && context.config.verbose !== false ) { - context.config = Object.assign({}, context.config, {verbose: true}); + contexts.forEach( + context => + context.config = Object.assign({}, context.config, {verbose: true}), + ); } if (context.config.updateSnapshot === true) { @@ -151,10 +196,9 @@ const runJest = async ( } const results = await new TestRunner( - context, context.config, { - getTestSummary: () => getTestSummary(argv, testRunData[0].patternInfo), + getTestSummary: () => getTestSummary(argv, pattern), maxWorkers, }, startRun, From 5752f242865fca1ffd5daf3e5cd2344d7f54d68a Mon Sep 17 00:00:00 2001 From: cpojer Date: Sun, 19 Mar 2017 08:11:45 +0900 Subject: [PATCH 03/11] Do not pass a single context to TestRunner and remove RunnerContext from reporters. --- packages/jest-cli/src/TestRunner.js | 67 +++++++++---------- .../jest-cli/src/__tests__/TestRunner-test.js | 6 +- .../jest-cli/src/reporters/BaseReporter.js | 6 +- .../src/reporters/CoverageReporter.js | 43 ++++++------ .../jest-cli/src/reporters/DefaultReporter.js | 3 +- .../jest-cli/src/reporters/NotifyReporter.js | 7 +- .../jest-cli/src/reporters/SummaryReporter.js | 22 +++--- .../__tests__/CoverageReporter-test.js | 2 + types/Reporters.js | 5 -- 9 files changed, 86 insertions(+), 75 deletions(-) diff --git a/packages/jest-cli/src/TestRunner.js b/packages/jest-cli/src/TestRunner.js index c9a2b432c7d3..64de7366ca93 100644 --- a/packages/jest-cli/src/TestRunner.js +++ b/packages/jest-cli/src/TestRunner.js @@ -16,8 +16,6 @@ import type { } from 'types/TestResult'; import type {Config} from 'types/Config'; import type {Context} from 'types/Context'; -import type {HasteFS} from 'types/HasteMap'; -import type {RunnerContext} from 'types/Reporters'; import type {Test, Tests} from 'types/TestRunner'; import type BaseReporter from './reporters/BaseReporter'; @@ -54,24 +52,14 @@ type OnTestSuccess = (test: Test, result: TestResult) => void; const TEST_WORKER_PATH = require.resolve('./TestWorker'); class TestRunner { - _context: Context; _config: Config; _options: Options; _startRun: () => *; _dispatcher: ReporterDispatcher; - constructor( - context: Context, - config: Config, - options: Options, - startRun: () => *, - ) { + constructor(config: Config, options: Options, startRun: () => *) { this._config = config; - this._dispatcher = new ReporterDispatcher( - context.hasteFS, - options.getTestSummary, - ); - this._context = context; + this._dispatcher = new ReporterDispatcher(); this._options = options; this._startRun = startRun; this._setupReporters(); @@ -87,7 +75,9 @@ class TestRunner { async runTests(tests: Tests, watcher: TestWatcher) { const timings = []; + const contexts = new Set(); tests.forEach(test => { + contexts.add(test.context); if (test.duration) { timings.push(test.duration); } @@ -121,7 +111,7 @@ class TestRunner { } addResult(aggregatedResults, testResult); this._dispatcher.onTestResult(test, testResult, aggregatedResults); - this._bailIfNeeded(aggregatedResults, watcher); + this._bailIfNeeded(contexts, aggregatedResults, watcher); }; const onFailure = (test: Test, error: TestError) => { @@ -139,14 +129,15 @@ class TestRunner { }; const updateSnapshotState = () => { - const config = this._config; - const status = snapshot.cleanup( - this._context.hasteFS, - config.updateSnapshot, - ); - aggregatedResults.snapshot.filesRemoved += status.filesRemoved; - aggregatedResults.snapshot.didUpdate = config.updateSnapshot; - aggregatedResults.snapshot.failure = !!(!config.updateSnapshot && + contexts.forEach(context => { + const status = snapshot.cleanup( + context.hasteFS, + this._config.updateSnapshot, + ); + aggregatedResults.snapshot.filesRemoved += status.filesRemoved; + }); + aggregatedResults.snapshot.didUpdate = this._config.updateSnapshot; + aggregatedResults.snapshot.failure = !!(!this._config.updateSnapshot && (aggregatedResults.snapshot.unchecked || aggregatedResults.snapshot.unmatched || aggregatedResults.snapshot.filesRemoved)); @@ -170,7 +161,7 @@ class TestRunner { updateSnapshotState(); aggregatedResults.wasInterrupted = watcher.isInterrupted(); - this._dispatcher.onRunComplete(this._config, aggregatedResults); + this._dispatcher.onRunComplete(contexts, this._config, aggregatedResults); const anyTestFailures = !(aggregatedResults.numFailedTests === 0 && aggregatedResults.numRuntimeErrorTestSuites === 0); @@ -293,18 +284,28 @@ class TestRunner { this.addReporter(new CoverageReporter()); } - this.addReporter(new SummaryReporter()); + this.addReporter(new SummaryReporter({ + getTestSummary: this._options.getTestSummary, + })); if (config.notify) { this.addReporter(new NotifyReporter(this._startRun)); } } - _bailIfNeeded(aggregatedResults: AggregatedResult, watcher: TestWatcher) { + _bailIfNeeded( + contexts: Set, + aggregatedResults: AggregatedResult, + watcher: TestWatcher, + ) { if (this._config.bail && aggregatedResults.numFailedTests !== 0) { if (watcher.isWatchMode()) { watcher.setState({interrupted: true}); } else { - this._dispatcher.onRunComplete(this._config, aggregatedResults); + this._dispatcher.onRunComplete( + contexts, + this._config, + aggregatedResults, + ); process.exit(1); } } @@ -426,10 +427,8 @@ const buildFailureTestResult = ( class ReporterDispatcher { _disabled: boolean; _reporters: Array; - _runnerContext: RunnerContext; - constructor(hasteFS: HasteFS, getTestSummary: () => string) { - this._runnerContext = {getTestSummary, hasteFS}; + constructor() { this._reporters = []; } @@ -445,7 +444,7 @@ class ReporterDispatcher { onTestResult(test, testResult, results) { this._reporters.forEach(reporter => - reporter.onTestResult(test, testResult, results, this._runnerContext)); + reporter.onTestResult(test, testResult, results)); } onTestStart(test) { @@ -454,12 +453,12 @@ class ReporterDispatcher { onRunStart(config, results, options) { this._reporters.forEach(reporter => - reporter.onRunStart(config, results, this._runnerContext, options)); + reporter.onRunStart(config, results, options)); } - onRunComplete(config, results) { + onRunComplete(contexts, config, results) { this._reporters.forEach(reporter => - reporter.onRunComplete(config, results, this._runnerContext)); + reporter.onRunComplete(contexts, config, results)); } // Return a list of last errors for every reporter diff --git a/packages/jest-cli/src/__tests__/TestRunner-test.js b/packages/jest-cli/src/__tests__/TestRunner-test.js index d5c186d904a0..e3fe9c45afc5 100644 --- a/packages/jest-cli/src/__tests__/TestRunner-test.js +++ b/packages/jest-cli/src/__tests__/TestRunner-test.js @@ -30,7 +30,7 @@ jest.mock('../TestWorker', () => {}); jest.mock('../reporters/DefaultReporter'); test('.addReporter() .removeReporter()', () => { - const runner = new TestRunner({}, {}, {}); + const runner = new TestRunner({}, {}); const reporter = new SummaryReporter(); runner.addReporter(reporter); expect(runner._dispatcher._reporters).toContain(reporter); @@ -43,7 +43,7 @@ describe('_createInBandTestRun()', () => { const config = {watch: true}; const rawModuleMap = jest.fn(); const context = {config, moduleMap: {getRawModuleMap: () => rawModuleMap}}; - const runner = new TestRunner(context, config, {maxWorkers: 2}); + const runner = new TestRunner(config, {maxWorkers: 2}); return runner ._createParallelTestRun( @@ -70,7 +70,7 @@ describe('_createInBandTestRun()', () => { test('does not inject the rawModuleMap in non watch mode', () => { const config = {watch: false}; const context = {config}; - const runner = new TestRunner(context, config, {maxWorkers: 1}); + const runner = new TestRunner(config, {maxWorkers: 1}); return runner ._createParallelTestRun( diff --git a/packages/jest-cli/src/reporters/BaseReporter.js b/packages/jest-cli/src/reporters/BaseReporter.js index 49ace2212b7d..e2b7b84e6a3c 100644 --- a/packages/jest-cli/src/reporters/BaseReporter.js +++ b/packages/jest-cli/src/reporters/BaseReporter.js @@ -11,8 +11,9 @@ import type {AggregatedResult, TestResult} from 'types/TestResult'; import type {Config} from 'types/Config'; +import type {Context} from 'types/Context'; import type {Test} from 'types/TestRunner'; -import type {ReporterOnStartOptions, RunnerContext} from 'types/Reporters'; +import type {ReporterOnStartOptions} from 'types/Reporters'; const preRunMessage = require('../preRunMessage'); @@ -26,7 +27,6 @@ class BaseReporter { onRunStart( config: Config, results: AggregatedResult, - runnerContext: RunnerContext, options: ReporterOnStartOptions, ) { preRunMessage.remove(process.stderr); @@ -37,9 +37,9 @@ class BaseReporter { onTestStart(test: Test) {} onRunComplete( + contexts: Set, config: Config, aggregatedResults: AggregatedResult, - runnerContext: RunnerContext, ): ?Promise {} _setError(error: Error) { diff --git a/packages/jest-cli/src/reporters/CoverageReporter.js b/packages/jest-cli/src/reporters/CoverageReporter.js index 4a5b46231f56..2a8178522299 100644 --- a/packages/jest-cli/src/reporters/CoverageReporter.js +++ b/packages/jest-cli/src/reporters/CoverageReporter.js @@ -11,8 +11,8 @@ import type {AggregatedResult, CoverageMap, TestResult} from 'types/TestResult'; import type {Config} from 'types/Config'; +import type {Context} from 'types/Context'; import type {Test} from 'types/TestRunner'; -import type {RunnerContext} from 'types/Reporters'; const BaseReporter = require('./BaseReporter'); @@ -60,11 +60,11 @@ class CoverageReporter extends BaseReporter { } onRunComplete( + contexts: Set, config: Config, aggregatedResults: AggregatedResult, - runnerContext: RunnerContext, ) { - this._addUntestedFiles(config, runnerContext); + this._addUntestedFiles(contexts); let map = this._coverageMap; let sourceFinder: Object; if (config.mapCoverage) { @@ -104,37 +104,42 @@ class CoverageReporter extends BaseReporter { this._checkThreshold(map, config); } - _addUntestedFiles(config: Config, runnerContext: RunnerContext) { - if (config.collectCoverageFrom && config.collectCoverageFrom.length) { + _addUntestedFiles(contexts: Set) { + const files = []; + contexts.forEach(context => { + const config = context.config; + if (config.collectCoverageFrom && config.collectCoverageFrom.length) { + context.hasteFS + .matchFilesWithGlob(config.collectCoverageFrom, config.rootDir) + .forEach(filePath => + files.push({ + config, + path: filePath, + })); + } + }); + if (files.length) { if (isInteractive) { process.stderr.write( RUNNING_TEST_COLOR('Running coverage on untested files...'), ); } - const files = runnerContext.hasteFS.matchFilesWithGlob( - config.collectCoverageFrom, - config.rootDir, - ); - - files.forEach(filename => { - if (!this._coverageMap.data[filename]) { + files.forEach(({config, path}) => { + if (!this._coverageMap.data[path]) { try { - const source = fs.readFileSync(filename).toString(); - const result = generateEmptyCoverage(source, filename, config); + const source = fs.readFileSync(path).toString(); + const result = generateEmptyCoverage(source, path, config); if (result) { this._coverageMap.addFileCoverage(result.coverage); if (result.sourceMapPath) { - this._sourceMapStore.registerURL( - filename, - result.sourceMapPath, - ); + this._sourceMapStore.registerURL(path, result.sourceMapPath); } } } catch (e) { console.error( chalk.red( ` - Failed to collect coverage from ${filename} + Failed to collect coverage from ${path} ERROR: ${e} STACK: ${e.stack} `, diff --git a/packages/jest-cli/src/reporters/DefaultReporter.js b/packages/jest-cli/src/reporters/DefaultReporter.js index 987090add930..100192f0c06b 100644 --- a/packages/jest-cli/src/reporters/DefaultReporter.js +++ b/packages/jest-cli/src/reporters/DefaultReporter.js @@ -15,7 +15,7 @@ import type {AggregatedResult, TestResult} from 'types/TestResult'; import type {Config, Path} from 'types/Config'; import type {Test} from 'types/TestRunner'; -import type {ReporterOnStartOptions, RunnerContext} from 'types/Reporters'; +import type {ReporterOnStartOptions} from 'types/Reporters'; const BaseReporter = require('./BaseReporter'); const Status = require('./Status'); @@ -114,7 +114,6 @@ class DefaultReporter extends BaseReporter { onRunStart( config: Config, aggregatedResults: AggregatedResult, - runnerContext: RunnerContext, options: ReporterOnStartOptions, ) { this._status.runStarted(aggregatedResults, options); diff --git a/packages/jest-cli/src/reporters/NotifyReporter.js b/packages/jest-cli/src/reporters/NotifyReporter.js index 686f43bdf0e8..5ea323511ef2 100644 --- a/packages/jest-cli/src/reporters/NotifyReporter.js +++ b/packages/jest-cli/src/reporters/NotifyReporter.js @@ -11,6 +11,7 @@ import type {AggregatedResult} from 'types/TestResult'; import type {Config} from 'types/Config'; +import type {Context} from 'types/Context'; const BaseReporter = require('./BaseReporter'); const notifier = require('node-notifier'); @@ -29,7 +30,11 @@ class NotifyReporter extends BaseReporter { this._startRun = startRun; } - onRunComplete(config: Config, result: AggregatedResult): void { + onRunComplete( + contexts: Set, + config: Config, + result: AggregatedResult, + ): void { const success = result.numFailedTests === 0 && result.numRuntimeErrorTestSuites === 0; diff --git a/packages/jest-cli/src/reporters/SummaryReporter.js b/packages/jest-cli/src/reporters/SummaryReporter.js index 2014bab55d9f..dee64aead607 100644 --- a/packages/jest-cli/src/reporters/SummaryReporter.js +++ b/packages/jest-cli/src/reporters/SummaryReporter.js @@ -11,7 +11,8 @@ import type {AggregatedResult, SnapshotSummary} from 'types/TestResult'; import type {Config} from 'types/Config'; -import type {ReporterOnStartOptions, RunnerContext} from 'types/Reporters'; +import type {Context} from 'types/Context'; +import type {ReporterOnStartOptions} from 'types/Reporters'; const BaseReporter = require('./BaseReporter'); @@ -19,6 +20,10 @@ const {getSummary, pluralize} = require('./utils'); const chalk = require('chalk'); const getResultHeader = require('./getResultHeader'); +type Options = {| + getTestSummary: () => string, +|}; + const ARROW = ' \u203A '; const FAIL_COLOR = chalk.bold.red; const SNAPSHOT_ADDED = chalk.bold.green; @@ -55,11 +60,13 @@ const NPM_EVENTS = new Set([ 'postrestart', ]); -class SummareReporter extends BaseReporter { +class SummaryReporter extends BaseReporter { _estimatedTime: number; + _options: Options; - constructor() { + constructor(options: Options) { super(); + this._options = options; this._estimatedTime = 0; } @@ -77,17 +84,16 @@ class SummareReporter extends BaseReporter { onRunStart( config: Config, aggregatedResults: AggregatedResult, - runnerContext: RunnerContext, options: ReporterOnStartOptions, ) { - super.onRunStart(config, aggregatedResults, runnerContext, options); + super.onRunStart(config, aggregatedResults, options); this._estimatedTime = options.estimatedTime; } onRunComplete( + contexts: Set, config: Config, aggregatedResults: AggregatedResult, - runnerContext: RunnerContext, ) { const {numTotalTestSuites, testResults, wasInterrupted} = aggregatedResults; if (numTotalTestSuites) { @@ -109,7 +115,7 @@ class SummareReporter extends BaseReporter { if (numTotalTestSuites) { const testSummary = wasInterrupted ? chalk.bold.red('Test run was interrupted.') - : runnerContext.getTestSummary(); + : this._options.getTestSummary(); this.log( getSummary(aggregatedResults, { estimatedTime: this._estimatedTime, @@ -229,4 +235,4 @@ class SummareReporter extends BaseReporter { } } -module.exports = SummareReporter; +module.exports = SummaryReporter; diff --git a/packages/jest-cli/src/reporters/__tests__/CoverageReporter-test.js b/packages/jest-cli/src/reporters/__tests__/CoverageReporter-test.js index 2bcd9db1c393..fa5474628a39 100644 --- a/packages/jest-cli/src/reporters/__tests__/CoverageReporter-test.js +++ b/packages/jest-cli/src/reporters/__tests__/CoverageReporter-test.js @@ -83,6 +83,7 @@ describe('onRunComplete', () => { it('getLastError() returns an error when threshold is not met', () => { testReporter.onRunComplete( + new Set(), { collectCoverage: true, coverageThreshold: { @@ -99,6 +100,7 @@ describe('onRunComplete', () => { it('getLastError() returns `undefined` when threshold is met', () => { testReporter.onRunComplete( + new Set(), { collectCoverage: true, coverageThreshold: { diff --git a/types/Reporters.js b/types/Reporters.js index 1523f0500853..d7a4c9866cb8 100644 --- a/types/Reporters.js +++ b/types/Reporters.js @@ -11,11 +11,6 @@ import type {FS} from 'jest-haste-map'; -export type RunnerContext = {| - hasteFS: FS, - getTestSummary: () => string, -|}; - export type ReporterOnStartOptions = {| estimatedTime: number, showStatus: boolean, From 720d37b70fa964afe711b8a64e10af3d6d4fdd1b Mon Sep 17 00:00:00 2001 From: cpojer Date: Sun, 19 Mar 2017 08:23:29 +0900 Subject: [PATCH 04/11] Rename `patternInfo` to `PathPattern` --- packages/jest-cli/src/SearchSource.js | 30 ++++--------- ...thPatternInfo.js => getTestPathPattern.js} | 4 +- packages/jest-cli/src/lib/setState.js | 4 +- packages/jest-cli/src/runJest.js | 43 +++++++++++++++---- 4 files changed, 46 insertions(+), 35 deletions(-) rename packages/jest-cli/src/lib/{getTestPathPatternInfo.js => getTestPathPattern.js} (94%) diff --git a/packages/jest-cli/src/SearchSource.js b/packages/jest-cli/src/SearchSource.js index 3ec85913e0b6..d90516540c04 100644 --- a/packages/jest-cli/src/SearchSource.js +++ b/packages/jest-cli/src/SearchSource.js @@ -45,7 +45,7 @@ type Options = {| lastCommit?: boolean, |}; -export type PatternInfo = {| +export type PathPattern = {| input?: string, findRelatedTests?: boolean, lastCommit?: boolean, @@ -219,31 +219,17 @@ class SearchSource { }); } - getTestPaths(patternInfo: PatternInfo): Promise { - if (patternInfo.onlyChanged) { - return this.findChangedTests({lastCommit: patternInfo.lastCommit}); - } else if (patternInfo.findRelatedTests && patternInfo.paths) { - return Promise.resolve( - this.findRelatedTestsFromPattern(patternInfo.paths), - ); - } else if (patternInfo.testPathPattern != null) { - return Promise.resolve( - this.findMatchingTests(patternInfo.testPathPattern), - ); + getTestPaths(pattern: PathPattern): Promise { + if (pattern.onlyChanged) { + return this.findChangedTests({lastCommit: pattern.lastCommit}); + } else if (pattern.findRelatedTests && pattern.paths) { + return Promise.resolve(this.findRelatedTestsFromPattern(pattern.paths)); + } else if (pattern.testPathPattern != null) { + return Promise.resolve(this.findMatchingTests(pattern.testPathPattern)); } else { return Promise.resolve({paths: []}); } } - - static getTestPathPattern(patternInfo: PatternInfo): string { - const pattern = patternInfo.testPathPattern; - const input = patternInfo.input; - const formattedPattern = `/${pattern || ''}/`; - const formattedInput = patternInfo.shouldTreatInputAsPattern - ? `/${input || ''}/` - : `"${input || ''}"`; - return input === pattern ? formattedInput : formattedPattern; - } } module.exports = SearchSource; diff --git a/packages/jest-cli/src/lib/getTestPathPatternInfo.js b/packages/jest-cli/src/lib/getTestPathPattern.js similarity index 94% rename from packages/jest-cli/src/lib/getTestPathPatternInfo.js rename to packages/jest-cli/src/lib/getTestPathPattern.js index d8a677660026..b53385ee1ab7 100644 --- a/packages/jest-cli/src/lib/getTestPathPatternInfo.js +++ b/packages/jest-cli/src/lib/getTestPathPattern.js @@ -9,7 +9,7 @@ */ 'use strict'; -import type {PatternInfo} from '../SearchSource'; +import type {PathPattern} from '../SearchSource'; const {clearLine} = require('jest-util'); const chalk = require('chalk'); @@ -33,7 +33,7 @@ const showTestPathPatternError = (testPathPattern: string) => { ); }; -module.exports = (argv: Object): PatternInfo => { +module.exports = (argv: Object): PathPattern => { if (argv.onlyChanged) { return { input: '', diff --git a/packages/jest-cli/src/lib/setState.js b/packages/jest-cli/src/lib/setState.js index b2621a5e0bb9..8ec14ba30bfb 100644 --- a/packages/jest-cli/src/lib/setState.js +++ b/packages/jest-cli/src/lib/setState.js @@ -9,7 +9,7 @@ */ 'use strict'; -const getTestPathPatternInfo = require('./getTestPathPatternInfo'); +const getTestPathPattern = require('./getTestPathPattern'); module.exports = (argv: Object, mode: 'watch' | 'watchAll', options?: {}) => { options = options || {}; @@ -36,7 +36,7 @@ module.exports = (argv: Object, mode: 'watch' | 'watchAll', options?: {}) => { } argv.onlyChanged = false; - argv.onlyChanged = getTestPathPatternInfo(argv).input === '' && + argv.onlyChanged = getTestPathPattern(argv).input === '' && !argv.watchAll && !argv.testNamePattern; diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index bcd4261225a1..7802b99abb57 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -11,7 +11,7 @@ import type {Context} from 'types/Context'; import type {Test} from 'types/TestRunner'; -import type {PatternInfo} from './SearchSource'; +import type {PathPattern} from './SearchSource'; import type TestWatcher from './TestWatcher'; const fs = require('graceful-fs'); @@ -20,26 +20,45 @@ const SearchSource = require('./SearchSource'); const TestRunner = require('./TestRunner'); const TestSequencer = require('./TestSequencer'); -const getTestPathPatternInfo = require('./lib/getTestPathPatternInfo'); +const getTestPathPattern = require('./lib/getTestPathPattern'); const chalk = require('chalk'); const {Console, formatTestResults} = require('jest-util'); const getMaxWorkers = require('./lib/getMaxWorkers'); const path = require('path'); const setState = require('./lib/setState'); -const getTestSummary = (argv: Object, pattern: PatternInfo) => { - const testPathPattern = SearchSource.getTestPathPattern(pattern); +const formatTestPathPattern = pattern => { + const testPattern = pattern.testPathPattern; + const input = pattern.input; + const formattedPattern = `/${testPattern || ''}/`; + const formattedInput = pattern.shouldTreatInputAsPattern + ? `/${input || ''}/` + : `"${input || ''}"`; + return input === testPattern ? formattedInput : formattedPattern; +}; + +const getTestSummary = ( + contexts: Array, + pattern: PathPattern, + testPathPattern: string, + testNamePattern: string, +) => { const testInfo = pattern.onlyChanged ? chalk.dim(' related to changed files') : pattern.input !== '' ? chalk.dim(' matching ') + testPathPattern : ''; - const nameInfo = argv.testNamePattern - ? chalk.dim(' with tests matching ') + `"${argv.testNamePattern}"` + const nameInfo = testNamePattern + ? chalk.dim(' with tests matching ') + `"${testNamePattern}"` + : ''; + + const contextInfo = contexts.length > 1 + ? chalk.dim(' in ') + contexts.length + chalk.dim(' projects') : ''; return chalk.dim('Ran all test suites') + testInfo + nameInfo + + contextInfo + chalk.dim('.'); }; @@ -57,7 +76,7 @@ const getNoTestsFoundMessage = (testRunData, pattern) => { const pluralize = (word: string, count: number, ending: string) => `${count} ${word}${count === 1 ? '' : ending}`; - const testPathPattern = SearchSource.getTestPathPattern(pattern); + const testPathPattern = formatTestPathPattern(pattern); const individualResults = testRunData.map(testRun => { const stats = testRun.matches.stats || {}; const config = testRun.context.config; @@ -104,7 +123,7 @@ const getTestPaths = async (context, pattern, argv, pipe) => { setState(argv, 'watchAll', { noSCM: true, }); - pattern = getTestPathPatternInfo(argv); + pattern = getTestPathPattern(argv); data = await source.getTestPaths(pattern); } else { localConsole.log( @@ -198,7 +217,13 @@ const runJest = async ( const results = await new TestRunner( context.config, { - getTestSummary: () => getTestSummary(argv, pattern), + getTestSummary: () => + getTestSummary( + contexts, + pattern, + formatTestPathPattern(pattern), + argv.testNamePattern, + ), maxWorkers, }, startRun, From a0f25878d082e1958bfab7a639146879e70949e4 Mon Sep 17 00:00:00 2001 From: cpojer Date: Wed, 12 Apr 2017 09:12:07 +0100 Subject: [PATCH 05/11] Remove `hasDeprecationWarnings` from `watch` function, move it up one level. --- packages/jest-cli/src/TestRunner.js | 8 ++-- .../watch-filename-pattern-mode-test.js | 6 +-- .../watch-test-name-pattern-mode-test.js | 6 +-- packages/jest-cli/src/__tests__/watch-test.js | 16 +++---- packages/jest-cli/src/cli/runCLI.js | 24 +++++----- .../src/lib/handleDeprecationWarnings.js | 46 +++++++++++++++++++ packages/jest-cli/src/watch.js | 42 ----------------- 7 files changed, 75 insertions(+), 73 deletions(-) create mode 100644 packages/jest-cli/src/lib/handleDeprecationWarnings.js diff --git a/packages/jest-cli/src/TestRunner.js b/packages/jest-cli/src/TestRunner.js index 64de7366ca93..6a856c00a854 100644 --- a/packages/jest-cli/src/TestRunner.js +++ b/packages/jest-cli/src/TestRunner.js @@ -284,9 +284,11 @@ class TestRunner { this.addReporter(new CoverageReporter()); } - this.addReporter(new SummaryReporter({ - getTestSummary: this._options.getTestSummary, - })); + this.addReporter( + new SummaryReporter({ + getTestSummary: this._options.getTestSummary, + }), + ); if (config.notify) { this.addReporter(new NotifyReporter(this._startRun)); } diff --git a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js index f4729f5defdf..db6c9c8f9c7e 100644 --- a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js @@ -84,7 +84,6 @@ describe('Watch mode flows', () => { let argv; let context; let config; - let hasDeprecationWarnings; let stdin; beforeEach(() => { @@ -94,13 +93,12 @@ describe('Watch mode flows', () => { argv = {}; context = {}; config = {}; - hasDeprecationWarnings = false; stdin = new MockStdin(); }); it('Pressing "P" enters pattern mode', () => { config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); // Write a enter pattern mode stdin.emit(KEYS.P); @@ -137,7 +135,7 @@ describe('Watch mode flows', () => { it('Results in pattern mode get truncated appropriately', () => { config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); stdin.emit(KEYS.P); diff --git a/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js index 2b85748c2b76..de9c8f7931c0 100644 --- a/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js @@ -108,7 +108,6 @@ describe('Watch mode flows', () => { let argv; let context; let config; - let hasDeprecationWarnings; let stdin; beforeEach(() => { @@ -118,13 +117,12 @@ describe('Watch mode flows', () => { argv = {}; context = {}; config = {}; - hasDeprecationWarnings = false; stdin = new MockStdin(); }); it('Pressing "T" enters pattern mode', () => { config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); // Write a enter pattern mode stdin.emit(KEYS.T); @@ -161,7 +159,7 @@ describe('Watch mode flows', () => { it('Results in pattern mode get truncated appropriately', () => { config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); stdin.emit(KEYS.T); diff --git a/packages/jest-cli/src/__tests__/watch-test.js b/packages/jest-cli/src/__tests__/watch-test.js index 3390a985abcd..35f2d7086b9e 100644 --- a/packages/jest-cli/src/__tests__/watch-test.js +++ b/packages/jest-cli/src/__tests__/watch-test.js @@ -41,7 +41,6 @@ describe('Watch mode flows', () => { let argv; let context; let config; - let hasDeprecationWarnings; let stdin; beforeEach(() => { @@ -50,7 +49,6 @@ describe('Watch mode flows', () => { argv = {}; context = {}; config = {roots: [], testPathIgnorePatterns: [], testRegex: ''}; - hasDeprecationWarnings = false; stdin = new MockStdin(); }); @@ -58,7 +56,7 @@ describe('Watch mode flows', () => { argv.testPathPattern = 'test-*'; config.testPathPattern = 'test-*'; - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); expect(runJestMock).toBeCalledWith( [context], @@ -74,7 +72,7 @@ describe('Watch mode flows', () => { argv.testNamePattern = 'test-*'; config.testNamePattern = 'test-*'; - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); expect(runJestMock).toBeCalledWith( [context], @@ -87,7 +85,7 @@ describe('Watch mode flows', () => { }); it('Runs Jest once by default and shows usage', () => { - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); expect(runJestMock).toBeCalledWith( [context], argv, @@ -100,7 +98,7 @@ describe('Watch mode flows', () => { }); it('Pressing "o" runs test in "only changed files" mode', () => { - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); runJestMock.mockReset(); stdin.emit(KEYS.O); @@ -114,7 +112,7 @@ describe('Watch mode flows', () => { }); it('Pressing "a" runs test in "watch all" mode', () => { - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); runJestMock.mockReset(); stdin.emit(KEYS.A); @@ -128,14 +126,14 @@ describe('Watch mode flows', () => { }); it('Pressing "ENTER" reruns the tests', () => { - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); expect(runJestMock).toHaveBeenCalledTimes(1); stdin.emit(KEYS.ENTER); expect(runJestMock).toHaveBeenCalledTimes(2); }); it('Pressing "u" reruns the tests in "update snapshot" mode', () => { - watch(config, pipe, argv, hasteMap, context, hasDeprecationWarnings, stdin); + watch(config, pipe, argv, hasteMap, context, stdin); runJestMock.mockReset(); stdin.emit(KEYS.U); diff --git a/packages/jest-cli/src/cli/runCLI.js b/packages/jest-cli/src/cli/runCLI.js index 1680be085d7a..6d50bbc134af 100644 --- a/packages/jest-cli/src/cli/runCLI.js +++ b/packages/jest-cli/src/cli/runCLI.js @@ -14,11 +14,12 @@ import type {Config, Path} from 'types/Config'; const Runtime = require('jest-runtime'); -const chalk = require('chalk'); const {Console, clearLine} = require('jest-util'); const {createDirectory} = require('jest-util'); +const chalk = require('chalk'); const createContext = require('../lib/createContext'); const getMaxWorkers = require('../lib/getMaxWorkers'); +const handleDeprecationWarnings = require('../lib/handleDeprecationWarnings'); const logDebugMessages = require('../lib/logDebugMessages'); const preRunMessage = require('../preRunMessage'); const readConfig = require('jest-config').readConfig; @@ -56,7 +57,7 @@ module.exports = async ( } if (argv.watch || argv.watchAll) { - const {config} = configs[0]; + const {config, hasDeprecationWarnings} = configs[0]; createDirectory(config.cacheDirectory); const hasteMapInstance = Runtime.createHasteMap(config, { console: new Console(pipe, pipe), @@ -67,15 +68,16 @@ module.exports = async ( const hasteMap = await hasteMapInstance.build(); const context = createContext(config, hasteMap); - return watch( - config, - pipe, - argv, - hasteMapInstance, - context, - // TODO - configs[0].hasDeprecationWarnings, - ); + if (hasDeprecationWarnings) { + try { + await handleDeprecationWarnings(pipe, process.stdin); + return watch(config, pipe, argv, hasteMapInstance, context); + } catch (e) { + process.exit(0); + } + } + + return watch(config, pipe, argv, hasteMapInstance, context); } else { const contexts = await Promise.all( configs.map(async ({config}) => { diff --git a/packages/jest-cli/src/lib/handleDeprecationWarnings.js b/packages/jest-cli/src/lib/handleDeprecationWarnings.js new file mode 100644 index 000000000000..5b3b9191e38f --- /dev/null +++ b/packages/jest-cli/src/lib/handleDeprecationWarnings.js @@ -0,0 +1,46 @@ +/** + * 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 + */ +'use strict'; + +const chalk = require('chalk'); +const {KEYS} = require('../constants'); + +module.exports = ( + pipe: stream$Writable | tty$WriteStream, + stdin: stream$Readable | tty$ReadStream = process.stdin, +) => { + return new Promise((resolve, reject) => { + if (typeof stdin.setRawMode === 'function') { + const messages = [ + chalk.red('There are deprecation warnings.\n'), + chalk.dim(' \u203A Press ') + 'Enter' + chalk.dim(' to continue.'), + chalk.dim(' \u203A Press ') + 'Esc' + chalk.dim(' to exit.'), + ]; + + pipe.write(messages.join('\n')); + + // $FlowFixMe + stdin.setRawMode(true); + stdin.resume(); + stdin.setEncoding('hex'); + stdin.on('data', (key: string) => { + if (key === KEYS.ENTER) { + resolve(); + } else if ( + [KEYS.ESCAPE, KEYS.CONTROL_C, KEYS.CONTROL_D].indexOf(key) !== -1 + ) { + reject(); + } + }); + } else { + resolve(); + } + }); +}; diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index e97e70b46f17..19151c9e117e 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -34,17 +34,8 @@ const watch = ( argv: Object, hasteMap: HasteMap, context: Context, - hasDeprecationWarnings?: boolean, stdin?: stream$Readable | tty$ReadStream = process.stdin, ) => { - if (hasDeprecationWarnings) { - return handleDeprecatedWarnings(pipe, stdin) - .then(() => { - watch(config, pipe, argv, hasteMap, context); - }) - .catch(() => process.exit(0)); - } - setState(argv, argv.watch ? 'watch' : 'watchAll', { testNamePattern: argv.testNamePattern, testPathPattern: argv.testPathPattern || @@ -227,39 +218,6 @@ const watch = ( return Promise.resolve(); }; -const handleDeprecatedWarnings = ( - pipe: stream$Writable | tty$WriteStream, - stdin: stream$Readable | tty$ReadStream = process.stdin, -) => { - return new Promise((resolve, reject) => { - if (typeof stdin.setRawMode === 'function') { - const messages = [ - chalk.red('There are deprecation warnings.\n'), - chalk.dim(' \u203A Press ') + 'Enter' + chalk.dim(' to continue.'), - chalk.dim(' \u203A Press ') + 'Esc' + chalk.dim(' to exit.'), - ]; - - pipe.write(messages.join('\n')); - - // $FlowFixMe - stdin.setRawMode(true); - stdin.resume(); - stdin.setEncoding('hex'); - stdin.on('data', (key: string) => { - if (key === KEYS.ENTER) { - resolve(); - } else if ( - [KEYS.ESCAPE, KEYS.CONTROL_C, KEYS.CONTROL_D].indexOf(key) !== -1 - ) { - reject(); - } - }); - } else { - resolve(); - } - }); -}; - const usage = (argv, snapshotFailure, delimiter = '\n') => { /* eslint-disable max-len */ const messages = [ From 2977a2eb21a9b10061fea821109b47d6f44b6327 Mon Sep 17 00:00:00 2001 From: cpojer Date: Wed, 12 Apr 2017 10:55:34 +0100 Subject: [PATCH 06/11] Make watch mode work with multiple projects. --- packages/jest-cli/src/SearchSource.js | 20 +--- .../jest-cli/src/TestPathPatternPrompt.js | 49 ++++++---- .../src/__tests__/SearchSource-test.js | 23 +++-- .../watch-filename-pattern-mode-test.js | 18 ++-- .../watch-test-name-pattern-mode-test.js | 18 ++-- packages/jest-cli/src/__tests__/watch-test.js | 35 ++++--- packages/jest-cli/src/cli/runCLI.js | 47 ++++----- packages/jest-cli/src/runJest.js | 2 +- packages/jest-cli/src/watch.js | 95 ++++++++++++------- types/TestRunner.js | 4 +- 10 files changed, 157 insertions(+), 154 deletions(-) diff --git a/packages/jest-cli/src/SearchSource.js b/packages/jest-cli/src/SearchSource.js index d90516540c04..7b29edf9067e 100644 --- a/packages/jest-cli/src/SearchSource.js +++ b/packages/jest-cli/src/SearchSource.js @@ -25,13 +25,6 @@ const { replacePathSepForRegex, } = require('jest-regex-util'); -type SearchSourceConfig = { - roots: Array, - testMatch: Array, - testRegex: string, - testPathIgnorePatterns: Array, -}; - type SearchResult = {| noSCM?: boolean, paths: Array, @@ -83,7 +76,6 @@ const regexToMatcher = (testRegex: string) => { class SearchSource { _context: Context; - _config: SearchSourceConfig; _options: ResolveModuleConfig; _rootPattern: RegExp; _testIgnorePattern: ?RegExp; @@ -94,13 +86,9 @@ class SearchSource { testPathIgnorePatterns: (path: Path) => boolean, }; - constructor( - context: Context, - config: SearchSourceConfig, - options?: ResolveModuleConfig, - ) { + constructor(context: Context, options?: ResolveModuleConfig) { + const {config} = context; this._context = context; - this._config = config; this._options = options || { skipNodeResolution: false, }; @@ -199,7 +187,9 @@ class SearchSource { } findChangedTests(options: Options): Promise { - return Promise.all(this._config.roots.map(determineSCM)).then(repos => { + return Promise.all( + this._context.config.roots.map(determineSCM), + ).then(repos => { if (!repos.every(([gitRepo, hgRepo]) => gitRepo || hgRepo)) { return { noSCM: true, diff --git a/packages/jest-cli/src/TestPathPatternPrompt.js b/packages/jest-cli/src/TestPathPatternPrompt.js index 86531ba9d805..9117cc6dd4f8 100644 --- a/packages/jest-cli/src/TestPathPatternPrompt.js +++ b/packages/jest-cli/src/TestPathPatternPrompt.js @@ -11,7 +11,8 @@ 'use strict'; import type {Context} from 'types/Context'; -import type {Config, Path} from 'types/Config'; +import type {Test} from 'types/TestRunner'; +import type SearchSource from './SearchSource'; const ansiEscapes = require('ansi-escapes'); const chalk = require('chalk'); @@ -19,9 +20,13 @@ const {getTerminalWidth} = require('./lib/terminalUtils'); const highlight = require('./lib/highlight'); const stringLength = require('string-length'); const {trimAndFormatPath} = require('./reporters/utils'); -const SearchSource = require('./SearchSource'); const Prompt = require('./lib/Prompt'); +type SearchSources = Array<{| + context: Context, + searchSource: SearchSource, +|}>; + const pluralizeFile = (total: number) => total === 1 ? 'file' : 'files'; const usage = () => @@ -34,17 +39,11 @@ const usage = () => const usageRows = usage().split('\n').length; module.exports = class TestPathPatternPrompt { - _config: Config; _pipe: stream$Writable | tty$WriteStream; _prompt: Prompt; - _searchSource: SearchSource; - - constructor( - config: Config, - pipe: stream$Writable | tty$WriteStream, - prompt: Prompt, - ) { - this._config = config; + _searchSources: SearchSources; + + constructor(pipe: stream$Writable | tty$WriteStream, prompt: Prompt) { this._pipe = pipe; this._prompt = prompt; } @@ -65,16 +64,24 @@ module.exports = class TestPathPatternPrompt { regex = new RegExp(pattern, 'i'); } catch (e) {} - const paths = regex - ? this._searchSource.findMatchingTests(pattern).paths - : []; + let paths = []; + if (regex) { + this._searchSources.forEach(({searchSource, context}) => { + paths = paths.concat( + searchSource.findMatchingTests(pattern).paths.map(path => ({ + context, + path, + })), + ); + }); + } this._pipe.write(ansiEscapes.eraseLine); this._pipe.write(ansiEscapes.cursorLeft); this._printTypeahead(pattern, paths, 10); } - _printTypeahead(pattern: string, allResults: Array, max: number) { + _printTypeahead(pattern: string, allResults: Array, max: number) { const total = allResults.length; const results = allResults.slice(0, max); const inputText = `${chalk.dim(' pattern \u203A')} ${pattern}`; @@ -97,14 +104,14 @@ module.exports = class TestPathPatternPrompt { const padding = stringLength(prefix) + 2; results - .map(rawPath => { + .map(({path, context}) => { const filePath = trimAndFormatPath( padding, - this._config, - rawPath, + context.config, + path, width, ); - return highlight(rawPath, filePath, pattern, this._config.rootDir); + return highlight(path, filePath, pattern, context.config.rootDir); }) .forEach(filePath => this._pipe.write(`\n ${chalk.dim('\u203A')} ${filePath}`)); @@ -129,7 +136,7 @@ module.exports = class TestPathPatternPrompt { this._pipe.write(ansiEscapes.cursorRestorePosition); } - updateSearchSource(context: Context) { - this._searchSource = new SearchSource(context, this._config); + updateSearchSources(searchSources: SearchSources) { + this._searchSources = searchSources; } }; diff --git a/packages/jest-cli/src/__tests__/SearchSource-test.js b/packages/jest-cli/src/__tests__/SearchSource-test.js index 5b8a49f10f20..5db27c34914e 100644 --- a/packages/jest-cli/src/__tests__/SearchSource-test.js +++ b/packages/jest-cli/src/__tests__/SearchSource-test.js @@ -45,15 +45,14 @@ describe('SearchSource', () => { rootDir: '.', roots: [], }).config; - return Runtime.createContext(config, {maxWorkers}).then(hasteMap => { - searchSource = new SearchSource(hasteMap, config); + return Runtime.createContext(config, {maxWorkers}).then(context => { + searchSource = new SearchSource(context); }); }); // micromatch doesn't support '..' through the globstar ('**') to avoid // infinite recursion. - - it('supports ../ paths and unix separators via textRegex', () => { + it('supports ../ paths and unix separators via testRegex', () => { if (process.platform !== 'win32') { config = normalizeConfig({ name, @@ -64,8 +63,8 @@ describe('SearchSource', () => { }).config; return Runtime.createContext(config, { maxWorkers, - }).then(hasteMap => { - searchSource = new SearchSource(hasteMap, config); + }).then(context => { + searchSource = new SearchSource(context); const path = '/path/to/__tests__/foo/bar/baz/../../../test.js'; expect(searchSource.isTestFilePath(path)).toEqual(true); @@ -95,8 +94,8 @@ describe('SearchSource', () => { findMatchingTests = config => Runtime.createContext(config, { maxWorkers, - }).then(hasteMap => - new SearchSource(hasteMap, config).findMatchingTests()); + }).then(context => + new SearchSource(context).findMatchingTests()); }); it('finds tests matching a pattern via testRegex', () => { @@ -309,8 +308,8 @@ describe('SearchSource', () => { name: 'SearchSource-findRelatedTests-tests', rootDir, }); - Runtime.createContext(config, {maxWorkers}).then(hasteMap => { - searchSource = new SearchSource(hasteMap, config); + Runtime.createContext(config, {maxWorkers}).then(context => { + searchSource = new SearchSource(context); done(); }); }); @@ -342,8 +341,8 @@ describe('SearchSource', () => { rootDir, testMatch, }); - Runtime.createContext(config, {maxWorkers}).then(hasteMap => { - searchSource = new SearchSource(hasteMap, config); + Runtime.createContext(config, {maxWorkers}).then(context => { + searchSource = new SearchSource(context); done(); }); }); diff --git a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js index db6c9c8f9c7e..ed287a71f245 100644 --- a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js @@ -80,25 +80,23 @@ afterEach(runJestMock.mockReset); describe('Watch mode flows', () => { let pipe; - let hasteMap; + let hasteMapInstances; let argv; - let context; - let config; + let contexts; let stdin; beforeEach(() => { terminalWidth = 80; pipe = {write: jest.fn()}; - hasteMap = {on: () => {}}; + hasteMapInstances = [{on: () => {}}]; argv = {}; - context = {}; - config = {}; + contexts = [{config: {}}]; stdin = new MockStdin(); }); it('Pressing "P" enters pattern mode', () => { - config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, stdin); + contexts[0].config = {rootDir: ''}; + watch(contexts, argv, pipe, hasteMapInstances, stdin); // Write a enter pattern mode stdin.emit(KEYS.P); @@ -134,8 +132,8 @@ describe('Watch mode flows', () => { }); it('Results in pattern mode get truncated appropriately', () => { - config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, stdin); + contexts[0].config = {rootDir: ''}; + watch(contexts, argv, pipe, hasteMapInstances, stdin); stdin.emit(KEYS.P); diff --git a/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js index de9c8f7931c0..82a9d1d9d0dc 100644 --- a/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-test-name-pattern-mode-test.js @@ -104,25 +104,23 @@ afterEach(runJestMock.mockReset); describe('Watch mode flows', () => { let pipe; - let hasteMap; + let hasteMapInstances; let argv; - let context; - let config; + let contexts; let stdin; beforeEach(() => { terminalWidth = 80; pipe = {write: jest.fn()}; - hasteMap = {on: () => {}}; + hasteMapInstances = [{on: () => {}}]; argv = {}; - context = {}; - config = {}; + contexts = [{config: {}}]; stdin = new MockStdin(); }); it('Pressing "T" enters pattern mode', () => { - config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, stdin); + contexts[0].config = {rootDir: ''}; + watch(contexts, argv, pipe, hasteMapInstances, stdin); // Write a enter pattern mode stdin.emit(KEYS.T); @@ -158,8 +156,8 @@ describe('Watch mode flows', () => { }); it('Results in pattern mode get truncated appropriately', () => { - config = {rootDir: ''}; - watch(config, pipe, argv, hasteMap, context, stdin); + contexts[0].config = {rootDir: ''}; + watch(contexts, argv, pipe, hasteMapInstances, stdin); stdin.emit(KEYS.T); diff --git a/packages/jest-cli/src/__tests__/watch-test.js b/packages/jest-cli/src/__tests__/watch-test.js index 35f2d7086b9e..16b1e83deed6 100644 --- a/packages/jest-cli/src/__tests__/watch-test.js +++ b/packages/jest-cli/src/__tests__/watch-test.js @@ -37,29 +37,28 @@ afterEach(runJestMock.mockReset); describe('Watch mode flows', () => { let pipe; - let hasteMap; + let hasteMapInstances; let argv; - let context; - let config; + let contexts; let stdin; beforeEach(() => { + const config = {roots: [], testPathIgnorePatterns: [], testRegex: ''}; pipe = {write: jest.fn()}; - hasteMap = {on: () => {}}; + hasteMapInstances = [{on: () => {}}]; argv = {}; - context = {}; - config = {roots: [], testPathIgnorePatterns: [], testRegex: ''}; + contexts = [{config}]; stdin = new MockStdin(); }); it('Correctly passing test path pattern', () => { argv.testPathPattern = 'test-*'; - config.testPathPattern = 'test-*'; + contexts[0].config.testPathPattern = 'test-*'; - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); expect(runJestMock).toBeCalledWith( - [context], + contexts, argv, pipe, new TestWatcher({isWatchMode: true}), @@ -70,12 +69,12 @@ describe('Watch mode flows', () => { it('Correctly passing test name pattern', () => { argv.testNamePattern = 'test-*'; - config.testNamePattern = 'test-*'; + contexts[0].config.testNamePattern = 'test-*'; - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); expect(runJestMock).toBeCalledWith( - [context], + contexts, argv, pipe, new TestWatcher({isWatchMode: true}), @@ -85,9 +84,9 @@ describe('Watch mode flows', () => { }); it('Runs Jest once by default and shows usage', () => { - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); expect(runJestMock).toBeCalledWith( - [context], + contexts, argv, pipe, new TestWatcher({isWatchMode: true}), @@ -98,7 +97,7 @@ describe('Watch mode flows', () => { }); it('Pressing "o" runs test in "only changed files" mode', () => { - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); runJestMock.mockReset(); stdin.emit(KEYS.O); @@ -112,7 +111,7 @@ describe('Watch mode flows', () => { }); it('Pressing "a" runs test in "watch all" mode', () => { - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); runJestMock.mockReset(); stdin.emit(KEYS.A); @@ -126,14 +125,14 @@ describe('Watch mode flows', () => { }); it('Pressing "ENTER" reruns the tests', () => { - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); expect(runJestMock).toHaveBeenCalledTimes(1); stdin.emit(KEYS.ENTER); expect(runJestMock).toHaveBeenCalledTimes(2); }); it('Pressing "u" reruns the tests in "update snapshot" mode', () => { - watch(config, pipe, argv, hasteMap, context, stdin); + watch(contexts, argv, pipe, hasteMapInstances, stdin); runJestMock.mockReset(); stdin.emit(KEYS.U); diff --git a/packages/jest-cli/src/cli/runCLI.js b/packages/jest-cli/src/cli/runCLI.js index 6d50bbc134af..d2f8c28cbe8d 100644 --- a/packages/jest-cli/src/cli/runCLI.js +++ b/packages/jest-cli/src/cli/runCLI.js @@ -56,44 +56,33 @@ module.exports = async ( process.exit(0); } - if (argv.watch || argv.watchAll) { - const {config, hasDeprecationWarnings} = configs[0]; - createDirectory(config.cacheDirectory); - const hasteMapInstance = Runtime.createHasteMap(config, { - console: new Console(pipe, pipe), - maxWorkers: getMaxWorkers(argv), - resetCache: !config.cache, - watch: config.watch, - }); + const hasteMapInstances = Array(configs.length); + const contexts = await Promise.all( + configs.map(async ({config}, index) => { + createDirectory(config.cacheDirectory); + const hasteMapInstance = Runtime.createHasteMap(config, { + console: new Console(pipe, pipe), + maxWorkers: getMaxWorkers(argv), + resetCache: !config.cache, + watch: config.watch, + }); + hasteMapInstances[index] = hasteMapInstance; + return createContext(config, await hasteMapInstance.build()); + }), + ); - const hasteMap = await hasteMapInstance.build(); - const context = createContext(config, hasteMap); - if (hasDeprecationWarnings) { + if (argv.watch || argv.watchAll) { + if (configs.some(({hasDeprecationWarnings}) => hasDeprecationWarnings)) { try { await handleDeprecationWarnings(pipe, process.stdin); - return watch(config, pipe, argv, hasteMapInstance, context); + return watch(contexts, argv, pipe, hasteMapInstances); } catch (e) { process.exit(0); } } - return watch(config, pipe, argv, hasteMapInstance, context); + return watch(contexts, argv, pipe, hasteMapInstances); } else { - const contexts = await Promise.all( - configs.map(async ({config}) => { - createDirectory(config.cacheDirectory); - return createContext( - config, - await Runtime.createHasteMap(config, { - console: new Console(pipe, pipe), - maxWorkers: getMaxWorkers(argv), - resetCache: !config.cache, - watch: config.watch, - }).build(), - ); - }), - ); - const startRun = () => { preRunMessage.print(pipe); runJest( diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index 7802b99abb57..e33895c02f00 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -113,7 +113,7 @@ const getNoTestsFoundMessage = (testRunData, pattern) => { }; const getTestPaths = async (context, pattern, argv, pipe) => { - const source = new SearchSource(context, context.config); + const source = new SearchSource(context); let data = await source.getTestPaths(pattern); if (!data.paths.length) { const localConsole = new Console(pipe, pipe); diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index 19151c9e117e..7606e7bdc72d 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -10,7 +10,6 @@ 'use strict'; import type {Context} from 'types/Context'; -import type {Config} from 'types/Config'; const ansiEscapes = require('ansi-escapes'); const chalk = require('chalk'); @@ -22,18 +21,20 @@ const isValidPath = require('./lib/isValidPath'); const preRunMessage = require('./preRunMessage'); const runJest = require('./runJest'); const setState = require('./lib/setState'); +const SearchSource = require('./SearchSource'); const TestWatcher = require('./TestWatcher'); const Prompt = require('./lib/Prompt'); const TestPathPatternPrompt = require('./TestPathPatternPrompt'); const TestNamePatternPrompt = require('./TestNamePatternPrompt'); const {KEYS, CLEAR} = require('./constants'); +let hasExitListener = false; + const watch = ( - config: Config, - pipe: stream$Writable | tty$WriteStream, + contexts: Array, argv: Object, - hasteMap: HasteMap, - context: Context, + pipe: stream$Writable | tty$WriteStream, + hasteMapInstances: Array, stdin?: stream$Readable | tty$ReadStream = process.stdin, ) => { setState(argv, argv.watch ? 'watch' : 'watchAll', { @@ -43,35 +44,55 @@ const watch = ( }); const prompt = new Prompt(); - const testPathPatternPrompt = new TestPathPatternPrompt(config, pipe, prompt); + const testPathPatternPrompt = new TestPathPatternPrompt(pipe, prompt); const testNamePatternPrompt = new TestNamePatternPrompt(pipe, prompt); + let searchSources = contexts.map(context => ({ + context, + searchSource: new SearchSource(context), + })); let hasSnapshotFailure = false; let isRunning = false; let testWatcher; let shouldDisplayWatchUsage = true; let isWatchUsageDisplayed = false; - testPathPatternPrompt.updateSearchSource(context); + testPathPatternPrompt.updateSearchSources(searchSources); - hasteMap.on('change', ({eventsQueue, hasteFS, moduleMap}) => { - const validPaths = eventsQueue.filter(({filePath}) => { - return isValidPath(config, filePath); - }); + hasteMapInstances.forEach((hasteMapInstance, index) => { + hasteMapInstance.on('change', ({eventsQueue, hasteFS, moduleMap}) => { + const validPaths = eventsQueue.filter(({filePath}) => { + return isValidPath(contexts[index].config, filePath); + }); - if (validPaths.length) { - context = createContext(config, {hasteFS, moduleMap}); - prompt.abort(); - testPathPatternPrompt.updateSearchSource(context); - startRun(); - } + if (validPaths.length) { + const context = (contexts[index] = createContext( + contexts[index].config, + { + hasteFS, + moduleMap, + }, + )); + prompt.abort(); + searchSources = searchSources.slice(); + searchSources[index] = { + context, + searchSource: new SearchSource(context), + }; + testPathPatternPrompt.updateSearchSources(searchSources); + startRun(); + } + }); }); - process.on('exit', () => { - if (prompt.isEntering()) { - pipe.write(ansiEscapes.cursorDown()); - pipe.write(ansiEscapes.eraseDown); - } - }); + if (!hasExitListener) { + hasExitListener = true; + process.on('exit', () => { + if (prompt.isEntering()) { + pipe.write(ansiEscapes.cursorDown()); + pipe.write(ansiEscapes.eraseDown); + } + }); + } const startRun = (overrideConfig: Object = {}) => { if (isRunning) { @@ -82,19 +103,21 @@ const watch = ( isInteractive && pipe.write(CLEAR); preRunMessage.print(pipe); isRunning = true; - // $FlowFixMe - context.config = Object.freeze( + contexts.forEach(context => { // $FlowFixMe - Object.assign( - { - testNamePattern: argv.testNamePattern, - testPathPattern: argv.testPathPattern, - }, - config, - overrideConfig, - ), - ); - return runJest([context], argv, pipe, testWatcher, startRun, results => { + context.config = Object.freeze( + // $FlowFixMe + Object.assign( + { + testNamePattern: argv.testNamePattern, + testPathPattern: argv.testPathPattern, + }, + context.config, + overrideConfig, + ), + ); + }); + return runJest(contexts, argv, pipe, testWatcher, startRun, results => { isRunning = false; hasSnapshotFailure = !!results.snapshot.failure; // Create a new testWatcher instance so that re-runs won't be blocked. @@ -112,7 +135,7 @@ const watch = ( } testNamePatternPrompt.updateCachedTestResults(results.testResults); - }).then(() => {}, error => console.error(chalk.red(error.stack))); + }).catch(error => console.error(chalk.red(error.stack))); }; const onKeypress = (key: string) => { diff --git a/types/TestRunner.js b/types/TestRunner.js index 04fbd9554dc3..469bf1fc8005 100644 --- a/types/TestRunner.js +++ b/types/TestRunner.js @@ -12,10 +12,10 @@ import type {Context} from './Context'; import type {Path} from './Config'; -export type Test = { +export type Test = {| context: Context, path: Path, duration?: number, -}; +|}; export type Tests = Array; From d44e8b5a74bd682c0759930bdade87e17847a757 Mon Sep 17 00:00:00 2001 From: cpojer Date: Sun, 16 Apr 2017 19:12:03 +0100 Subject: [PATCH 07/11] Refactor runJest and Reporters, show proper relative paths. --- packages/jest-cli/src/TestRunner.js | 20 ++- .../src/__tests__/SearchSource-test.js | 3 +- .../jest-cli/src/reporters/DefaultReporter.js | 3 - .../jest-cli/src/reporters/SummaryReporter.js | 42 +++++- packages/jest-cli/src/runJest.js | 128 ++++++++---------- 5 files changed, 98 insertions(+), 98 deletions(-) diff --git a/packages/jest-cli/src/TestRunner.js b/packages/jest-cli/src/TestRunner.js index 6a856c00a854..eca2bbf65669 100644 --- a/packages/jest-cli/src/TestRunner.js +++ b/packages/jest-cli/src/TestRunner.js @@ -16,6 +16,7 @@ import type { } from 'types/TestResult'; import type {Config} from 'types/Config'; import type {Context} from 'types/Context'; +import type {PathPattern} from './SearchSource'; import type {Test, Tests} from 'types/TestRunner'; import type BaseReporter from './reporters/BaseReporter'; @@ -41,9 +42,12 @@ class CancelRun extends Error { } } -type Options = {| +export type Options = {| maxWorkers: number, - getTestSummary: () => string, + pattern: PathPattern, + startRun: () => *, + testNamePattern: string, + testPathPattern: string, |}; type OnTestFailure = (test: Test, err: TestError) => void; @@ -54,14 +58,12 @@ const TEST_WORKER_PATH = require.resolve('./TestWorker'); class TestRunner { _config: Config; _options: Options; - _startRun: () => *; _dispatcher: ReporterDispatcher; - constructor(config: Config, options: Options, startRun: () => *) { + constructor(config: Config, options: Options) { this._config = config; this._dispatcher = new ReporterDispatcher(); this._options = options; - this._startRun = startRun; this._setupReporters(); } @@ -284,13 +286,9 @@ class TestRunner { this.addReporter(new CoverageReporter()); } - this.addReporter( - new SummaryReporter({ - getTestSummary: this._options.getTestSummary, - }), - ); + this.addReporter(new SummaryReporter(this._options)); if (config.notify) { - this.addReporter(new NotifyReporter(this._startRun)); + this.addReporter(new NotifyReporter(this._options.startRun)); } } diff --git a/packages/jest-cli/src/__tests__/SearchSource-test.js b/packages/jest-cli/src/__tests__/SearchSource-test.js index 5db27c34914e..705eca5eebf4 100644 --- a/packages/jest-cli/src/__tests__/SearchSource-test.js +++ b/packages/jest-cli/src/__tests__/SearchSource-test.js @@ -94,8 +94,7 @@ describe('SearchSource', () => { findMatchingTests = config => Runtime.createContext(config, { maxWorkers, - }).then(context => - new SearchSource(context).findMatchingTests()); + }).then(context => new SearchSource(context).findMatchingTests()); }); it('finds tests matching a pattern via testRegex', () => { diff --git a/packages/jest-cli/src/reporters/DefaultReporter.js b/packages/jest-cli/src/reporters/DefaultReporter.js index 100192f0c06b..44387138c4a4 100644 --- a/packages/jest-cli/src/reporters/DefaultReporter.js +++ b/packages/jest-cli/src/reporters/DefaultReporter.js @@ -34,10 +34,7 @@ const isInteractive = process.stdin.isTTY && !isCI; class DefaultReporter extends BaseReporter { _clear: string; // ANSI clear sequence for the last printed status - _currentlyRunning: Map; - _currentStatusHeight: number; _err: write; - _lastAggregatedResults: AggregatedResult; _out: write; _status: Status; diff --git a/packages/jest-cli/src/reporters/SummaryReporter.js b/packages/jest-cli/src/reporters/SummaryReporter.js index dee64aead607..1bd16e278a60 100644 --- a/packages/jest-cli/src/reporters/SummaryReporter.js +++ b/packages/jest-cli/src/reporters/SummaryReporter.js @@ -12,6 +12,8 @@ import type {AggregatedResult, SnapshotSummary} from 'types/TestResult'; import type {Config} from 'types/Config'; import type {Context} from 'types/Context'; +import type {Options as SummaryReporterOptions} from '../TestRunner'; +import type {PathPattern} from '../SearchSource'; import type {ReporterOnStartOptions} from 'types/Reporters'; const BaseReporter = require('./BaseReporter'); @@ -20,10 +22,6 @@ const {getSummary, pluralize} = require('./utils'); const chalk = require('chalk'); const getResultHeader = require('./getResultHeader'); -type Options = {| - getTestSummary: () => string, -|}; - const ARROW = ' \u203A '; const FAIL_COLOR = chalk.bold.red; const SNAPSHOT_ADDED = chalk.bold.green; @@ -62,9 +60,9 @@ const NPM_EVENTS = new Set([ class SummaryReporter extends BaseReporter { _estimatedTime: number; - _options: Options; + _options: SummaryReporterOptions; - constructor(options: Options) { + constructor(options: SummaryReporterOptions) { super(); this._options = options; this._estimatedTime = 0; @@ -115,7 +113,12 @@ class SummaryReporter extends BaseReporter { if (numTotalTestSuites) { const testSummary = wasInterrupted ? chalk.bold.red('Test run was interrupted.') - : this._options.getTestSummary(); + : this._getTestSummary( + contexts, + this._options.pattern, + this._options.testNamePattern, + this._options.testPathPattern, + ); this.log( getSummary(aggregatedResults, { estimatedTime: this._estimatedTime, @@ -233,6 +236,31 @@ class SummaryReporter extends BaseReporter { this.log(''); // print empty line } } + + _getTestSummary( + contexts: Set, + pattern: PathPattern, + testNamePattern: string, + testPathPattern: string, + ) { + const testInfo = pattern.onlyChanged + ? chalk.dim(' related to changed files') + : pattern.input !== '' ? chalk.dim(' matching ') + testPathPattern : ''; + + const nameInfo = testNamePattern + ? chalk.dim(' with tests matching ') + `"${testNamePattern}"` + : ''; + + const contextInfo = contexts.size > 1 + ? chalk.dim(' in ') + contexts.size + chalk.dim(' projects') + : ''; + + return chalk.dim('Ran all test suites') + + testInfo + + nameInfo + + contextInfo + + chalk.dim('.'); + } } module.exports = SummaryReporter; diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index e33895c02f00..ede1374e5197 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -11,7 +11,6 @@ import type {Context} from 'types/Context'; import type {Test} from 'types/TestRunner'; -import type {PathPattern} from './SearchSource'; import type TestWatcher from './TestWatcher'; const fs = require('graceful-fs'); @@ -27,6 +26,11 @@ const getMaxWorkers = require('./lib/getMaxWorkers'); const path = require('path'); const setState = require('./lib/setState'); +const setConfig = (contexts, newConfig) => + contexts.forEach( + context => context.config = Object.assign({}, context.config, newConfig), + ); + const formatTestPathPattern = pattern => { const testPattern = pattern.testPathPattern; const input = pattern.input; @@ -37,31 +41,6 @@ const formatTestPathPattern = pattern => { return input === testPattern ? formattedInput : formattedPattern; }; -const getTestSummary = ( - contexts: Array, - pattern: PathPattern, - testPathPattern: string, - testNamePattern: string, -) => { - const testInfo = pattern.onlyChanged - ? chalk.dim(' related to changed files') - : pattern.input !== '' ? chalk.dim(' matching ') + testPathPattern : ''; - - const nameInfo = testNamePattern - ? chalk.dim(' with tests matching ') + `"${testNamePattern}"` - : ''; - - const contextInfo = contexts.length > 1 - ? chalk.dim(' in ') + contexts.length + chalk.dim(' projects') - : ''; - - return chalk.dim('Ran all test suites') + - testInfo + - nameInfo + - contextInfo + - chalk.dim('.'); -}; - const getNoTestsFoundMessage = (testRunData, pattern) => { if (pattern.onlyChanged) { return chalk.bold( @@ -116,7 +95,6 @@ const getTestPaths = async (context, pattern, argv, pipe) => { const source = new SearchSource(context); let data = await source.getTestPaths(pattern); if (!data.paths.length) { - const localConsole = new Console(pipe, pipe); if (pattern.onlyChanged && data.noSCM) { if (context.config.watch) { // Run all the tests @@ -126,7 +104,7 @@ const getTestPaths = async (context, pattern, argv, pipe) => { pattern = getTestPathPattern(argv); data = await source.getTestPaths(pattern); } else { - localConsole.log( + new Console(pipe, pipe).log( 'Jest can only find uncommitted changed files in a git or hg ' + 'repository. If you make your project a git or hg ' + 'repository (`git init` or `hg init`), Jest will be able ' + @@ -140,6 +118,30 @@ const getTestPaths = async (context, pattern, argv, pipe) => { return data; }; +const processResults = (runResults, options) => { + if (options.testResultsProcessor) { + /* $FlowFixMe */ + runResults = require(options.testResultsProcessor)(runResults); + } + if (options.isJSON) { + if (options.outputFile) { + const outputFile = path.resolve(process.cwd(), options.outputFile); + + fs.writeFileSync( + outputFile, + JSON.stringify(formatTestResults(runResults)), + ); + process.stdout.write( + `Test results written to: ` + + `${path.relative(process.cwd(), outputFile)}\n`, + ); + } else { + process.stdout.write(JSON.stringify(formatTestResults(runResults))); + } + } + return options.onComplete && options.onComplete(runResults); +}; + const runJest = async ( contexts: Array, argv: Object, @@ -159,30 +161,6 @@ const runJest = async ( }), ); - const processResults = runResults => { - if (context.config.testResultsProcessor) { - /* $FlowFixMe */ - runResults = require(context.config.testResultsProcessor)(runResults); - } - if (argv.json) { - if (argv.outputFile) { - const outputFile = path.resolve(process.cwd(), argv.outputFile); - - fs.writeFileSync( - outputFile, - JSON.stringify(formatTestResults(runResults)), - ); - process.stdout.write( - `Test results written to: ` + - `${path.relative(process.cwd(), outputFile)}\n`, - ); - } else { - process.stdout.write(JSON.stringify(formatTestResults(runResults))); - } - } - return onComplete && onComplete(runResults); - }; - const allTests = testRunData .reduce((tests, testRun) => tests.concat(testRun.tests), []) .sort((a: Test, b: Test) => { @@ -193,8 +171,7 @@ const runJest = async ( }); if (!allTests.length) { - const localConsole = new Console(pipe, pipe); - localConsole.log(getNoTestsFoundMessage(testRunData, pattern)); + new Console(pipe, pipe).log(getNoTestsFoundMessage(testRunData, pattern)); } if ( @@ -202,35 +179,36 @@ const runJest = async ( context.config.silent !== true && context.config.verbose !== false ) { - contexts.forEach( - context => - context.config = Object.assign({}, context.config, {verbose: true}), - ); + setConfig(contexts, {verbose: true}); } if (context.config.updateSnapshot === true) { - context.config = Object.assign({}, context.config, { - updateSnapshot: true, - }); + setConfig(contexts, {updateSnapshot: true}); + } + + // When using more than one context, make all printed paths relative to the + // current cwd. + if (contexts.length > 1) { + setConfig(contexts, {rootDir: process.cwd()}); } - const results = await new TestRunner( - context.config, - { - getTestSummary: () => - getTestSummary( - contexts, - pattern, - formatTestPathPattern(pattern), - argv.testNamePattern, - ), - maxWorkers, - }, + const results = await new TestRunner(context.config, { + maxWorkers, + pattern, startRun, - ).runTests(allTests, testWatcher); + testNamePattern: argv.testNamePattern, + testPathPattern: formatTestPathPattern(pattern), + }).runTests(allTests, testWatcher); + testRunData.forEach(({sequencer, tests}) => sequencer.cacheResults(tests, results)); - return processResults(results); + + return processResults(results, { + isJSON: argv.json, + onComplete, + outputFile: argv.outputFile, + testResultsProcessor: context.config.testResultsProcessor, + }); }; module.exports = runJest; From ac61b80595319854ae7ba859f5e9fdeb054eb3f6 Mon Sep 17 00:00:00 2001 From: cpojer Date: Sun, 16 Apr 2017 20:52:46 +0100 Subject: [PATCH 08/11] SearchSource now returns `tests: Array`. --- packages/jest-cli/src/SearchSource.js | 46 +++++++++++-------- .../jest-cli/src/TestPathPatternPrompt.js | 11 ++--- packages/jest-cli/src/TestSequencer.js | 14 ++---- .../src/__tests__/SearchSource-test.js | 38 +++++++-------- .../src/__tests__/TestSequencer-test.js | 33 ++++++++----- .../watch-filename-pattern-mode-test.js | 12 ++++- packages/jest-cli/src/runJest.js | 4 +- types/TestRunner.js | 2 +- 8 files changed, 92 insertions(+), 68 deletions(-) diff --git a/packages/jest-cli/src/SearchSource.js b/packages/jest-cli/src/SearchSource.js index 7b29edf9067e..a2dd14ca09c0 100644 --- a/packages/jest-cli/src/SearchSource.js +++ b/packages/jest-cli/src/SearchSource.js @@ -13,6 +13,7 @@ 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'; const micromatch = require('micromatch'); @@ -27,8 +28,8 @@ const { type SearchResult = {| noSCM?: boolean, - paths: Array, stats?: {[key: string]: number}, + tests: Array, total?: number, |}; @@ -62,7 +63,7 @@ const globsToMatcher = (globs: ?Array) => { } const matchers = globs.map(each => micromatch.matcher(each, {dot: true})); - return (path: Path) => matchers.some(each => each(path)); + return path => matchers.some(each => each(path)); }; const regexToMatcher = (testRegex: string) => { @@ -71,9 +72,16 @@ const regexToMatcher = (testRegex: string) => { } const regex = new RegExp(pathToRegex(testRegex)); - return (path: Path) => regex.test(path); + return path => regex.test(path); }; +const toTests = (context, tests) => + tests.map(path => ({ + context, + duration: undefined, + path, + })); + class SearchSource { _context: Context; _options: ResolveModuleConfig; @@ -112,12 +120,12 @@ class SearchSource { } _filterTestPathsWithStats( - allPaths: Array, + allPaths: Array, testPathPattern?: StrOrRegExpPattern, ): SearchResult { const data = { - paths: [], stats: {}, + tests: [], total: allPaths.length, }; @@ -128,11 +136,10 @@ class SearchSource { } const testCasesKeys = Object.keys(testCases); - - data.paths = allPaths.filter(path => { + data.tests = allPaths.filter(test => { return testCasesKeys.reduce( (flag, key) => { - if (testCases[key](path)) { + if (testCases[key](test.path)) { data.stats[key] = ++data.stats[key] || 1; return flag && true; } @@ -148,7 +155,7 @@ class SearchSource { _getAllTestPaths(testPathPattern: StrOrRegExpPattern): SearchResult { return this._filterTestPathsWithStats( - this._context.hasteFS.getAllFiles(), + toTests(this._context, this._context.hasteFS.getAllFiles()), testPathPattern, ); } @@ -168,12 +175,15 @@ class SearchSource { this._context.hasteFS, ); return { - paths: dependencyResolver.resolveInverse( - allPaths, - this.isTestFilePath.bind(this), - { - skipNodeResolution: this._options.skipNodeResolution, - }, + tests: toTests( + this._context, + dependencyResolver.resolveInverse( + allPaths, + this.isTestFilePath.bind(this), + { + skipNodeResolution: this._options.skipNodeResolution, + }, + ), ), }; } @@ -183,7 +193,7 @@ class SearchSource { const resolvedPaths = paths.map(p => path.resolve(process.cwd(), p)); return this.findRelatedTests(new Set(resolvedPaths)); } - return {paths: []}; + return {tests: []}; } findChangedTests(options: Options): Promise { @@ -193,7 +203,7 @@ class SearchSource { if (!repos.every(([gitRepo, hgRepo]) => gitRepo || hgRepo)) { return { noSCM: true, - paths: [], + tests: [], }; } return Promise.all( @@ -217,7 +227,7 @@ class SearchSource { } else if (pattern.testPathPattern != null) { return Promise.resolve(this.findMatchingTests(pattern.testPathPattern)); } else { - return Promise.resolve({paths: []}); + return Promise.resolve({tests: []}); } } } diff --git a/packages/jest-cli/src/TestPathPatternPrompt.js b/packages/jest-cli/src/TestPathPatternPrompt.js index 9117cc6dd4f8..c2a8ca657bf9 100644 --- a/packages/jest-cli/src/TestPathPatternPrompt.js +++ b/packages/jest-cli/src/TestPathPatternPrompt.js @@ -64,21 +64,16 @@ module.exports = class TestPathPatternPrompt { regex = new RegExp(pattern, 'i'); } catch (e) {} - let paths = []; + let tests = []; if (regex) { this._searchSources.forEach(({searchSource, context}) => { - paths = paths.concat( - searchSource.findMatchingTests(pattern).paths.map(path => ({ - context, - path, - })), - ); + tests = tests.concat(searchSource.findMatchingTests(pattern).tests); }); } this._pipe.write(ansiEscapes.eraseLine); this._pipe.write(ansiEscapes.cursorLeft); - this._printTypeahead(pattern, paths, 10); + this._printTypeahead(pattern, tests, 10); } _printTypeahead(pattern: string, allResults: Array, max: number) { diff --git a/packages/jest-cli/src/TestSequencer.js b/packages/jest-cli/src/TestSequencer.js index d722f997250c..8591638751e0 100644 --- a/packages/jest-cli/src/TestSequencer.js +++ b/packages/jest-cli/src/TestSequencer.js @@ -45,8 +45,7 @@ class TestSequencer { // After a test run we store the time it took to run a test and on // subsequent runs we use that to run the slowest tests first, yielding the // fastest results. - sort(testPaths: Array): Tests { - const context = this._context; + sort(tests: Tests): Tests { const stats = {}; const fileSize = filePath => stats[filePath] || (stats[filePath] = fs.statSync(filePath).size); @@ -56,14 +55,14 @@ class TestSequencer { this._cache = {}; try { - if (context.config.cache) { + if (this._context.config.cache) { this._cache = JSON.parse( fs.readFileSync(this._getTestPerformanceCachePath(), 'utf8'), ); } } catch (e) {} - testPaths = testPaths.sort((pathA, pathB) => { + tests = tests.sort(({path: pathA}, {path: pathB}) => { const failedA = failed(pathA); const failedB = failed(pathB); if (failedA !== failedB) { @@ -83,11 +82,8 @@ class TestSequencer { return fileSize(pathA) < fileSize(pathB) ? 1 : -1; }); - return testPaths.map(path => ({ - context, - duration: this._cache[path] && this._cache[path][1], - path, - })); + tests.forEach(test => test.duration = time(test.path)); + return tests; } cacheResults(tests: Tests, results: AggregatedResult) { diff --git a/packages/jest-cli/src/__tests__/SearchSource-test.js b/packages/jest-cli/src/__tests__/SearchSource-test.js index 705eca5eebf4..7732d589d3b6 100644 --- a/packages/jest-cli/src/__tests__/SearchSource-test.js +++ b/packages/jest-cli/src/__tests__/SearchSource-test.js @@ -19,6 +19,8 @@ const testRegex = path.sep + '__testtests__' + path.sep; const testMatch = ['**/__testtests__/**/*']; const maxWorkers = 1; +const toPaths = tests => tests.map(({path}) => path); + let findMatchingTests; let normalizeConfig; @@ -106,7 +108,7 @@ describe('SearchSource', () => { testRegex: 'not-really-a-test', }); return findMatchingTests(config).then(data => { - const relPaths = data.paths + const relPaths = toPaths(data.tests) .map(absPath => path.relative(rootDir, absPath)) .sort(); expect(relPaths).toEqual( @@ -127,7 +129,7 @@ describe('SearchSource', () => { testRegex: '', }); return findMatchingTests(config).then(data => { - const relPaths = data.paths + const relPaths = toPaths(data.tests) .map(absPath => path.relative(rootDir, absPath)) .sort(); expect(relPaths).toEqual( @@ -148,7 +150,7 @@ describe('SearchSource', () => { testRegex: 'test\.jsx?', }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -166,7 +168,7 @@ describe('SearchSource', () => { testRegex: '', }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -183,7 +185,7 @@ describe('SearchSource', () => { testRegex, }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -200,7 +202,7 @@ describe('SearchSource', () => { testRegex: '', }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -217,7 +219,7 @@ describe('SearchSource', () => { testMatch, }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths).toEqual([path.normalize('__testtests__/test.jsx')]); }); @@ -231,7 +233,7 @@ describe('SearchSource', () => { testMatch, }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths).toEqual([path.normalize('__testtests__/test.foobar')]); }); @@ -245,7 +247,7 @@ describe('SearchSource', () => { testMatch, }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -262,7 +264,7 @@ describe('SearchSource', () => { testRegex, }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -279,7 +281,7 @@ describe('SearchSource', () => { testRegex: '', }); return findMatchingTests(config).then(data => { - const relPaths = data.paths.map(absPath => + const relPaths = toPaths(data.tests).map(absPath => path.relative(rootDir, absPath)); expect(relPaths.sort()).toEqual([ path.normalize('__testtests__/test.js'), @@ -315,7 +317,7 @@ describe('SearchSource', () => { it('makes sure a file is related to itself', () => { const data = searchSource.findRelatedTests(new Set([rootPath])); - expect(data.paths).toEqual([rootPath]); + expect(toPaths(data.tests)).toEqual([rootPath]); }); it('finds tests that depend directly on the path', () => { @@ -323,7 +325,7 @@ describe('SearchSource', () => { const loggingDep = path.join(rootDir, 'logging.js'); const parentDep = path.join(rootDir, 'ModuleWithSideEffects.js'); const data = searchSource.findRelatedTests(new Set([filePath])); - expect(data.paths.sort()).toEqual([ + expect(toPaths(data.tests).sort()).toEqual([ parentDep, filePath, loggingDep, @@ -349,25 +351,25 @@ describe('SearchSource', () => { it('returns empty search result for empty input', () => { const input = []; const data = searchSource.findRelatedTestsFromPattern(input); - expect(data.paths).toEqual([]); + expect(data.tests).toEqual([]); }); it('returns empty search result for invalid input', () => { const input = ['non-existend.js']; const data = searchSource.findRelatedTestsFromPattern(input); - expect(data.paths).toEqual([]); + expect(data.tests).toEqual([]); }); it('returns empty search result if no related tests were found', () => { const input = ['no tests.js']; const data = searchSource.findRelatedTestsFromPattern(input); - expect(data.paths).toEqual([]); + expect(data.tests).toEqual([]); }); it('finds tests for a single file', () => { const input = ['packages/jest-cli/src/__tests__/test_root/module.jsx']; const data = searchSource.findRelatedTestsFromPattern(input); - expect(data.paths.sort()).toEqual([ + expect(toPaths(data.tests).sort()).toEqual([ path.join(rootDir, '__testtests__', 'test.js'), path.join(rootDir, '__testtests__', 'test.jsx'), ]); @@ -379,7 +381,7 @@ describe('SearchSource', () => { 'packages/jest-cli/src/__tests__/test_root/module.foobar', ]; const data = searchSource.findRelatedTestsFromPattern(input); - expect(data.paths.sort()).toEqual([ + expect(toPaths(data.tests).sort()).toEqual([ path.join(rootDir, '__testtests__', 'test.foobar'), path.join(rootDir, '__testtests__', 'test.js'), path.join(rootDir, '__testtests__', 'test.jsx'), diff --git a/packages/jest-cli/src/__tests__/TestSequencer-test.js b/packages/jest-cli/src/__tests__/TestSequencer-test.js index e381e545109d..026bfed26f10 100644 --- a/packages/jest-cli/src/__tests__/TestSequencer-test.js +++ b/packages/jest-cli/src/__tests__/TestSequencer-test.js @@ -26,6 +26,13 @@ const context = { }, }; +const toTests = paths => + paths.map(path => ({ + context, + duration: undefined, + path, + })); + beforeEach(() => { sequencer = new TestSequencer(context); @@ -34,7 +41,7 @@ beforeEach(() => { }); test('sorts by file size if there is no timing information', () => { - expect(sequencer.sort(['/test-a.js', '/test-ab.js'])).toEqual([ + expect(sequencer.sort(toTests(['/test-a.js', '/test-ab.js']))).toEqual([ {context, duration: undefined, path: '/test-ab.js'}, {context, duration: undefined, path: '/test-a.js'}, ]); @@ -46,7 +53,7 @@ test('sorts based on timing information', () => { '/test-a.js': [SUCCESS, 5], '/test-ab.js': [SUCCESS, 3], })); - expect(sequencer.sort(['/test-a.js', '/test-ab.js'])).toEqual([ + expect(sequencer.sort(toTests(['/test-a.js', '/test-ab.js']))).toEqual([ {context, duration: 5, path: '/test-a.js'}, {context, duration: 3, path: '/test-ab.js'}, ]); @@ -61,7 +68,9 @@ test('sorts based on failures and timing information', () => { '/test-d.js': [SUCCESS, 2], })); expect( - sequencer.sort(['/test-a.js', '/test-ab.js', '/test-c.js', '/test-d.js']), + sequencer.sort( + toTests(['/test-a.js', '/test-ab.js', '/test-c.js', '/test-d.js']), + ), ).toEqual([ {context, duration: 6, path: '/test-c.js'}, {context, duration: 0, path: '/test-ab.js'}, @@ -80,13 +89,15 @@ test('sorts based on failures, timing information and file size', () => { '/test-efg.js': [FAIL], })); expect( - sequencer.sort([ - '/test-a.js', - '/test-ab.js', - '/test-c.js', - '/test-d.js', - '/test-efg.js', - ]), + sequencer.sort( + toTests([ + '/test-a.js', + '/test-ab.js', + '/test-c.js', + '/test-d.js', + '/test-efg.js', + ]), + ), ).toEqual([ {context, duration: undefined, path: '/test-efg.js'}, {context, duration: undefined, path: '/test-c.js'}, @@ -105,7 +116,7 @@ test('writes the cache based on the results', () => { })); const testPaths = ['/test-a.js', '/test-b.js', '/test-c.js']; - const tests = sequencer.sort(testPaths); + const tests = sequencer.sort(toTests(testPaths)); sequencer.cacheResults(tests, { testResults: [ { diff --git a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js index ed287a71f245..1d8be3c41602 100644 --- a/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js +++ b/packages/jest-cli/src/__tests__/watch-filename-pattern-mode-test.js @@ -31,6 +31,10 @@ jest.mock( '../SearchSource', () => class { + constructor(context) { + this._context = context; + } + findMatchingTests(pattern) { const paths = [ './path/to/file1-test.js', @@ -46,7 +50,13 @@ jest.mock( './path/to/file11-test.js', ].filter(path => path.match(pattern)); - return {paths}; + return { + tests: paths.map(path => ({ + context: this._context, + duration: null, + path, + })), + }; } }, ); diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index ede1374e5197..490eb88ba62d 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -94,7 +94,7 @@ const getNoTestsFoundMessage = (testRunData, pattern) => { const getTestPaths = async (context, pattern, argv, pipe) => { const source = new SearchSource(context); let data = await source.getTestPaths(pattern); - if (!data.paths.length) { + if (!data.tests.length) { if (pattern.onlyChanged && data.noSCM) { if (context.config.watch) { // Run all the tests @@ -156,7 +156,7 @@ const runJest = async ( contexts.map(async context => { const matches = await getTestPaths(context, pattern, argv, pipe); const sequencer = new TestSequencer(context); - const tests = sequencer.sort(matches.paths); + const tests = sequencer.sort(matches.tests); return {context, matches, sequencer, tests}; }), ); diff --git a/types/TestRunner.js b/types/TestRunner.js index 469bf1fc8005..de0da39dc812 100644 --- a/types/TestRunner.js +++ b/types/TestRunner.js @@ -15,7 +15,7 @@ import type {Path} from './Config'; export type Test = {| context: Context, path: Path, - duration?: number, + duration: ?number, |}; export type Tests = Array; From e8e609e366ae0d65d947f47220c5779d541a1869 Mon Sep 17 00:00:00 2001 From: cpojer Date: Sun, 16 Apr 2017 21:46:19 +0100 Subject: [PATCH 09/11] Use one TestSequencer instance for all contexts. --- packages/jest-cli/src/TestSequencer.js | 90 +++++++++---------- .../src/__tests__/TestSequencer-test.js | 66 +++++++++++++- packages/jest-cli/src/runJest.js | 29 ++---- 3 files changed, 118 insertions(+), 67 deletions(-) diff --git a/packages/jest-cli/src/TestSequencer.js b/packages/jest-cli/src/TestSequencer.js index 8591638751e0..13b6a9b0c6e6 100644 --- a/packages/jest-cli/src/TestSequencer.js +++ b/packages/jest-cli/src/TestSequencer.js @@ -11,7 +11,7 @@ import type {AggregatedResult} from 'types/TestResult'; import type {Context} from 'types/Context'; -import type {Tests} from 'types/TestRunner'; +import type {Test, Tests} from 'types/TestRunner'; const fs = require('fs'); const getCacheFilePath = require('jest-haste-map').getCacheFilePath; @@ -24,19 +24,31 @@ type Cache = { }; class TestSequencer { - _context: Context; - _cache: Cache; + _cache: Map; - constructor(context: Context) { - this._context = context; - this._cache = {}; + constructor() { + this._cache = new Map(); } - _getTestPerformanceCachePath() { - const {config} = this._context; + _getCachePath(context: Context) { + const {config} = context; return getCacheFilePath(config.cacheDirectory, 'perf-cache-' + config.name); } + _getCache(test: Test) { + const {context} = test; + if (!this._cache.has(context) && context.config.cache) { + try { + this._cache.set( + context, + JSON.parse(fs.readFileSync(this._getCachePath(context), 'utf8')), + ); + } catch (e) {} + } + + return this._cache.get(context) || {}; + } + // When running more tests than we have workers available, sort the tests // by size - big test files usually take longer to complete, so we run // them first in an effort to minimize worker idle time at the end of a @@ -47,51 +59,38 @@ class TestSequencer { // fastest results. sort(tests: Tests): Tests { const stats = {}; - const fileSize = filePath => - stats[filePath] || (stats[filePath] = fs.statSync(filePath).size); - const failed = filePath => - this._cache[filePath] && this._cache[filePath][0] === FAIL; - const time = filePath => this._cache[filePath] && this._cache[filePath][1]; + const fileSize = test => + stats[test.path] || (stats[test.path] = fs.statSync(test.path).size); + const hasFailed = (cache, test) => + cache[test.path] && cache[test.path][0] === FAIL; + const time = (cache, test) => cache[test.path] && cache[test.path][1]; - this._cache = {}; - try { - if (this._context.config.cache) { - this._cache = JSON.parse( - fs.readFileSync(this._getTestPerformanceCachePath(), 'utf8'), - ); - } - } catch (e) {} - - tests = tests.sort(({path: pathA}, {path: pathB}) => { - const failedA = failed(pathA); - const failedB = failed(pathB); + tests.forEach(test => test.duration = time(this._getCache(test), test)); + return tests.sort((testA, testB) => { + const cacheA = this._getCache(testA); + const cacheB = this._getCache(testB); + const failedA = hasFailed(cacheA, testA); + const failedB = hasFailed(cacheB, testB); + const hasTimeA = testA.duration != null; if (failedA !== failedB) { return failedA ? -1 : 1; + } else if (hasTimeA != (testB.duration != null)) { + // Check if only one of two tests has timing information + return hasTimeA != null ? 1 : -1; + } else if (testA.duration != null && testB.duration != null) { + return testA.duration < testB.duration ? 1 : -1; + } else { + return fileSize(testA) < fileSize(testB) ? 1 : -1; } - const timeA = time(pathA); - const timeB = time(pathB); - const hasTimeA = timeA != null; - const hasTimeB = timeB != null; - // Check if only one of two tests has timing information - if (hasTimeA != hasTimeB) { - return hasTimeA ? 1 : -1; - } - if (timeA != null && !timeB != null) { - return timeA < timeB ? 1 : -1; - } - return fileSize(pathA) < fileSize(pathB) ? 1 : -1; }); - - tests.forEach(test => test.duration = time(test.path)); - return tests; } cacheResults(tests: Tests, results: AggregatedResult) { - const cache = this._cache; const map = Object.create(null); - tests.forEach(({path}) => map[path] = true); + tests.forEach(test => map[test.path] = test); results.testResults.forEach(testResult => { if (testResult && map[testResult.testFilePath] && !testResult.skipped) { + const cache = this._getCache(map[testResult.testFilePath]); const perf = testResult.perfStats; cache[testResult.testFilePath] = [ testResult.numFailingTests ? FAIL : SUCCESS, @@ -99,10 +98,9 @@ class TestSequencer { ]; } }); - fs.writeFileSync( - this._getTestPerformanceCachePath(), - JSON.stringify(cache), - ); + + this._cache.forEach((cache, context) => + fs.writeFileSync(this._getCachePath(context), JSON.stringify(cache))); } } diff --git a/packages/jest-cli/src/__tests__/TestSequencer-test.js b/packages/jest-cli/src/__tests__/TestSequencer-test.js index 026bfed26f10..bd89eca04a02 100644 --- a/packages/jest-cli/src/__tests__/TestSequencer-test.js +++ b/packages/jest-cli/src/__tests__/TestSequencer-test.js @@ -26,6 +26,14 @@ const context = { }, }; +const secondContext = { + config: { + cache: true, + cacheDirectory: '/cache2', + name: 'test2', + }, +}; + const toTests = paths => paths.map(path => ({ context, @@ -34,10 +42,11 @@ const toTests = paths => })); beforeEach(() => { - sequencer = new TestSequencer(context); + sequencer = new TestSequencer(); fs.readFileSync = jest.fn(() => '{}'); fs.statSync = jest.fn(filePath => ({size: filePath.length})); + fs.writeFileSync = jest.fn(); }); test('sorts by file size if there is no timing information', () => { @@ -149,3 +158,58 @@ test('writes the cache based on the results', () => { '/test-c.js': [FAIL, 3], }); }); + +test('works with multiple contexts', () => { + fs.readFileSync = jest.fn( + cacheName => + cacheName.startsWith('/cache/') + ? JSON.stringify({ + '/test-a.js': [SUCCESS, 5], + '/test-b.js': [FAIL, 1], + }) + : JSON.stringify({ + '/test-c.js': [FAIL], + }), + ); + + const testPaths = [ + {context, duration: null, path: '/test-a.js'}, + {context, duration: null, path: '/test-b.js'}, + {context: secondContext, duration: null, path: '/test-c.js'}, + ]; + const tests = sequencer.sort(testPaths); + sequencer.cacheResults(tests, { + testResults: [ + { + numFailingTests: 0, + perfStats: {end: 2, start: 1}, + testFilePath: '/test-a.js', + }, + { + numFailingTests: 0, + perfStats: {end: 0, start: 0}, + skipped: true, + testFilePath: '/test-b.js', + }, + { + numFailingTests: 0, + perfStats: {end: 4, start: 1}, + testFilePath: '/test-c.js', + }, + { + numFailingTests: 1, + perfStats: {end: 2, start: 1}, + testFilePath: '/test-x.js', + }, + ], + }); + const fileDataA = JSON.parse(fs.writeFileSync.mock.calls[0][1]); + expect(fileDataA).toEqual({ + '/test-a.js': [SUCCESS, 1], + '/test-b.js': [FAIL, 1], + }); + const fileDataB = JSON.parse(fs.writeFileSync.mock.calls[1][1]); + expect(fileDataB).toEqual({ + '/test-c.js': [SUCCESS, 3], + }); +}); diff --git a/packages/jest-cli/src/runJest.js b/packages/jest-cli/src/runJest.js index 490eb88ba62d..5c91917d6158 100644 --- a/packages/jest-cli/src/runJest.js +++ b/packages/jest-cli/src/runJest.js @@ -10,7 +10,6 @@ 'use strict'; import type {Context} from 'types/Context'; -import type {Test} from 'types/TestRunner'; import type TestWatcher from './TestWatcher'; const fs = require('graceful-fs'); @@ -114,7 +113,6 @@ const getTestPaths = async (context, pattern, argv, pipe) => { } } } - return data; }; @@ -150,31 +148,23 @@ const runJest = async ( startRun: () => *, onComplete: (testResults: any) => void, ) => { - const maxWorkers = getMaxWorkers(argv); const context = contexts[0]; + const maxWorkers = getMaxWorkers(argv); + const pattern = getTestPathPattern(argv); + const sequencer = new TestSequencer(); + let allTests = []; const testRunData = await Promise.all( contexts.map(async context => { const matches = await getTestPaths(context, pattern, argv, pipe); - const sequencer = new TestSequencer(context); - const tests = sequencer.sort(matches.tests); - return {context, matches, sequencer, tests}; + allTests = allTests.concat(matches.tests); + return {context, matches}; }), ); - const allTests = testRunData - .reduce((tests, testRun) => tests.concat(testRun.tests), []) - .sort((a: Test, b: Test) => { - if (a.duration != null && b.duration != null) { - return a.duration < b.duration ? 1 : -1; - } - return a.duration == null ? 1 : 0; - }); - + allTests = sequencer.sort(allTests); if (!allTests.length) { new Console(pipe, pipe).log(getNoTestsFoundMessage(testRunData, pattern)); - } - - if ( + } else if ( allTests.length === 1 && context.config.silent !== true && context.config.verbose !== false @@ -200,8 +190,7 @@ const runJest = async ( testPathPattern: formatTestPathPattern(pattern), }).runTests(allTests, testWatcher); - testRunData.forEach(({sequencer, tests}) => - sequencer.cacheResults(tests, results)); + sequencer.cacheResults(allTests, results); return processResults(results, { isJSON: argv.json, From 8db8140eba85079deff6259497647b108b61e9c4 Mon Sep 17 00:00:00 2001 From: cpojer Date: Mon, 17 Apr 2017 19:30:42 -0700 Subject: [PATCH 10/11] Fix runJest-test. --- .../jest-cli/src/__tests__/runJest-test.js | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/jest-cli/src/__tests__/runJest-test.js b/packages/jest-cli/src/__tests__/runJest-test.js index a46850ff21ac..f7bf2e29e3c5 100644 --- a/packages/jest-cli/src/__tests__/runJest-test.js +++ b/packages/jest-cli/src/__tests__/runJest-test.js @@ -30,17 +30,24 @@ jest.mock('../TestRunner', () => { jest.mock('../SearchSource', () => { const SearchSource = require.requireActual('../SearchSource'); - SearchSource.prototype.getTestPaths = () => - Promise.resolve({ - paths: ['/path.js'], + SearchSource.prototype.getTestPaths = function() { + return Promise.resolve({ stats: {}, + tests: [ + { + context: this._context, + path: '/path.js', + }, + ], total: 1, }); + }; return SearchSource; }); jest.mock('../TestSequencer', () => { const TestSequencer = require.requireActual('../TestSequencer'); + TestSequencer.prototype.sort = jest.fn(tests => tests); TestSequencer.prototype.cacheResults = jest.fn(); return TestSequencer; }); @@ -64,15 +71,25 @@ if (process.platform !== 'win32') { }); } -test('passes updateSnapshot to hasteContext.config', async () => { - const hasteContext = { - config: {rootDir}, - hasteFS, - }; +test('passes updateSnapshot to context.config', async () => { + const contexts = [ + { + config, + hasteFS, + }, + { + config: { + rootDir, + roots: [], + testPathIgnorePatterns: [], + }, + hasteFS, + }, + ]; const noop = () => {}; const argv = {}; const pipe = process.stdout; const testWatcher = new TestWatcher({isWatchMode: true}); - await runJest(hasteContext, config, argv, pipe, testWatcher, noop, noop); - expect(hasteContext.config.updateSnapshot).toBe(true); + await runJest(contexts, argv, pipe, testWatcher, noop, noop); + expect(contexts.every(({config}) => config.updateSnapshot)).toBe(true); }); From 8831e005289b4abcc7b41e7b0d08487b0828a415 Mon Sep 17 00:00:00 2001 From: cpojer Date: Mon, 17 Apr 2017 19:46:27 -0700 Subject: [PATCH 11/11] Fix TestSequencer-test on Windows. --- packages/jest-cli/src/__tests__/TestSequencer-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/jest-cli/src/__tests__/TestSequencer-test.js b/packages/jest-cli/src/__tests__/TestSequencer-test.js index bd89eca04a02..90a4c9aef759 100644 --- a/packages/jest-cli/src/__tests__/TestSequencer-test.js +++ b/packages/jest-cli/src/__tests__/TestSequencer-test.js @@ -12,6 +12,7 @@ jest.mock('fs'); const TestSequencer = require('../TestSequencer'); const fs = require('fs'); +const path = require('path'); const FAIL = 0; const SUCCESS = 1; @@ -162,7 +163,7 @@ test('writes the cache based on the results', () => { test('works with multiple contexts', () => { fs.readFileSync = jest.fn( cacheName => - cacheName.startsWith('/cache/') + cacheName.startsWith(path.sep + 'cache' + path.sep) ? JSON.stringify({ '/test-a.js': [SUCCESS, 5], '/test-b.js': [FAIL, 1],