From a5bd76eb99838e19d133209d1e408d2a64682770 Mon Sep 17 00:00:00 2001 From: Rico Hermans Date: Mon, 13 Jan 2025 16:15:46 +0100 Subject: [PATCH] chore: re-enable console output in tests (#32883) Remove `silent: true` from the test config. `console.log()` is a time-honored debugging tradition, and the `silent: true` config makes that not work in a non-obvious way. A nice behavior that many other testing frameworks have is to only print the console output of tests that fail. Use the "Jest Environments" feature to build an environment that replaces `console.log()` etc and buffers their output to only print it for failing tests. This achieves the same goals of not polluting the terminal too much while running tests, while still allowing the ability to see the output of `console.log`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk-lib/jest.config.js | 2 + packages/aws-cdk-lib/package.json | 1 + .../testhelpers/jest-bufferedconsole.ts | 75 +++++++++++++++++++ .../cdk-build-tools/config/jest.config.js | 6 -- 4 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts diff --git a/packages/aws-cdk-lib/jest.config.js b/packages/aws-cdk-lib/jest.config.js index ff2f907fa96ec..861dd4bb1731a 100644 --- a/packages/aws-cdk-lib/jest.config.js +++ b/packages/aws-cdk-lib/jest.config.js @@ -19,4 +19,6 @@ module.exports = { statements: 55, }, }, + + testEnvironment: './testhelpers/jest-bufferedconsole.ts', }; diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index b096ede4cdda3..5ba109bcaaa11 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -176,6 +176,7 @@ "fast-check": "^3.22.0", "jest": "^29.7.0", "jest-each": "^29.7.0", + "jest-environment-node": "^29.7.0", "lambda-tester": "^4.0.1", "lodash": "^4.17.21", "nock": "^13.5.5", diff --git a/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts b/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts new file mode 100644 index 0000000000000..c66ededbdaff0 --- /dev/null +++ b/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts @@ -0,0 +1,75 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/** + * A Jest environment that buffers outputs to `console.log()` and only shows it for failing tests. + */ +import type { EnvironmentContext, JestEnvironment, JestEnvironmentConfig } from '@jest/environment'; +import { Circus } from '@jest/types'; +import { TestEnvironment as NodeEnvironment } from 'jest-environment-node'; + +interface ConsoleMessage { + type: 'log' | 'error' | 'warn' | 'info' | 'debug'; + message: string; +} + +export default class TestEnvironment extends NodeEnvironment implements JestEnvironment { + private log = new Array(); + private originalConsole!: typeof console; + + constructor(config: JestEnvironmentConfig, context: EnvironmentContext) { + super(config, context); + + // We need to set the event handler by assignment in the constructor, + // because if we declare it as an async member TypeScript's type derivation + // doesn't work properly. + (this as JestEnvironment).handleTestEvent = (async (event, _state) => { + if (event.name === 'test_done' && event.test.errors.length > 0 && this.log.length > 0) { + this.originalConsole.log(`[Console output] ${fullTestName(event.test)}\n`); + for (const item of this.log) { + this.originalConsole[item.type](' ' + item.message); + } + this.originalConsole.log('\n'); + } + + if (event.name === 'test_done') { + this.log = []; + } + }) satisfies Circus.EventHandler; + } + + async setup() { + await super.setup(); + + this.log = []; + this.originalConsole = console; + + this.global.console = { + ...console, + log: (message) => this.log.push({ type: 'log', message }), + error: (message) => this.log.push({ type: 'error', message }), + warn: (message) => this.log.push({ type: 'warn', message }), + info: (message) => this.log.push({ type: 'info', message }), + debug: (message) => this.log.push({ type: 'debug', message }), + }; + } + + async teardown() { + this.global.console = this.originalConsole; + await super.teardown(); + } +} + +// DescribeBlock is not exported from `@jest/types`, so we need to build the parts we are interested in +type TestDescription = PartialBy, 'parent'>; + +// Utility type to make specific fields optional +type PartialBy = Omit & Partial> + +function fullTestName(test: TestDescription) { + let ret = test.name; + while (test.parent != null && test.parent.name !== 'ROOT_DESCRIBE_BLOCK') { + ret = test.parent.name + ' › ' + fullTestName; + test = test.parent; + } + return ret; +} + diff --git a/tools/@aws-cdk/cdk-build-tools/config/jest.config.js b/tools/@aws-cdk/cdk-build-tools/config/jest.config.js index 28224e03b6695..c344ffe2f34de 100644 --- a/tools/@aws-cdk/cdk-build-tools/config/jest.config.js +++ b/tools/@aws-cdk/cdk-build-tools/config/jest.config.js @@ -35,10 +35,4 @@ module.exports = { ], coveragePathIgnorePatterns: ['\\.generated\\.[jt]s$', '/test/', '.warnings.jsii.js$', '/node_modules/'], reporters: ['default', ['jest-junit', { suiteName: 'jest tests', outputDirectory: 'coverage' }]], - /** - * This will still show us helpful information when running tests but remove console statements. - * The exception is when we use our custom logger in the CLI or when other processes are spun up - * within tests. It has no impact there. - */ - silent: true, };