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, };