From fa56247144f80a51faf047e384967c5ba0f643d3 Mon Sep 17 00:00:00 2001 From: Conrad Buck Date: Mon, 23 Aug 2021 12:46:25 -0400 Subject: [PATCH] Ensure verbose mode prints output synchronously Fixes #8208 --- .../consoleDebugging.test.ts.snap | 16 +++++++++++++ e2e/__tests__/consoleDebugging.test.ts | 23 ++++++++++++++++++ .../__tests__/console-debugging.test.js | 24 +++++++++++++++++++ e2e/console-debugging/jest.config.js | 6 +++++ e2e/console-debugging/package.json | 1 + e2e/console-debugging/stdout-spy.js | 18 ++++++++++++++ .../jest-reporters/src/DefaultReporter.ts | 24 ++++++++++--------- .../jest-reporters/src/VerboseReporter.ts | 15 ++++++++++++ yarn.lock | 1 + 9 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/consoleDebugging.test.ts.snap create mode 100644 e2e/__tests__/consoleDebugging.test.ts create mode 100644 e2e/console-debugging/__tests__/console-debugging.test.js create mode 100644 e2e/console-debugging/jest.config.js create mode 100644 e2e/console-debugging/package.json create mode 100644 e2e/console-debugging/stdout-spy.js diff --git a/e2e/__tests__/__snapshots__/consoleDebugging.test.ts.snap b/e2e/__tests__/__snapshots__/consoleDebugging.test.ts.snap new file mode 100644 index 000000000000..f5bfd36dfb92 --- /dev/null +++ b/e2e/__tests__/__snapshots__/consoleDebugging.test.ts.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`console debugging with --verbose 1`] = ``; + +exports[`console debugging with --verbose 2`] = ` +PASS __tests__/console-debugging.test.js + ✓ verbose mode prints console output synchronously +`; + +exports[`console debugging with --verbose 3`] = ` +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Snapshots: 1 passed, 1 total +Time: <> +Ran all test suites. +`; diff --git a/e2e/__tests__/consoleDebugging.test.ts b/e2e/__tests__/consoleDebugging.test.ts new file mode 100644 index 000000000000..4aca32566208 --- /dev/null +++ b/e2e/__tests__/consoleDebugging.test.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {wrap} from 'jest-snapshot-serializer-raw'; +import {extractSummary} from '../Utils'; +import runJest from '../runJest'; + +test('console debugging with --verbose', () => { + const {stderr, stdout, exitCode} = runJest('console-debugging', [ + '--noStackTrace', + '--no-cache', + ]); + const {summary, rest} = extractSummary(stderr); + + expect(exitCode).toBe(0); + expect(wrap(stdout)).toMatchSnapshot(); + expect(wrap(rest)).toMatchSnapshot(); + expect(wrap(summary)).toMatchSnapshot(); +}); diff --git a/e2e/console-debugging/__tests__/console-debugging.test.js b/e2e/console-debugging/__tests__/console-debugging.test.js new file mode 100644 index 000000000000..fbeff2a65114 --- /dev/null +++ b/e2e/console-debugging/__tests__/console-debugging.test.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const stdoutWrite = require('../stdout-spy'); + +process.stdout.write = jest.fn(process.stdout.write); + +test('verbose mode prints console output synchronously', () => { + console.log('test'); + + expect(stdoutWrite.text).toMatchInlineSnapshot(` +" console.log + test + + at Object.log (__tests__/console-debugging.test.js:14:11) + +" +`); +}); diff --git a/e2e/console-debugging/jest.config.js b/e2e/console-debugging/jest.config.js new file mode 100644 index 000000000000..7e74c7cc6124 --- /dev/null +++ b/e2e/console-debugging/jest.config.js @@ -0,0 +1,6 @@ +require('./stdout-spy'); + +module.exports = { + testEnvironment: 'node', + verbose: true, +}; diff --git a/e2e/console-debugging/package.json b/e2e/console-debugging/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/e2e/console-debugging/package.json @@ -0,0 +1 @@ +{} diff --git a/e2e/console-debugging/stdout-spy.js b/e2e/console-debugging/stdout-spy.js new file mode 100644 index 000000000000..636965247645 --- /dev/null +++ b/e2e/console-debugging/stdout-spy.js @@ -0,0 +1,18 @@ +const originalStdoutWrite = process.stdout.write; + +global.process.__stdoutWriteMock = global.process.__stdoutWriteMock || null; + +/* + This is a terrible hack to ensure that we monkeyPath stdoutWrite before + the jest reporter does... +*/ +if (!global.process.__stdoutWriteMock) { + global.process.__stdoutWriteMock = (...args) => { + global.process.__stdoutWriteMock.text = args[0]; + originalStdoutWrite(...args); + }; + + process.stdout.write = global.process.__stdoutWriteMock; +} + +module.exports = global.process.__stdoutWriteMock; diff --git a/packages/jest-reporters/src/DefaultReporter.ts b/packages/jest-reporters/src/DefaultReporter.ts index 77b1fbe94b46..d9aeee5b210a 100644 --- a/packages/jest-reporters/src/DefaultReporter.ts +++ b/packages/jest-reporters/src/DefaultReporter.ts @@ -43,16 +43,18 @@ export default class DefaultReporter extends BaseReporter { this._err = process.stderr.write.bind(process.stderr); this._status = new Status(); this._bufferedOutput = new Set(); - this._wrapStdio(process.stdout); - this._wrapStdio(process.stderr); + this.__wrapStdio(process.stdout); + this.__wrapStdio(process.stderr); this._status.onChange(() => { - this._clearStatus(); - this._printStatus(); + this.__clearStatus(); + this.__printStatus(); }); } - private _wrapStdio(stream: NodeJS.WritableStream | NodeJS.WriteStream) { - const originalWrite = stream.write; + protected __wrapStdio( + stream: NodeJS.WritableStream | NodeJS.WriteStream, + ): void { + const write = stream.write.bind(stream); let buffer: Array = []; let timeout: NodeJS.Timeout | null = null; @@ -62,11 +64,11 @@ export default class DefaultReporter extends BaseReporter { buffer = []; // This is to avoid conflicts between random output and status text - this._clearStatus(); + this.__clearStatus(); if (string) { - originalWrite.call(stream, string); + write(string); } - this._printStatus(); + this.__printStatus(); this._bufferedOutput.delete(flushBufferedOutput); }; @@ -103,7 +105,7 @@ export default class DefaultReporter extends BaseReporter { } } - private _clearStatus() { + protected __clearStatus(): void { if (isInteractive) { if (this._globalConfig.useStderr) { this._err(this._clear); @@ -113,7 +115,7 @@ export default class DefaultReporter extends BaseReporter { } } - private _printStatus() { + protected __printStatus(): void { const {content, clear} = this._status.get(); this._clear = clear; if (isInteractive) { diff --git a/packages/jest-reporters/src/VerboseReporter.ts b/packages/jest-reporters/src/VerboseReporter.ts index 5d7de322f330..dfe5901ef7bd 100644 --- a/packages/jest-reporters/src/VerboseReporter.ts +++ b/packages/jest-reporters/src/VerboseReporter.ts @@ -29,6 +29,21 @@ export default class VerboseReporter extends DefaultReporter { this._globalConfig = globalConfig; } + // Verbose mode is for debugging. Buffering of output is undesirable. + // See https://github.com/facebook/jest/issues/8208 + protected __wrapStdio( + stream: NodeJS.WritableStream | NodeJS.WriteStream, + ): void { + const write = stream.write.bind(stream); + + stream.write = (chunk: string) => { + this.__clearStatus(); + write(chunk); + this.__printStatus(); + return true; + }; + } + static filterTestResults( testResults: Array, ): Array { diff --git a/yarn.lock b/yarn.lock index 94e28baf81a6..7996e76ba4e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2654,6 +2654,7 @@ __metadata: "@types/istanbul-lib-report": ^3.0.0 "@types/istanbul-lib-source-maps": ^4.0.0 "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" "@types/node-notifier": ^8.0.0 chalk: ^4.0.0 collect-v8-coverage: ^1.0.0