diff --git a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap index 57e9f0d32837..b2ba413d57fb 100644 --- a/integration_tests/__tests__/__snapshots__/show_config.test.js.snap +++ b/integration_tests/__tests__/__snapshots__/show_config.test.js.snap @@ -75,7 +75,6 @@ exports[`--showConfig outputs config info and exits 1`] = ` \\"testPathPattern\\": \\"\\", \\"testResultsProcessor\\": null, \\"updateSnapshot\\": \\"all\\", - \\"useStderr\\": false, \\"verbose\\": null, \\"watch\\": false, \\"watchman\\": true diff --git a/integration_tests/__tests__/custom_reporters.test.js b/integration_tests/__tests__/custom_reporters.test.js index a9fe577f55fd..e6785998b853 100644 --- a/integration_tests/__tests__/custom_reporters.test.js +++ b/integration_tests/__tests__/custom_reporters.test.js @@ -125,9 +125,8 @@ describe('Custom Reporters Integration', () => { ]); expect(status).toBe(0); - expect(stderr.trim()).toBe(''); - expect(stdout).toMatchSnapshot(); + expect(stderr.trim()).toBe(''); }); test('prints reporter errors', () => { diff --git a/integration_tests/__tests__/json_stdout.test.js b/integration_tests/__tests__/json_stdout.test.js new file mode 100644 index 000000000000..6f397b14b545 --- /dev/null +++ b/integration_tests/__tests__/json_stdout.test.js @@ -0,0 +1,93 @@ +/** + * 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 path = require('path'); +const os = require('os'); +const skipOnWindows = require('../../scripts/skip_on_windows'); +const {cleanup, writeFiles} = require('../utils'); +const runJest = require('../runJest'); + +const DIR = path.resolve(os.tmpdir(), 'json_stdout_test'); + +skipOnWindows.suite(); + +beforeEach(() => cleanup(DIR)); +afterAll(() => cleanup(DIR)); + +const reporterCode = ` +'use strict'; + +console.log('at require time'); +console.log(console.log.toString()); +module.exports = class Reporter { + onRunStart() { + console.log('at runtime'); + } +}; +`; + +test('does not pollute stdout from third party code (or any code)', () => { + writeFiles(DIR, { + '.watchmanconfig': '', + '__tests__/hey.test.js': `test('test', () => {})`, + 'custom_reporter.js': reporterCode, + 'package.json': JSON.stringify({ + jest: { + // Reporter will be required as a regular module in the master process + // before any of the test run, which makes it a perfect place to try + // to break the stdout by console.logging some text. + reporters: ['/custom_reporter.js', 'default'], + }, + }), + }); + + let stdout; + let stderr; + ({stdout, stderr} = runJest(DIR, ['--json'])); + expect(stdout).not.toMatch('at require time'); + expect(stdout).not.toMatch('at runtime'); + expect(stderr).toMatch('at require time'); + expect(stderr).toMatch('at runtime'); + expect(() => JSON.parse(stdout)).not.toThrow(); + + ({stdout, stderr} = runJest(DIR, ['--listTests', '__tests__/hey.test.js'])); + + expect(stdout).not.toMatch('at require time'); + expect(stdout).not.toMatch('at runtime'); + expect(stderr).not.toMatch('at require time'); + expect(stderr).not.toMatch('at runtime'); + expect(() => JSON.parse(stdout)).not.toThrow(); + expect(stdout).toMatch('hey.test.js'); +}); + +test('prints to stdout if not in json mode', () => { + writeFiles(DIR, { + '.watchmanconfig': '', + '__tests__/hey.test.js': `test('test', () => {})`, + 'custom_reporter.js': reporterCode, + 'package.json': JSON.stringify({ + jest: { + // Reporter will be required as a regular module in the master process + // before any of the test run, which makes it a perfect place to try + // to break the stdout by console.logging some text. + reporters: ['/custom_reporter.js', 'default'], + }, + }), + }); + + const {status, stdout, stderr} = runJest(DIR); + expect(stderr).not.toMatch('at runtime'); + expect(stderr).not.toMatch('at require time'); + expect(stdout).toMatch('at require time'); + expect(stdout).toMatch('at runtime'); + expect(status).toBe(0); +}); diff --git a/packages/jest-cli/bin/jest.js b/packages/jest-cli/bin/jest.js index 67ea4091b528..d8f1be52a727 100755 --- a/packages/jest-cli/bin/jest.js +++ b/packages/jest-cli/bin/jest.js @@ -11,4 +11,9 @@ if (process.env.NODE_ENV == null) { process.env.NODE_ENV = 'test'; } -require('../build/cli').run(); +const stdout = process.stdout; +// Prevent any code from printing to stdout because it will break JSON +// output if we run jest with `--json` or `--listTests` flags. +Object.defineProperty(process, 'stdout', {value: process.stderr}); + +require('../build/cli').run(undefined, undefined, stdout); diff --git a/packages/jest-cli/src/__tests__/__snapshots__/watch_test_name_pattern_mode.test.js.snap b/packages/jest-cli/src/__tests__/__snapshots__/watch_test_name_pattern_mode.test.js.snap index 73d0b2048b57..7c4a8b5da56b 100644 --- a/packages/jest-cli/src/__tests__/__snapshots__/watch_test_name_pattern_mode.test.js.snap +++ b/packages/jest-cli/src/__tests__/__snapshots__/watch_test_name_pattern_mode.test.js.snap @@ -20,7 +20,7 @@ exports[`Watch mode flows Pressing "T" enters pattern mode 1`] = ` › should recognize null and undefined - › should not output colors to pipe + › should not output colors to outputStream › should convert string to a RegExp @@ -49,7 +49,7 @@ exports[`Watch mode flows Pressing "T" enters pattern mode 2`] = ` › should recognize null and undefined - › should not output colors to pipe + › should not output colors to outputStream › should convert string to a RegExp @@ -187,7 +187,7 @@ exports[`Watch mode flows Results in pattern mode get truncated appropriately 1` › should recognize various types - › should not output colors to pipe + › should not output colors to outputStream › should convert string to a RegExp diff --git a/packages/jest-cli/src/__tests__/watch.test.js b/packages/jest-cli/src/__tests__/watch.test.js index eaeaf1e658ca..f667ac240ac3 100644 --- a/packages/jest-cli/src/__tests__/watch.test.js +++ b/packages/jest-cli/src/__tests__/watch.test.js @@ -36,7 +36,7 @@ const watch = require('../watch'); afterEach(runJestMock.mockReset); describe('Watch mode flows', () => { - let pipe; + let outputStream; let hasteMapInstances; let globalConfig; let contexts; @@ -44,7 +44,7 @@ describe('Watch mode flows', () => { beforeEach(() => { const config = {roots: [], testPathIgnorePatterns: [], testRegex: ''}; - pipe = {write: jest.fn()}; + outputStream = {write: jest.fn()}; globalConfig = {watch: true}; hasteMapInstances = [{on: () => {}}]; contexts = [{config}]; @@ -54,13 +54,19 @@ describe('Watch mode flows', () => { it('Correctly passing test path pattern', () => { globalConfig.testPathPattern = 'test-*'; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); expect(runJestMock.mock.calls[0][0]).toMatchObject({ contexts, globalConfig, onComplete: expect.any(Function), - outputStream: pipe, + outputStream, testWatcher: new TestWatcher({isWatchMode: true}), }); }); @@ -68,31 +74,49 @@ describe('Watch mode flows', () => { it('Correctly passing test name pattern', () => { globalConfig.testNamePattern = 'test-*'; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); expect(runJestMock.mock.calls[0][0]).toMatchObject({ contexts, globalConfig, onComplete: expect.any(Function), - outputStream: pipe, + outputStream, testWatcher: new TestWatcher({isWatchMode: true}), }); }); it('Runs Jest once by default and shows usage', () => { - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); expect(runJestMock.mock.calls[0][0]).toMatchObject({ contexts, globalConfig, onComplete: expect.any(Function), - outputStream: pipe, + outputStream, testWatcher: new TestWatcher({isWatchMode: true}), }); - expect(pipe.write.mock.calls.reverse()[0]).toMatchSnapshot(); + expect(outputStream.write.mock.calls.reverse()[0]).toMatchSnapshot(); }); it('Pressing "o" runs test in "only changed files" mode', () => { - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); runJestMock.mockReset(); stdin.emit(KEYS.O); @@ -106,7 +130,13 @@ describe('Watch mode flows', () => { }); it('Pressing "a" runs test in "watch all" mode', () => { - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); runJestMock.mockReset(); stdin.emit(KEYS.A); @@ -120,14 +150,26 @@ describe('Watch mode flows', () => { }); it('Pressing "ENTER" reruns the tests', () => { - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); expect(runJestMock).toHaveBeenCalledTimes(1); stdin.emit(KEYS.ENTER); expect(runJestMock).toHaveBeenCalledTimes(2); }); it('Pressing "u" reruns the tests in "update snapshot" mode', () => { - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); runJestMock.mockReset(); stdin.emit(KEYS.U); 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 561840dbba14..1c65826a635a 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 @@ -4,8 +4,6 @@ * 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. - * - * @emails oncall+jsinfra */ 'use strict'; @@ -94,14 +92,14 @@ const globalConfig = {watch: true}; afterEach(runJestMock.mockReset); describe('Watch mode flows', () => { - let pipe; + let outputStream; let hasteMapInstances; let contexts; let stdin; beforeEach(() => { terminalWidth = 80; - pipe = {write: jest.fn()}; + outputStream = {write: jest.fn()}; hasteMapInstances = [{on: () => {}}]; contexts = [{config: {}}]; stdin = new MockStdin(); @@ -109,16 +107,22 @@ describe('Watch mode flows', () => { it('Pressing "P" enters pattern mode', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); // Write a enter pattern mode stdin.emit(KEYS.P); - expect(pipe.write).toBeCalledWith(' pattern › '); + expect(outputStream.write).toBeCalledWith(' pattern › '); const assertPattern = hex => { - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(hex); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }; // Write a pattern @@ -147,7 +151,13 @@ describe('Watch mode flows', () => { const toUnixPathPattern = pathPattern => pathPattern.replace(/\\\\/g, '/'); contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); // Write a enter pattern mode stdin.emit(KEYS.P); @@ -174,22 +184,34 @@ describe('Watch mode flows', () => { it('Results in pattern mode get truncated appropriately', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); stdin.emit(KEYS.P); [30, 25, 20].forEach(width => { terminalWidth = width; stdin.emit(KEYS.BACKSPACE); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.A); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }); }); it('Shows the appropiate header when the filename filter is active', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); stdin.emit(KEYS.P); @@ -198,20 +220,26 @@ describe('Watch mode flows', () => { .concat(KEYS.ENTER) .forEach(key => stdin.emit(key)); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.P); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); ['p'].map(toHex).concat(KEYS.ENTER).forEach(key => stdin.emit(key)); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.P); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }); it('Shows the appropiate header when the test name filter is active', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); stdin.emit(KEYS.T); @@ -220,20 +248,26 @@ describe('Watch mode flows', () => { .concat(KEYS.ENTER) .forEach(key => stdin.emit(key)); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.T); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); ['t'].map(toHex).concat(KEYS.ENTER).forEach(key => stdin.emit(key)); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.T); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }); it('Shows the appropiate header when both filters are active', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); stdin.emit(KEYS.P); @@ -248,14 +282,20 @@ describe('Watch mode flows', () => { .concat(KEYS.ENTER) .forEach(key => stdin.emit(key)); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.T); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }); it('Pressing "c" clears the filters', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); stdin.emit(KEYS.P); @@ -272,9 +312,9 @@ describe('Watch mode flows', () => { stdin.emit(KEYS.C); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.P); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }); }); 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 66691a48ec1e..eda61e204a7e 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 @@ -75,7 +75,7 @@ jest.doMock( testResults: [{title: 'should recognize null and undefined'}], }, { - testResults: [{title: 'should not output colors to pipe'}], + testResults: [{title: 'should not output colors to outputStream'}], }, { testResults: [{title: 'should convert string to a RegExp'}], @@ -110,14 +110,14 @@ const globalConfig = { afterEach(runJestMock.mockReset); describe('Watch mode flows', () => { - let pipe; + let outputStream; let hasteMapInstances; let contexts; let stdin; beforeEach(() => { terminalWidth = 80; - pipe = {write: jest.fn()}; + outputStream = {write: jest.fn()}; hasteMapInstances = [{on: () => {}}]; contexts = [{config: {}}]; stdin = new MockStdin(); @@ -125,16 +125,22 @@ describe('Watch mode flows', () => { it('Pressing "T" enters pattern mode', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); // Write a enter pattern mode stdin.emit(KEYS.T); - expect(pipe.write).toBeCalledWith(' pattern › '); + expect(outputStream.write).toBeCalledWith(' pattern › '); const assertPattern = hex => { - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(hex); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }; // Write a pattern @@ -160,7 +166,13 @@ describe('Watch mode flows', () => { it('can select a specific test name from the typeahead results', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); // Write a enter pattern mode stdin.emit(KEYS.T); @@ -185,16 +197,22 @@ describe('Watch mode flows', () => { it('Results in pattern mode get truncated appropriately', () => { contexts[0].config = {rootDir: ''}; - watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); + watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdin, + }); stdin.emit(KEYS.T); [50, 30].forEach(width => { terminalWidth = width; stdin.emit(KEYS.BACKSPACE); - pipe.write.mockReset(); + outputStream.write.mockReset(); stdin.emit(KEYS.T); - expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); + expect(outputStream.write.mock.calls.join('\n')).toMatchSnapshot(); }); }); }); diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index 3456d839eb5c..a91342924050 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -18,6 +18,7 @@ import { createDirectory, validateCLIOptions, } from 'jest-util'; +import {Console as NativeConsole} from 'console'; import {readConfig} from 'jest-config'; import {version as VERSION} from '../../package.json'; import args from './args'; @@ -34,45 +35,53 @@ import TestWatcher from '../test_watcher'; import watch from '../watch'; import yargs from 'yargs'; -async function run(maybeArgv?: Argv, project?: Path) { +async function run( + maybeArgv?: Argv, + project?: Path, + stdout?: stream$Writable | tty$WriteStream, +) { const argv: Argv = _buildArgv(maybeArgv, project); const projects = _getProjectListFromCLIArgs(argv, project); // If we're running a single Jest project, we might want to use another // version of Jest (the one that is specified in this project's package.json) const runCLIFn = _getRunCLIFn(projects); - const {results, globalConfig} = await runCLIFn(argv, projects); + const {results, globalConfig} = await runCLIFn(argv, projects, stdout); _readResultsAndExit(results, globalConfig); } const runCLI = async ( argv: Argv, projects: Array, + stdout?: stream$Writable | tty$WriteStream = process.stdout, ): Promise<{results: AggregatedResult, globalConfig: GlobalConfig}> => { let results; // Optimize 'fs' module and make it more compatible with multiple platforms. _patchGlobalFSModule(); - // If we output a JSON object, we can't write anything to stdout, since - // it'll break the JSON structure and it won't be valid. - const outputStream = - argv.json || argv.useStderr ? process.stderr : process.stdout; - - argv.version && _printVersionAndExit(outputStream); + argv.version && _printVersionAndExit(stdout); try { const {globalConfig, configs, hasDeprecationWarnings} = _getConfigs( projects, argv, - outputStream, + stdout, ); + _restoreStdoutIfNeeded(globalConfig, stdout); + + // If we output a JSON object, we can't write anything to stdout, since + // it'll break the JSON structure and it won't be valid. + const outputStream = + argv.json || argv.useStderr ? process.stderr : process.stdout; + await _run( globalConfig, configs, hasDeprecationWarnings, outputStream, (r: AggregatedResult) => (results = r), + stdout, ); if (argv.watch || argv.watchAll) { @@ -98,6 +107,24 @@ const runCLI = async ( } }; +// We hijack `process.stdout` and redirect everything to `process.stderr` +// very early in the Jest process to prevent any of required code to pollute +// stdout with any `console.log`s (this will break JSON output in stdout). If +// we aren't planning to output JSON (or --useStderr) we will reassign +// stdout back to its original value here. +const _restoreStdoutIfNeeded = (globalConfig: GlobalConfig, stdout) => { + if ( + !globalConfig.json && + !globalConfig.listTests && + !globalConfig.useStderr + ) { + Object.defineProperty(process, 'stdout', {value: stdout}); + Object.defineProperty(global, 'console', { + value: new NativeConsole(process.stdout, process.stderr), + }); + } +}; + const _readResultsAndExit = ( result: ?AggregatedResult, globalConfig: GlobalConfig, @@ -144,10 +171,10 @@ const _printDebugInfoAndExitIfNeeded = ( argv, globalConfig, configs, - outputStream, + stdout, ) => { if (argv.debug || argv.showConfig) { - logDebugMessages(globalConfig, configs, outputStream); + logDebugMessages(globalConfig, configs, stdout); } if (argv.showConfig) { process.exit(0); @@ -194,7 +221,7 @@ const _ensureNoDuplicateConfigs = (parsedConfigs, projects) => { const _getConfigs = ( projectsFromCLIArgs: Array, argv: Argv, - outputStream, + stdout, ): { globalConfig: GlobalConfig, configs: Array, @@ -243,7 +270,7 @@ const _getConfigs = ( throw new Error('jest: No configuration found for any project.'); } - _printDebugInfoAndExitIfNeeded(argv, globalConfig, configs, outputStream); + _printDebugInfoAndExitIfNeeded(argv, globalConfig, configs, stdout); return { configs, @@ -288,6 +315,7 @@ const _run = async ( hasDeprecationWarnings, outputStream, onComplete, + stdout, ) => { // Queries to hg/git can take a while, so we need to start the process // as soon as possible, so by the time we need the result it's already there. @@ -307,6 +335,7 @@ const _run = async ( outputStream, hasteMapInstances, changedFilesPromise, + stdout, ) : await _runWithoutWatch( globalConfig, @@ -314,6 +343,7 @@ const _run = async ( outputStream, onComplete, changedFilesPromise, + stdout, ); }; @@ -325,17 +355,30 @@ const _runWatch = async ( outputStream, hasteMapInstances, changedFilesPromise, + stdout, ) => { if (hasDeprecationWarnings) { try { await handleDeprecationWarnings(outputStream, process.stdin); - return watch(globalConfig, contexts, outputStream, hasteMapInstances); + return watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdout, + }); } catch (e) { process.exit(0); } } - return watch(globalConfig, contexts, outputStream, hasteMapInstances); + return watch({ + contexts, + globalConfig, + hasteMapInstances, + outputStream, + stdout, + }); }; const _runWithoutWatch = async ( @@ -344,6 +387,7 @@ const _runWithoutWatch = async ( outputStream, onComplete, changedFilesPromise, + stdout, ) => { const startRun = async () => { if (!globalConfig.listTests) { @@ -356,6 +400,7 @@ const _runWithoutWatch = async ( onComplete, outputStream, startRun, + stdout, testWatcher: new TestWatcher({isWatchMode: false}), }); }; diff --git a/packages/jest-cli/src/reporters/default_reporter.js b/packages/jest-cli/src/reporters/default_reporter.js index 2dca4d492c4a..154f2e88a1e7 100644 --- a/packages/jest-cli/src/reporters/default_reporter.js +++ b/packages/jest-cli/src/reporters/default_reporter.js @@ -8,8 +8,6 @@ * @flow */ -/* global stream$Writable, tty$WriteStream */ - import type {AggregatedResult, TestResult} from 'types/TestResult'; import type {GlobalConfig, Path, ProjectConfig} from 'types/Config'; import type {Test} from 'types/TestRunner'; diff --git a/packages/jest-cli/src/run_jest.js b/packages/jest-cli/src/run_jest.js index 85d1dcf9c066..1bcf54c11bb1 100644 --- a/packages/jest-cli/src/run_jest.js +++ b/packages/jest-cli/src/run_jest.js @@ -121,12 +121,12 @@ const processResults = (runResults, options) => { const filePath = path.resolve(process.cwd(), outputFile); fs.writeFileSync(filePath, JSON.stringify(formatTestResults(runResults))); - process.stdout.write( + options.stdout.write( `Test results written to: ` + `${path.relative(process.cwd(), filePath)}\n`, ); } else { - process.stdout.write(JSON.stringify(formatTestResults(runResults))); + options.stdout.write(JSON.stringify(formatTestResults(runResults))); } } return options.onComplete && options.onComplete(runResults); @@ -140,6 +140,7 @@ const runJest = async ({ startRun, changedFilesPromise, onComplete, + stdout, }: { globalConfig: GlobalConfig, contexts: Array, @@ -148,6 +149,7 @@ const runJest = async ({ startRun: (globalConfig: GlobalConfig) => *, changedFilesPromise: ?ChangedFilesPromise, onComplete: (testResults: AggregatedResult) => any, + stdout: stream$Writable | tty$WriteStream, }) => { const sequencer = new TestSequencer(); let allTests = []; @@ -167,7 +169,7 @@ const runJest = async ({ allTests = sequencer.sort(allTests); if (globalConfig.listTests) { - console.log(JSON.stringify(allTests.map(test => test.path))); + stdout.write(JSON.stringify(allTests.map(test => test.path))); onComplete && onComplete(makeEmptyAggregatedTestResult()); return null; } @@ -202,6 +204,7 @@ const runJest = async ({ isJSON: globalConfig.json, onComplete, outputFile: globalConfig.outputFile, + stdout, testResultsProcessor: globalConfig.testResultsProcessor, }); }; diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index e1e7785b298d..719ff4ac3de4 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -32,17 +32,28 @@ import {KEYS, CLEAR} from './constants'; const isInteractive = process.stdout.isTTY && !isCI; let hasExitListener = false; -const watch = ( - initialGlobalConfig: GlobalConfig, +const watch = ({ + globalConfig: initialGlobalConfig, + contexts, + outputStream, + hasteMapInstances, + stdin, + stdout, +}: { + globalConfig: GlobalConfig, contexts: Array, outputStream: stream$Writable | tty$WriteStream, hasteMapInstances: Array, - stdin?: stream$Readable | tty$ReadStream = process.stdin, -) => { + stdin?: stream$Readable | tty$ReadStream, + stdout: stream$Writable | tty$WriteStream, +}) => { // `globalConfig` will be consantly updated and reassigned as a result of // watch mode interactions. let globalConfig = initialGlobalConfig; + if (!stdin) { + stdin = process.stdin; + } globalConfig = updateGlobalConfig(globalConfig, { mode: globalConfig.watch ? 'watch' : 'watchAll', }); @@ -134,6 +145,7 @@ const watch = ( }, outputStream, startRun, + stdout, testWatcher, }).catch(error => console.error(chalk.red(error.stack))); }; diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index a6814b4995c8..6e27240bbfec 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -62,7 +62,6 @@ module.exports = ({ testURL: 'about:blank', timers: 'real', transformIgnorePatterns: [NODE_MODULES_REGEXP], - useStderr: false, verbose: null, watch: false, watchman: true, diff --git a/types/Config.js b/types/Config.js index e3f38882ef80..f8a49cef6925 100644 --- a/types/Config.js +++ b/types/Config.js @@ -54,7 +54,6 @@ export type DefaultOptions = {| testURL: string, timers: 'real' | 'fake', transformIgnorePatterns: Array, - useStderr: boolean, verbose: ?boolean, watch: boolean, watchman: boolean,