Skip to content

Commit 1d7d1fc

Browse files
authored
chore(cli): also suppress test logging for CLI (#32952)
The CLI tests are also quite noisy. Suppress logging for those as well by using the `jest-bufferedconsole` environment. Enhance the buffered-console environments by also capturing writes to `process.stdout` and `process.stderr`. Yes, the files are copy/pasted between the CLI and the library. I don't see an easy way around this, and the code is going to be lifted into a separate repo soon anyway. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent c7d6fb6 commit 1d7d1fc

File tree

4 files changed

+127
-2
lines changed

4 files changed

+127
-2
lines changed

packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ interface ConsoleMessage {
1313

1414
export default class TestEnvironment extends NodeEnvironment implements JestEnvironment<unknown> {
1515
private log = new Array<ConsoleMessage>();
16+
1617
private originalConsole!: typeof console;
18+
private originalStdoutWrite!: typeof process.stdout.write;
19+
private originalStderrWrite!: typeof process.stderr.write;
1720

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

4245
this.log = [];
4346
this.originalConsole = console;
47+
this.originalStdoutWrite = process.stdout.write;
48+
this.originalStderrWrite = process.stderr.write;
4449

4550
this.global.console = {
4651
...console,
@@ -50,10 +55,30 @@ export default class TestEnvironment extends NodeEnvironment implements JestEnvi
5055
info: (message) => this.log.push({ type: 'info', message }),
5156
debug: (message) => this.log.push({ type: 'debug', message }),
5257
};
58+
59+
const self = this;
60+
process.stdout.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
61+
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
62+
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
63+
self.log.push({ type: 'log', message: message.replace(/\n$/, '') });
64+
if (typeof enccb === 'function') {
65+
enccb();
66+
}
67+
} as any;
68+
process.stderr.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
69+
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
70+
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
71+
self.log.push({ type: 'error', message: message.replace(/\n$/, '') });
72+
if (typeof enccb === 'function') {
73+
enccb();
74+
}
75+
} as any;
5376
}
5477

5578
async teardown() {
5679
this.global.console = this.originalConsole;
80+
process.stdout.write = this.originalStdoutWrite;
81+
process.stderr.write = this.originalStderrWrite;
5782
await super.teardown();
5883
}
5984
}
@@ -72,4 +97,3 @@ function fullTestName(test: TestDescription) {
7297
}
7398
return ret;
7499
}
75-

packages/aws-cdk/jest.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const config = {
3131
// fail because they rely on shared mutable state left by other tests
3232
// (files on disk, global mocks, etc).
3333
randomize: true,
34+
testEnvironment: './test/jest-bufferedconsole.ts',
3435
};
3536

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

43-
module.exports = config;
44+
module.exports = config;

packages/aws-cdk/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"constructs": "^10.0.0",
9393
"fast-check": "^3.22.0",
9494
"jest": "^29.7.0",
95+
"jest-environment-node": "^29.7.0",
9596
"jest-mock": "^29.7.0",
9697
"madge": "^5.0.2",
9798
"make-runnable": "^1.4.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
/**
3+
* A Jest environment that buffers outputs to `console.log()` and only shows it for failing tests.
4+
*/
5+
import type { EnvironmentContext, JestEnvironment, JestEnvironmentConfig } from '@jest/environment';
6+
import { Circus } from '@jest/types';
7+
import { TestEnvironment as NodeEnvironment } from 'jest-environment-node';
8+
9+
interface ConsoleMessage {
10+
type: 'log' | 'error' | 'warn' | 'info' | 'debug';
11+
message: string;
12+
}
13+
14+
export default class TestEnvironment extends NodeEnvironment implements JestEnvironment<unknown> {
15+
private log = new Array<ConsoleMessage>();
16+
17+
private originalConsole!: typeof console;
18+
private originalStdoutWrite!: typeof process.stdout.write;
19+
private originalStderrWrite!: typeof process.stderr.write;
20+
21+
constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
22+
super(config, context);
23+
24+
// We need to set the event handler by assignment in the constructor,
25+
// because if we declare it as an async member TypeScript's type derivation
26+
// doesn't work properly.
27+
(this as JestEnvironment<unknown>).handleTestEvent = (async (event, _state) => {
28+
if (event.name === 'test_done' && event.test.errors.length > 0 && this.log.length > 0) {
29+
this.originalConsole.log(`[Console output] ${fullTestName(event.test)}\n`);
30+
for (const item of this.log) {
31+
this.originalConsole[item.type](' ' + item.message);
32+
}
33+
this.originalConsole.log('\n');
34+
}
35+
36+
if (event.name === 'test_done') {
37+
this.log = [];
38+
}
39+
}) satisfies Circus.EventHandler;
40+
}
41+
42+
async setup() {
43+
await super.setup();
44+
45+
this.log = [];
46+
this.originalConsole = console;
47+
this.originalStdoutWrite = process.stdout.write;
48+
this.originalStderrWrite = process.stderr.write;
49+
50+
this.global.console = {
51+
...console,
52+
log: (message) => this.log.push({ type: 'log', message }),
53+
error: (message) => this.log.push({ type: 'error', message }),
54+
warn: (message) => this.log.push({ type: 'warn', message }),
55+
info: (message) => this.log.push({ type: 'info', message }),
56+
debug: (message) => this.log.push({ type: 'debug', message }),
57+
};
58+
59+
const self = this;
60+
process.stdout.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
61+
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
62+
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
63+
self.log.push({ type: 'log', message: message.replace(/\n$/, '') });
64+
if (typeof enccb === 'function') {
65+
enccb();
66+
}
67+
} as any;
68+
process.stderr.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void {
69+
const encoding = typeof enccb === 'string' ? enccb : 'utf-8';
70+
const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk;
71+
self.log.push({ type: 'error', message: message.replace(/\n$/, '') });
72+
if (typeof enccb === 'function') {
73+
enccb();
74+
}
75+
} as any;
76+
}
77+
78+
async teardown() {
79+
this.global.console = this.originalConsole;
80+
process.stdout.write = this.originalStdoutWrite;
81+
process.stderr.write = this.originalStderrWrite;
82+
await super.teardown();
83+
}
84+
}
85+
86+
// DescribeBlock is not exported from `@jest/types`, so we need to build the parts we are interested in
87+
type TestDescription = PartialBy<Pick<Circus.TestEntry, 'name' | 'parent'>, 'parent'>;
88+
89+
// Utility type to make specific fields optional
90+
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
91+
92+
function fullTestName(test: TestDescription) {
93+
let ret = test.name;
94+
while (test.parent != null && test.parent.name !== 'ROOT_DESCRIBE_BLOCK') {
95+
ret = test.parent.name + ' › ' + fullTestName;
96+
test = test.parent;
97+
}
98+
return ret;
99+
}

0 commit comments

Comments
 (0)