Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(cli): also suppress test logging for CLI #32952

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ interface ConsoleMessage {

export default class TestEnvironment extends NodeEnvironment implements JestEnvironment<unknown> {
private log = new Array<ConsoleMessage>();

private originalConsole!: typeof console;
private originalStdoutWrite!: typeof process.stdout.write;
private originalStderrWrite!: typeof process.stderr.write;

constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
super(config, context);
Expand Down Expand Up @@ -41,6 +44,8 @@ export default class TestEnvironment extends NodeEnvironment implements JestEnvi

this.log = [];
this.originalConsole = console;
this.originalStdoutWrite = process.stdout.write;
this.originalStderrWrite = process.stderr.write;

this.global.console = {
...console,
Expand All @@ -50,10 +55,30 @@ export default class TestEnvironment extends NodeEnvironment implements JestEnvi
info: (message) => this.log.push({ type: 'info', message }),
debug: (message) => this.log.push({ type: 'debug', message }),
};

const self = this;
process.stdout.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
self.log.push({ type: 'log', message: message.replace(/\n$/, '') });
if (typeof enccb === 'function') {
enccb();
}
} as any;
process.stderr.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
self.log.push({ type: 'error', message: message.replace(/\n$/, '') });
if (typeof enccb === 'function') {
enccb();
}
} as any;
}

async teardown() {
this.global.console = this.originalConsole;
process.stdout.write = this.originalStdoutWrite;
process.stderr.write = this.originalStderrWrite;
await super.teardown();
}
}
Expand All @@ -72,4 +97,3 @@ function fullTestName(test: TestDescription) {
}
return ret;
}

3 changes: 2 additions & 1 deletion packages/aws-cdk/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const config = {
// fail because they rely on shared mutable state left by other tests
// (files on disk, global mocks, etc).
randomize: true,
testEnvironment: './test/jest-bufferedconsole.ts',
};

// Disable coverage running in the VSCode debug terminal: we never want coverage
Expand All @@ -40,4 +41,4 @@ if (process.env.VSCODE_INJECTION) {
config.collectCoverage = false;
}

module.exports = config;
module.exports = config;
1 change: 1 addition & 0 deletions packages/aws-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"constructs": "^10.0.0",
"fast-check": "^3.22.0",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"jest-mock": "^29.7.0",
"madge": "^5.0.2",
"make-runnable": "^1.4.1",
Expand Down
99 changes: 99 additions & 0 deletions packages/aws-cdk/test/jest-bufferedconsole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* 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<unknown> {
private log = new Array<ConsoleMessage>();

private originalConsole!: typeof console;
private originalStdoutWrite!: typeof process.stdout.write;
private originalStderrWrite!: typeof process.stderr.write;

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<unknown>).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.originalStdoutWrite = process.stdout.write;
this.originalStderrWrite = process.stderr.write;

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

const self = this;
process.stdout.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
self.log.push({ type: 'log', message: message.replace(/\n$/, '') });
if (typeof enccb === 'function') {
enccb();
}
} as any;
process.stderr.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
self.log.push({ type: 'error', message: message.replace(/\n$/, '') });
if (typeof enccb === 'function') {
enccb();
}
} as any;
}

async teardown() {
this.global.console = this.originalConsole;
process.stdout.write = this.originalStdoutWrite;
process.stderr.write = this.originalStderrWrite;
await super.teardown();
}
}

// DescribeBlock is not exported from `@jest/types`, so we need to build the parts we are interested in
type TestDescription = PartialBy<Pick<Circus.TestEntry, 'name' | 'parent'>, 'parent'>;

// Utility type to make specific fields optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

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