Skip to content

Commit

Permalink
Merge branch 'main' into fix-iotevents-alpha
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Jan 15, 2025
2 parents 8c374f1 + 1d7d1fc commit 2e22ee3
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 2 deletions.
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;
}

0 comments on commit 2e22ee3

Please sign in to comment.