From d0d7afb3ee2f2ee8f8afdd1b9ea30d5435eb1894 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Thu, 12 Oct 2023 17:39:50 +0300 Subject: [PATCH 1/5] wip: extend from reporter --- e2e/reporters/{recorder.js => recorder.ts} | 5 ++ src/environment-decorator.ts | 8 +-- src/environment-hooks.ts | 78 +++++++++++++++++++--- src/environment-jsdom.ts | 2 +- src/environment-node.ts | 2 +- 5 files changed, 78 insertions(+), 17 deletions(-) rename e2e/reporters/{recorder.js => recorder.ts} (96%) diff --git a/e2e/reporters/recorder.js b/e2e/reporters/recorder.ts similarity index 96% rename from e2e/reporters/recorder.js rename to e2e/reporters/recorder.ts index a6501d5..8e454f4 100644 --- a/e2e/reporters/recorder.js +++ b/e2e/reporters/recorder.ts @@ -89,6 +89,11 @@ class E2eRecorderReporter extends JestMetadataReporter { await debugUtils.aggregateLogs(); } } + + static onTestEnvironmentCreate(env) { + console.log('I AM ALIVE'); + console.log(' ..--..'); + } } function sleep(ms) { diff --git a/src/environment-decorator.ts b/src/environment-decorator.ts index 92d7a51..cb1c145 100644 --- a/src/environment-decorator.ts +++ b/src/environment-decorator.ts @@ -2,7 +2,7 @@ import type { JestEnvironment } from '@jest/environment'; import type { Circus } from '@jest/types'; import { - ForwardedCircusEvent, + TestEnvironmentEvent, getEmitter, onHandleTestEvent, onTestEnvironmentCreate, @@ -11,10 +11,10 @@ import { } from './environment-hooks'; import type { ReadonlyAsyncEmitter } from './types'; -export { ForwardedCircusEvent } from './environment-hooks'; +export { TestEnvironmentEvent } from './environment-hooks'; export type WithEmitter = E & { - readonly testEvents: ReadonlyAsyncEmitter; + readonly testEvents: ReadonlyAsyncEmitter; }; /** @@ -89,7 +89,7 @@ export function WithMetadata( onTestEnvironmentCreate(this, args[0], args[1]); } - protected get testEvents(): ReadonlyAsyncEmitter { + protected get testEvents(): ReadonlyAsyncEmitter { return getEmitter(this); } diff --git a/src/environment-hooks.ts b/src/environment-hooks.ts index 80148f0..b87d7d3 100644 --- a/src/environment-hooks.ts +++ b/src/environment-hooks.ts @@ -5,7 +5,8 @@ import { JestMetadataError } from './errors'; import { realm, injectRealmIntoSandbox } from './realms'; import { jestUtils, SemiAsyncEmitter } from './utils'; -const emitterMap: WeakMap> = new WeakMap(); +const emitterMap: WeakMap> = new WeakMap(); +const configMap: WeakMap = new WeakMap(); export function onTestEnvironmentCreate( jestEnvironment: JestEnvironment, @@ -52,13 +53,15 @@ export function onTestEnvironmentCreate( const flushHandler = () => realm.ipc.flush(); - const emitter = new SemiAsyncEmitter('environment', [ + const emitter = new SemiAsyncEmitter('environment', [ 'start_describe_definition', 'finish_describe_definition', 'add_hook', 'add_test', 'error', ]) + .on('test_environment_setup', startIpc, -1) + .on('test_environment_teardown', stopIpc, Number.MAX_SAFE_INTEGER) .on('setup', testEventHandler, -1) .on('include_test_location_in_result', testEventHandler, -1) .on('start_describe_definition', testEventHandler, -1) @@ -88,24 +91,28 @@ export function onTestEnvironmentCreate( .on('teardown', testEventHandler, Number.MAX_SAFE_INTEGER); emitterMap.set(jestEnvironment, emitter); + configMap.set(jestEnvironment, jestEnvironmentConfig); } -export type ForwardedCircusEvent = { +export type TestEnvironmentEvent = + | { + type: 'test_environment_setup' | 'test_environment_teardown'; + } + | ForwardedCircusEvent; + +type ForwardedCircusEvent = { type: E['name']; event: E; state: Circus.State; }; -export async function onTestEnvironmentSetup(_env: JestEnvironment): Promise { - if (realm.type === 'child_process') { - await realm.ipc.start(); - } +export async function onTestEnvironmentSetup(env: JestEnvironment): Promise { + await initReporters(env); + await getEmitter(env).emit({ type: 'test_environment_setup' }); } -export async function onTestEnvironmentTeardown(_env: JestEnvironment): Promise { - if (realm.type === 'child_process') { - await realm.ipc.stop(); - } +export async function onTestEnvironmentTeardown(env: JestEnvironment): Promise { + await getEmitter(env).emit({ type: 'test_environment_teardown' }); } /** @@ -118,6 +125,9 @@ export const onHandleTestEvent = ( state: Circus.State, ): void | Promise => getEmitter(env).emit({ type: event.name, event, state }); +/** + * Get the environment event emitter by the environment reference. + */ export const getEmitter = (env: JestEnvironment) => { const emitter = emitterMap.get(env); if (!emitter) { @@ -128,3 +138,49 @@ export const getEmitter = (env: JestEnvironment) => { return emitter; }; + +/** + * Get the environment configuration by the environment reference. + */ +export const getConfig = (env: JestEnvironment) => { + const config = configMap.get(env); + if (!config) { + throw new JestMetadataError( + 'Environment config is not found. Most likely, you are using a non-valid environment reference.', + ); + } + + return config; +}; + +async function initReporters(env: JestEnvironment) { + const reporterModules = (getConfig(env)?.globalConfig?.reporters ?? []).map((r) => r[0]); + const reporterExports = await Promise.all( + reporterModules.map((m) => { + try { + return import(m); + } catch (error: unknown) { + // TODO: log this to trace + console.warn(`[jest-metadata] Failed to import reporter module "${m}"`, error); + return; + } + }), + ); + + for (const reporterExport of reporterExports) { + const ReporterClass = reporterExport?.default ?? reporterExport; + ReporterClass?.onTestEnvironmentCreate?.(env); + } +} + +async function startIpc() { + if (realm.type === 'child_process') { + await realm.ipc.start(); + } +} + +async function stopIpc() { + if (realm.type === 'child_process') { + await realm.ipc.stop(); + } +} diff --git a/src/environment-jsdom.ts b/src/environment-jsdom.ts index 71f5910..2bff8ba 100644 --- a/src/environment-jsdom.ts +++ b/src/environment-jsdom.ts @@ -1,6 +1,6 @@ import JestEnvironmentJsdom from 'jest-environment-jsdom'; import { WithMetadata } from './environment-decorator'; -export { ForwardedCircusEvent } from './environment-hooks'; +export { TestEnvironmentEvent } from './environment-hooks'; export const TestEnvironment = WithMetadata(JestEnvironmentJsdom); export default TestEnvironment; diff --git a/src/environment-node.ts b/src/environment-node.ts index b159b67..024ac6f 100644 --- a/src/environment-node.ts +++ b/src/environment-node.ts @@ -1,6 +1,6 @@ import JestEnvironmentNode from 'jest-environment-node'; import { WithMetadata } from './environment-decorator'; -export { ForwardedCircusEvent } from './environment-hooks'; +export { TestEnvironmentEvent } from './environment-hooks'; export const TestEnvironment = WithMetadata(JestEnvironmentNode); export default TestEnvironment; From 8c659c441c8c851ec65842d8fc81411c3e5ec279 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Sun, 5 Nov 2023 11:32:55 +0200 Subject: [PATCH 2/5] wip --- environment-hooks.js | 2 - package.json | 8 +- src/environment-decorator.ts | 206 ++++++++++++++++------------------- src/environment-hooks.ts | 191 -------------------------------- src/environment-jsdom.ts | 1 - src/environment-node.ts | 1 - src/utils/logger.ts | 44 +++----- 7 files changed, 112 insertions(+), 341 deletions(-) delete mode 100644 environment-hooks.js delete mode 100644 src/environment-hooks.ts diff --git a/environment-hooks.js b/environment-hooks.js deleted file mode 100644 index ab5934e..0000000 --- a/environment-hooks.js +++ /dev/null @@ -1,2 +0,0 @@ -/* Jest 27 fallback */ -module.exports = require('./dist/environment-hooks'); diff --git a/package.json b/package.json index 9f9f60f..c3143dc 100644 --- a/package.json +++ b/package.json @@ -30,11 +30,6 @@ "require": "./dist/environment-decorator.js", "types": "./dist/environment-decorator.d.ts" }, - "./environment-hooks": { - "import": "./dist/environment-hooks.js", - "require": "./dist/environment-hooks.js", - "types": "./dist/environment-hooks.d.ts" - }, "./environment-jsdom": { "import": "./dist/environment-jsdom.js", "require": "./dist/environment-jsdom.js", @@ -87,10 +82,11 @@ }, "homepage": "https://github.com/wix-incubator/jest-metadata#readme", "dependencies": { - "bunyamin": "^1.1.1", + "bunyamin": "^1.4.0", "bunyan": "^2.0.5", "bunyan-debug-stream": "^3.1.0", "funpermaproxy": "^1.1.0", + "jest-environment-emit": "^1.0.0-alpha.2", "lodash.merge": "^4.6.2", "node-ipc": "9.2.1", "strip-ansi": "^6.0.0", diff --git a/src/environment-decorator.ts b/src/environment-decorator.ts index cb1c145..cca6aaf 100644 --- a/src/environment-decorator.ts +++ b/src/environment-decorator.ts @@ -1,124 +1,108 @@ -import type { JestEnvironment } from '@jest/environment'; -import type { Circus } from '@jest/types'; +import { inspect } from 'util'; +import type { EmitterSubscriptionContext, TestEnvironmentCircusEvent } from 'jest-environment-emit'; +import WithEmitter from 'jest-environment-emit'; +import { JestMetadataError } from './errors'; +import { detectDuplicateRealms, injectRealmIntoSandbox, realm } from './realms'; +import { jestUtils, logger } from './utils'; -import { - TestEnvironmentEvent, - getEmitter, - onHandleTestEvent, - onTestEnvironmentCreate, - onTestEnvironmentSetup, - onTestEnvironmentTeardown, -} from './environment-hooks'; -import type { ReadonlyAsyncEmitter } from './types'; +const log = logger.child({ cat: 'environment', tid: 'environment' }); -export { TestEnvironmentEvent } from './environment-hooks'; +WithEmitter.subscribe((context: EmitterSubscriptionContext) => { + const jestEnvironment = context.env; + const jestEnvironmentConfig = context.config; + const environmentContext = context.context; -export type WithEmitter = E & { - readonly testEvents: ReadonlyAsyncEmitter; -}; + detectDuplicateRealms(true); + injectRealmIntoSandbox(jestEnvironment.global, realm); + const testFilePath = environmentContext.testPath; + realm.environmentHandler.handleEnvironmentCreated(testFilePath); + realm.events.add(realm.setEmitter); -/** - * Decorator for a given JestEnvironment subclass that extends - * {@link JestEnvironment#constructor}, {@link JestEnvironment#global}, - * {@link JestEnvironment#setup}, and {@link JestEnvironment#handleTestEvent} - * and {@link JestEnvironment#teardown} in a way that is compatible with - * jest-metadata. - * - * You can use this decorator to extend a base JestEnvironment class inside - * your own environment class in a declarative way. If you prefer to control - * the integration with {@link module:jest-metadata} yourself, you can use - * low-level hooks from {@link module:jest-metadata/environment-hooks}. - * @param JestEnvironmentClass - Jest environment subclass to decorate - * @returns a decorated Jest environment subclass, e.g. `WithMetadata(JestEnvironmentNode)` - * @example - * ```javascript - * import WithMetadata from 'jest-metadata/environment-decorator'; - * - * class MyEnvironment extends WithMetadata(JestEnvironmentNode) { - * constructor(config, context) { - * super(config, context); - * - * this.testEvents - * .on('setup', async ({ event, state }) => { ... }) - * .on('include_test_location_in_result', ({ event, state }) => { ... }) - * .on('start_describe_definition', ({ event, state }) => { ... }) - * .on('finish_describe_definition', ({ event, state }) => { ... }) - * .on('add_hook', ({ event, state }) => { ... }) - * .on('add_test', ({ event, state }) => { ... }) - * .on('hook_failure', async ({ event, state }) => { ... }) - * .on('hook_start', async ({ event, state }) => { ... }) - * .on('hook_success', async ({ event, state }) => { ... }) - * .on('run_finish', async ({ event, state }) => { ... }) - * .on('run_start', async ({ event, state }) => { ... }) - * .on('run_describe_start', async ({ event, state }) => { ... }) - * .on('test_start', async ({ event, state }) => { ... }) - * .on('test_retry', async ({ event, state }) => { ... }) - * .on('test_skip', async ({ event, state }) => { ... }) - * .on('test_todo', async ({ event, state }) => { ... }) - * .on('test_fn_start', async ({ event, state }) => { ... }) - * .on('test_fn_failure', async ({ event, state }) => { ... }) - * .on('test_fn_success', async ({ event, state }) => { ... }) - * .on('test_done', async ({ event, state }) => { ... }) - * .on('run_describe_finish', async ({ event, state }) => { ... }) - * .on('teardown', async ({ event, state }) => { ... }) - * .on('error', ({ event, state }) => { ... }); - * } - * - * async setup() { - * await super.setup(); - * // ... your custom logic - * } - * - * async teardown() { - * // ... your custom logic - * await super.teardown(); - * } - * } - * ``` - */ -export function WithMetadata( - JestEnvironmentClass: new (...args: any[]) => E, -): new (...args: any[]) => WithEmitter { - const compositeName = `WithMetadata(${JestEnvironmentClass.name})`; + if (!realm.globalMetadata.hasTestFileMetadata(testFilePath)) { + realm.coreEmitter.emit({ + type: 'add_test_file', + testFilePath, + }); - return { - // @ts-expect-error TS2415: Class '[`${compositeName}`]' incorrectly extends base class 'E'. - [`${compositeName}`]: class extends JestEnvironmentClass { - constructor(...args: any[]) { - super(...args); - onTestEnvironmentCreate(this, args[0], args[1]); - } + if (realm.type === 'parent_process') { + const { globalConfig } = jestEnvironmentConfig; + const first = (r: T[]) => r[0]; + const hint = globalConfig + ? ` "reporters": ${inspect(globalConfig.reporters?.map(first))}\n` + : ''; // Jest 27 fallback - protected get testEvents(): ReadonlyAsyncEmitter { - return getEmitter(this); - } + const message = + `Cannot use a metadata test environment without a metadata server.\n` + + `Please check that at least one of the reporters in your Jest config inherits from "jest-metadata/reporter".\n` + + hint; - async setup() { - await super.setup(); - await onTestEnvironmentSetup(this); + if ( + globalConfig && + (jestUtils.isSingleWorker(globalConfig) || jestUtils.isInsideIDE(globalConfig)) + ) { + log.warn(message); + } else { + log.debug(message); + throw new JestMetadataError(message); } + } + } - // @ts-expect-error TS2415: The base class has an arrow function, but this can be a method - handleTestEvent(event: Circus.Event, state: Circus.State): void | Promise { - const maybePromise = (super.handleTestEvent as JestEnvironment['handleTestEvent'])?.( - event as any, - state, - ); + const testEventHandler = ({ event, state }: TestEnvironmentCircusEvent) => { + realm.environmentHandler.handleTestEvent(event, state); + }; - return typeof maybePromise?.then === 'function' - ? maybePromise.then(() => onHandleTestEvent(this, event, state)) - : onHandleTestEvent(this, event, state); - } + const flushHandler = () => realm.ipc.flush(); - async teardown() { - await super.teardown(); - await onTestEnvironmentTeardown(this); - } - }, - }[compositeName] as unknown as new (...args: any[]) => WithEmitter; -} + context.testEvents + .on( + 'test_environment_setup', + async function () { + if (realm.type === 'child_process') { + await realm.ipc.start(); + } + }, + -1, + ) + .on( + 'test_environment_teardown', + async function () { + detectDuplicateRealms(false); + + if (realm.type === 'child_process') { + await realm.ipc.stop(); + } + }, + Number.MAX_SAFE_INTEGER, + ) + .on('setup', testEventHandler, -1) + .on('include_test_location_in_result', testEventHandler, -1) + .on('start_describe_definition', testEventHandler, -1) + .on('finish_describe_definition', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('add_hook', testEventHandler, -1) + .on('add_test', testEventHandler, -1) + .on('run_start', testEventHandler, -1) + .on('run_start', flushHandler, Number.MAX_SAFE_INTEGER) + .on('run_describe_start', testEventHandler, -1) + .on('hook_failure', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('hook_start', testEventHandler, -1) + .on('hook_success', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_start', testEventHandler, -1) + .on('test_start', flushHandler, Number.MAX_SAFE_INTEGER) + .on('test_started', testEventHandler, -1) + .on('test_retry', testEventHandler, -1) + .on('test_skip', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_todo', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_fn_start', testEventHandler, -1) + .on('test_fn_failure', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_fn_success', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_done', testEventHandler, Number.MAX_SAFE_INTEGER - 1) + .on('test_done', flushHandler, Number.MAX_SAFE_INTEGER) + .on('run_describe_finish', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('run_finish', testEventHandler, Number.MAX_SAFE_INTEGER - 1) + .on('run_finish', flushHandler, Number.MAX_SAFE_INTEGER) + .on('teardown', testEventHandler, Number.MAX_SAFE_INTEGER); +}); -/** - * @inheritDoc - */ +export const WithMetadata = WithEmitter; export default WithMetadata; diff --git a/src/environment-hooks.ts b/src/environment-hooks.ts deleted file mode 100644 index 3730a8a..0000000 --- a/src/environment-hooks.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { inspect } from 'util'; -import type { EnvironmentContext, JestEnvironment, JestEnvironmentConfig } from '@jest/environment'; -import type { Circus } from '@jest/types'; -import { JestMetadataError } from './errors'; -import { injectRealmIntoSandbox, realm, detectDuplicateRealms } from './realms'; -import { logger, jestUtils, SemiAsyncEmitter } from './utils'; - -const log = logger.child({ cat: 'environment', tid: 'environment' }); -const emitterMap: WeakMap> = new WeakMap(); -const configMap: WeakMap = new WeakMap(); - -export function onTestEnvironmentCreate( - jestEnvironment: JestEnvironment, - jestEnvironmentConfig: JestEnvironmentConfig, - environmentContext: EnvironmentContext, -): void { - detectDuplicateRealms(true); - injectRealmIntoSandbox(jestEnvironment.global, realm); - const testFilePath = environmentContext.testPath; - realm.environmentHandler.handleEnvironmentCreated(testFilePath); - realm.events.add(realm.setEmitter); - - if (!realm.globalMetadata.hasTestFileMetadata(testFilePath)) { - realm.coreEmitter.emit({ - type: 'add_test_file', - testFilePath, - }); - - if (realm.type === 'parent_process') { - const { globalConfig } = jestEnvironmentConfig; - const first = (r: T[]) => r[0]; - const hint = globalConfig - ? ` "reporters": ${inspect(globalConfig.reporters?.map(first))}\n` - : ''; // Jest 27 fallback - - const message = - `Cannot use a metadata test environment without a metadata server.\n` + - `Please check that at least one of the reporters in your Jest config inherits from "jest-metadata/reporter".\n` + - hint; - - if ( - globalConfig && - (jestUtils.isSingleWorker(globalConfig) || jestUtils.isInsideIDE(globalConfig)) - ) { - log.warn(message); - } else { - log.debug(message); - throw new JestMetadataError(message); - } - } - } - - const testEventHandler = ({ event, state }: ForwardedCircusEvent) => { - realm.environmentHandler.handleTestEvent(event, state); - }; - - const flushHandler = () => realm.ipc.flush(); - - const emitter = new SemiAsyncEmitter('environment', [ - 'start_describe_definition', - 'finish_describe_definition', - 'add_hook', - 'add_test', - 'error', - ]) - .on('test_environment_setup', startIpc, -1) - .on('test_environment_teardown', stopIpc, Number.MAX_SAFE_INTEGER) - .on('setup', testEventHandler, -1) - .on('include_test_location_in_result', testEventHandler, -1) - .on('start_describe_definition', testEventHandler, -1) - .on('finish_describe_definition', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('add_hook', testEventHandler, -1) - .on('add_test', testEventHandler, -1) - .on('run_start', testEventHandler, -1) - .on('run_start', flushHandler, Number.MAX_SAFE_INTEGER) - .on('run_describe_start', testEventHandler, -1) - .on('hook_failure', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('hook_start', testEventHandler, -1) - .on('hook_success', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_start', testEventHandler, -1) - .on('test_start', flushHandler, Number.MAX_SAFE_INTEGER) - .on('test_started', testEventHandler, -1) - .on('test_retry', testEventHandler, -1) - .on('test_skip', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_todo', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_fn_start', testEventHandler, -1) - .on('test_fn_failure', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_fn_success', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_done', testEventHandler, Number.MAX_SAFE_INTEGER - 1) - .on('test_done', flushHandler, Number.MAX_SAFE_INTEGER) - .on('run_describe_finish', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('run_finish', testEventHandler, Number.MAX_SAFE_INTEGER - 1) - .on('run_finish', flushHandler, Number.MAX_SAFE_INTEGER) - .on('teardown', testEventHandler, Number.MAX_SAFE_INTEGER); - - emitterMap.set(jestEnvironment, emitter); - configMap.set(jestEnvironment, jestEnvironmentConfig); -} - -export type TestEnvironmentEvent = - | { - type: 'test_environment_setup' | 'test_environment_teardown'; - } - | ForwardedCircusEvent; - -type ForwardedCircusEvent = { - type: E['name']; - event: E; - state: Circus.State; -}; - -export async function onTestEnvironmentSetup(env: JestEnvironment): Promise { - await initReporters(env); - await getEmitter(env).emit({ type: 'test_environment_setup' }); -} - -export async function onTestEnvironmentTeardown(env: JestEnvironment): Promise { - await getEmitter(env).emit({ type: 'test_environment_teardown' }); -} - -/** - * Pass Jest Circus event and state to the handler. - * After recalculating the state, this method synchronizes with the metadata server. - */ -export const onHandleTestEvent = ( - env: JestEnvironment, - event: Circus.Event, - state: Circus.State, -): void | Promise => getEmitter(env).emit({ type: event.name, event, state }); - -/** - * Get the environment event emitter by the environment reference. - */ -export const getEmitter = (env: JestEnvironment) => { - const emitter = emitterMap.get(env); - if (!emitter) { - throw new JestMetadataError( - 'Emitter is not found. Most likely, you are using a non-valid environment reference.', - ); - } - - return emitter; -}; - -/** - * Get the environment configuration by the environment reference. - */ -export const getConfig = (env: JestEnvironment) => { - const config = configMap.get(env); - if (!config) { - throw new JestMetadataError( - 'Environment config is not found. Most likely, you are using a non-valid environment reference.', - ); - } - - return config; -}; - -async function initReporters(env: JestEnvironment) { - const reporterModules = (getConfig(env)?.globalConfig?.reporters ?? []).map((r) => r[0]); - const reporterExports = await Promise.all( - reporterModules.map((m) => { - try { - return import(m); - } catch (error: unknown) { - // TODO: log this to trace - console.warn(`[jest-metadata] Failed to import reporter module "${m}"`, error); - return; - } - }), - ); - - for (const reporterExport of reporterExports) { - const ReporterClass = reporterExport?.default ?? reporterExport; - ReporterClass?.onTestEnvironmentCreate?.(env); - } -} - -async function startIpc() { - if (realm.type === 'child_process') { - await realm.ipc.start(); - } -} - -async function stopIpc() { - detectDuplicateRealms(false); - - if (realm.type === 'child_process') { - await realm.ipc.stop(); - } -} diff --git a/src/environment-jsdom.ts b/src/environment-jsdom.ts index 2bff8ba..7c839e0 100644 --- a/src/environment-jsdom.ts +++ b/src/environment-jsdom.ts @@ -1,6 +1,5 @@ import JestEnvironmentJsdom from 'jest-environment-jsdom'; import { WithMetadata } from './environment-decorator'; -export { TestEnvironmentEvent } from './environment-hooks'; export const TestEnvironment = WithMetadata(JestEnvironmentJsdom); export default TestEnvironment; diff --git a/src/environment-node.ts b/src/environment-node.ts index 024ac6f..e6f2ccc 100644 --- a/src/environment-node.ts +++ b/src/environment-node.ts @@ -1,6 +1,5 @@ import JestEnvironmentNode from 'jest-environment-node'; import { WithMetadata } from './environment-decorator'; -export { TestEnvironmentEvent } from './environment-hooks'; export const TestEnvironment = WithMetadata(JestEnvironmentNode); export default TestEnvironment; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 38b3b47..e5690f7 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,19 +1,23 @@ import fs from 'fs'; import path from 'path'; -import { traceEventStream, uniteTraceEventsToFile, wrapLogger } from 'bunyamin'; +import { bunyamin, traceEventStream, uniteTraceEventsToFile } from 'bunyamin'; import { createLogger } from 'bunyan'; import createDebugStream from 'bunyan-debug-stream'; import { noop } from './noop'; const logsDirectory = process.env.JEST_METADATA_DEBUG; -export const logger = wrapLogger({ - logger: createBunyanImpl(isTraceEnabled()), -}); - -export const nologger = wrapLogger({ - logger: createBunyanNoop(), -}) as typeof logger; +bunyamin.logger = createBunyanImpl(isTraceEnabled()); +bunyamin.threadGroups.push( + { id: 'ipc-server', displayName: 'IPC Server (jest-metadata)' }, + { id: 'ipc-client', displayName: 'IPC Client (jest-metadata)' }, + { id: 'emitter-core', displayName: 'Core emitter (jest-metadata)' }, + { id: 'emitter-set', displayName: 'Set emitter (jest-metadata)' }, + { id: 'emitter-events', displayName: 'Events emitter (jest-metadata)' }, + { id: 'environment', displayName: 'Test Environment (jest-metadata)' }, + { id: 'metadata', displayName: 'Metadata (jest-metadata)' }, + { id: 'reporter', displayName: 'Reporter (jest-metadata)' }, +); // eslint-disable-next-line @typescript-eslint/no-explicit-any export const optimizeTracing: (f: F) => F = isTraceEnabled() ? (f) => f : ((() => noop) as any); @@ -67,16 +71,7 @@ function createBunyanImpl(traceEnabled: boolean) { level: 'trace' as const, stream: traceEventStream({ filePath: createLogFilePath(), - threadGroups: [ - { id: 'ipc-server', displayName: 'IPC Server' }, - { id: 'ipc-client', displayName: 'IPC Client' }, - { id: 'emitter-core', displayName: 'Emitter (core)' }, - { id: 'emitter-set', displayName: 'Emitter (set)' }, - { id: 'emitter-events', displayName: 'Emitter (events)' }, - { id: 'environment', displayName: 'Test Environment' }, - { id: 'metadata', displayName: 'Metadata' }, - { id: 'reporter', displayName: 'Reporter' }, - ], + threadGroups: bunyamin.threadGroups, }), }, ] @@ -87,17 +82,6 @@ function createBunyanImpl(traceEnabled: boolean) { return bunyan; } -function createBunyanNoop() { - return { - trace: noop, - debug: noop, - info: noop, - warn: noop, - error: noop, - fatal: noop, - }; -} - const LOG_PATTERN = /^jest-metadata\..*\.log$/; export async function aggregateLogs() { @@ -123,3 +107,5 @@ export async function aggregateLogs() { fs.renameSync(logs[0], unitedLogPath); } } + +export { bunyamin as logger, nobunyamin as nologger } from 'bunyamin'; From e9e267bf0b140c46b89918f0ddc0c2b4659e2429 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Sat, 25 Nov 2023 11:56:32 +0200 Subject: [PATCH 3/5] wip2 --- docs/jest-environment.md | 72 ++++++++++++++--------- environment-decorator.js | 2 +- package.json | 7 ++- src/environment-decorator.ts | 111 ++--------------------------------- src/environment-listener.ts | 106 +++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 136 deletions(-) create mode 100644 src/environment-listener.ts diff --git a/docs/jest-environment.md b/docs/jest-environment.md index 7332571..1a45db6 100644 --- a/docs/jest-environment.md +++ b/docs/jest-environment.md @@ -29,36 +29,52 @@ If you already have a custom test environment, you can extend it via composition ## Manual integration -If your use case is not covered by the above, you can call `jest-metadata` lifecycle methods manually: +If your use case is not covered by the above, you can rewrite your test environment +into a listener of `jest-environment-emit` events. Here is an example of how to do it: + +```js title="jest.config.js" +/** @type {import('jest').Config} */ +module.exports = { + testEnvironment: 'jest-environment-emit', + testEnvironmentOptions: { + listeners: [ + 'jest-metadata/environment-listener', + ['your-project/listener', { /* options */ }], + ], + }, +}; +``` -```diff -+ import * as jmHooks from 'jest-metadata/environment-hooks'; - -class MyCustomEnvironment extends NodeEnvironment { - constructor(config, context) { - super(config, context); -+ jmHooks.onTestEnvironmentCreate(this, config, context); - } - - async setup() { - await super.setup(); -+ await jmHooks.onTestEnvironmentSetup(this); - } - - async teardown() { -+ await jmHooks.onTestEnvironmentTeardown(this); - await super.teardown(); - } - - async handleTestEvent(event, state) { -+ await jmHooks.onTestEnvironmentHandleTestEvent(this, event, state); - // ... - } +where `your-project/listener` file contains the following code: + +```js title="your-project/listeners.js" +import * as jee from 'jest-environment-emit'; + +const listener: jee.EnvironmentListenerFn = (context, yourOptions) => { + context.testEvents + .on('test_environment_setup', ({ env }: jee.TestEnvironmentSetupEvent) => { + // ... + }) + .on('test_start', ({ event, state }: jee.TestEnvironmentCircusEvent) => { + // ... + }) + .on('test_done', ({ event, state }: jee.TestEnvironmentCircusEvent) => { + // ... + }) + .on('test_environment_teardown', ({ env }: jee.TestEnvironmentTeardownEvent) => { + // ... + }); +}; + +export default listener; ``` +## Lifecycle of `jest-metadata` test environment + Here is a brief description of each lifecycle method: -* `onTestEnvironmentCreate` - called when the test environment is created. This is the first lifecycle method to be called. It injects `__JEST_METADATA__` context into `this.global` object to eliminate sandboxing issues. -* `onTestEnvironmentSetup` - called when the test environment is set up. This is the second lifecycle method to be called. It initializes `jest-metadata` IPC client if Jest is running in a multi-worker mode to enable communication with the main process where the reporters reside. -* `onTestEnvironmentHandleTestEvent` - called when the test environment receives a test event from `jest-circus`. This method is called many times during the test run. It is responsible for building a metadata tree, parallel to `jest-circus` State tree, and for sending test events to the main process. -* `onTestEnvironmentTeardown` - called when the test environment is torn down. This is the last lifecycle method to be called. It is responsible for shutting down the IPC client and for sending the final test event to the main process. \ No newline at end of file +* `test_environment_setup`: + * injects `__JEST_METADATA__` context into `this.global` object to eliminate sandboxing issues; + * initializes `jest-metadata` IPC client if Jest is running in a multi-worker mode to enable communication with the main process where the reporters reside; +* all `jest-circus` events coming to `handleTestEvent` – building a metadata tree, parallel to `jest-circus` State tree, and for sending test events to the main process. +* `test_environment_teardown` – responsible for shutting down the IPC client and for sending the final test event to the main process. diff --git a/environment-decorator.js b/environment-decorator.js index 721ceaf..3089cdc 100644 --- a/environment-decorator.js +++ b/environment-decorator.js @@ -1,2 +1,2 @@ /* Jest 27 fallback */ -module.exports = require('./dist/environment-decorator'); +module.exports = require('./dist/environment-listener'); diff --git a/package.json b/package.json index c3143dc..5082d01 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,11 @@ "require": "./dist/environment-decorator.js", "types": "./dist/environment-decorator.d.ts" }, + "./environment-listener": { + "import": "./dist/environment-listener.js", + "require": "./dist/environment-listener.js", + "types": "./dist/environment-listener.d.ts" + }, "./environment-jsdom": { "import": "./dist/environment-jsdom.js", "require": "./dist/environment-jsdom.js", @@ -86,7 +91,7 @@ "bunyan": "^2.0.5", "bunyan-debug-stream": "^3.1.0", "funpermaproxy": "^1.1.0", - "jest-environment-emit": "^1.0.0-alpha.2", + "jest-environment-emit": "^1.0.0-alpha.4", "lodash.merge": "^4.6.2", "node-ipc": "9.2.1", "strip-ansi": "^6.0.0", diff --git a/src/environment-decorator.ts b/src/environment-decorator.ts index cca6aaf..2dde039 100644 --- a/src/environment-decorator.ts +++ b/src/environment-decorator.ts @@ -1,108 +1,7 @@ -import { inspect } from 'util'; -import type { EmitterSubscriptionContext, TestEnvironmentCircusEvent } from 'jest-environment-emit'; +import type { JestEnvironment } from '@jest/environment'; import WithEmitter from 'jest-environment-emit'; -import { JestMetadataError } from './errors'; -import { detectDuplicateRealms, injectRealmIntoSandbox, realm } from './realms'; -import { jestUtils, logger } from './utils'; +import listener from './environment-listener'; -const log = logger.child({ cat: 'environment', tid: 'environment' }); - -WithEmitter.subscribe((context: EmitterSubscriptionContext) => { - const jestEnvironment = context.env; - const jestEnvironmentConfig = context.config; - const environmentContext = context.context; - - detectDuplicateRealms(true); - injectRealmIntoSandbox(jestEnvironment.global, realm); - const testFilePath = environmentContext.testPath; - realm.environmentHandler.handleEnvironmentCreated(testFilePath); - realm.events.add(realm.setEmitter); - - if (!realm.globalMetadata.hasTestFileMetadata(testFilePath)) { - realm.coreEmitter.emit({ - type: 'add_test_file', - testFilePath, - }); - - if (realm.type === 'parent_process') { - const { globalConfig } = jestEnvironmentConfig; - const first = (r: T[]) => r[0]; - const hint = globalConfig - ? ` "reporters": ${inspect(globalConfig.reporters?.map(first))}\n` - : ''; // Jest 27 fallback - - const message = - `Cannot use a metadata test environment without a metadata server.\n` + - `Please check that at least one of the reporters in your Jest config inherits from "jest-metadata/reporter".\n` + - hint; - - if ( - globalConfig && - (jestUtils.isSingleWorker(globalConfig) || jestUtils.isInsideIDE(globalConfig)) - ) { - log.warn(message); - } else { - log.debug(message); - throw new JestMetadataError(message); - } - } - } - - const testEventHandler = ({ event, state }: TestEnvironmentCircusEvent) => { - realm.environmentHandler.handleTestEvent(event, state); - }; - - const flushHandler = () => realm.ipc.flush(); - - context.testEvents - .on( - 'test_environment_setup', - async function () { - if (realm.type === 'child_process') { - await realm.ipc.start(); - } - }, - -1, - ) - .on( - 'test_environment_teardown', - async function () { - detectDuplicateRealms(false); - - if (realm.type === 'child_process') { - await realm.ipc.stop(); - } - }, - Number.MAX_SAFE_INTEGER, - ) - .on('setup', testEventHandler, -1) - .on('include_test_location_in_result', testEventHandler, -1) - .on('start_describe_definition', testEventHandler, -1) - .on('finish_describe_definition', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('add_hook', testEventHandler, -1) - .on('add_test', testEventHandler, -1) - .on('run_start', testEventHandler, -1) - .on('run_start', flushHandler, Number.MAX_SAFE_INTEGER) - .on('run_describe_start', testEventHandler, -1) - .on('hook_failure', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('hook_start', testEventHandler, -1) - .on('hook_success', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_start', testEventHandler, -1) - .on('test_start', flushHandler, Number.MAX_SAFE_INTEGER) - .on('test_started', testEventHandler, -1) - .on('test_retry', testEventHandler, -1) - .on('test_skip', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_todo', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_fn_start', testEventHandler, -1) - .on('test_fn_failure', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_fn_success', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('test_done', testEventHandler, Number.MAX_SAFE_INTEGER - 1) - .on('test_done', flushHandler, Number.MAX_SAFE_INTEGER) - .on('run_describe_finish', testEventHandler, Number.MAX_SAFE_INTEGER) - .on('run_finish', testEventHandler, Number.MAX_SAFE_INTEGER - 1) - .on('run_finish', flushHandler, Number.MAX_SAFE_INTEGER) - .on('teardown', testEventHandler, Number.MAX_SAFE_INTEGER); -}); - -export const WithMetadata = WithEmitter; -export default WithMetadata; +export const WithMetadata = ( + JestEnvironmentClass: new (...args: any[]) => E, +) => WithEmitter(JestEnvironmentClass, listener, 'WithMetadata'); diff --git a/src/environment-listener.ts b/src/environment-listener.ts new file mode 100644 index 0000000..19c1b38 --- /dev/null +++ b/src/environment-listener.ts @@ -0,0 +1,106 @@ +import { inspect } from 'util'; +import type { EnvironmentListenerFn, TestEnvironmentCircusEvent } from 'jest-environment-emit'; +import { JestMetadataError } from './errors'; +import { detectDuplicateRealms, injectRealmIntoSandbox, realm } from './realms'; +import { jestUtils, logger } from './utils'; + +const log = logger.child({ cat: 'environment', tid: 'environment' }); + +const listener: EnvironmentListenerFn = (context) => { + const jestEnvironment = context.env; + const jestEnvironmentConfig = context.config; + const environmentContext = context.context; + + detectDuplicateRealms(true); + injectRealmIntoSandbox(jestEnvironment.global, realm); + const testFilePath = environmentContext.testPath; + realm.environmentHandler.handleEnvironmentCreated(testFilePath); + realm.events.add(realm.setEmitter); + + if (!realm.globalMetadata.hasTestFileMetadata(testFilePath)) { + realm.coreEmitter.emit({ + type: 'add_test_file', + testFilePath, + }); + + if (realm.type === 'parent_process') { + const { globalConfig } = jestEnvironmentConfig; + const first = (r: T[]) => r[0]; + const hint = globalConfig + ? ` "reporters": ${inspect(globalConfig.reporters?.map(first))}\n` + : ''; // Jest 27 fallback + + const message = + `Cannot use a metadata test environment without a metadata server.\n` + + `Please check that at least one of the reporters in your Jest config inherits from "jest-metadata/reporter".\n` + + hint; + + if ( + globalConfig && + (jestUtils.isSingleWorker(globalConfig) || jestUtils.isInsideIDE(globalConfig)) + ) { + log.warn(message); + } else { + log.debug(message); + throw new JestMetadataError(message); + } + } + } + + const testEventHandler = ({ event, state }: TestEnvironmentCircusEvent) => { + realm.environmentHandler.handleTestEvent(event, state); + }; + + const flushHandler = () => realm.ipc.flush(); + + context.testEvents + .on( + 'test_environment_setup', + async function () { + if (realm.type === 'child_process') { + await realm.ipc.start(); + } + }, + -1, + ) + .on( + 'test_environment_teardown', + async function () { + detectDuplicateRealms(false); + + if (realm.type === 'child_process') { + await realm.ipc.stop(); + } + }, + Number.MAX_SAFE_INTEGER, + ) + .on('setup', testEventHandler, -1) + .on('include_test_location_in_result', testEventHandler, -1) + .on('start_describe_definition', testEventHandler, -1) + .on('finish_describe_definition', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('add_hook', testEventHandler, -1) + .on('add_test', testEventHandler, -1) + .on('run_start', testEventHandler, -1) + .on('run_start', flushHandler, Number.MAX_SAFE_INTEGER) + .on('run_describe_start', testEventHandler, -1) + .on('hook_failure', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('hook_start', testEventHandler, -1) + .on('hook_success', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_start', testEventHandler, -1) + .on('test_start', flushHandler, Number.MAX_SAFE_INTEGER) + .on('test_started', testEventHandler, -1) + .on('test_retry', testEventHandler, -1) + .on('test_skip', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_todo', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_fn_start', testEventHandler, -1) + .on('test_fn_failure', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_fn_success', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('test_done', testEventHandler, Number.MAX_SAFE_INTEGER - 1) + .on('test_done', flushHandler, Number.MAX_SAFE_INTEGER) + .on('run_describe_finish', testEventHandler, Number.MAX_SAFE_INTEGER) + .on('run_finish', testEventHandler, Number.MAX_SAFE_INTEGER - 1) + .on('run_finish', flushHandler, Number.MAX_SAFE_INTEGER) + .on('teardown', testEventHandler, Number.MAX_SAFE_INTEGER); +}; + +export default listener; From 7d6930a84bda8d4ad6749ebd0c071e0cf2caf86c Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Sat, 25 Nov 2023 13:51:10 +0200 Subject: [PATCH 4/5] Update package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5082d01..3fdf371 100644 --- a/package.json +++ b/package.json @@ -87,11 +87,11 @@ }, "homepage": "https://github.com/wix-incubator/jest-metadata#readme", "dependencies": { - "bunyamin": "^1.4.0", + "bunyamin": "^1.4.2", "bunyan": "^2.0.5", "bunyan-debug-stream": "^3.1.0", "funpermaproxy": "^1.1.0", - "jest-environment-emit": "^1.0.0-alpha.4", + "jest-environment-emit": "^1.0.0-alpha.5", "lodash.merge": "^4.6.2", "node-ipc": "9.2.1", "strip-ansi": "^6.0.0", From bc73ef453da45f9ce5726b2b9b390694eecf9daa Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Sat, 25 Nov 2023 13:47:28 +0200 Subject: [PATCH 5/5] finish --- e2e/reporters/{recorder.ts => recorder.js} | 5 - environment-decorator.js | 2 +- environment-listener.js | 2 + src/environment-listener.ts | 3 +- .../__tests__/fallback-api.test.ts | 6 +- src/metadata/__tests__/integration.test.ts | 4 +- src/metadata/__tests__/run-traversal.test.ts | 4 +- src/realms/BaseRealm.ts | 8 +- src/utils/emitters/AggregatedEmitter.test.ts | 12 +- src/utils/emitters/AggregatedEmitter.ts | 6 +- src/utils/emitters/ReadonlyEmitterBase.ts | 94 ------------- src/utils/emitters/SemiAsyncEmitter.ts | 62 --------- src/utils/emitters/SerialAsyncEmitter.ts | 32 ----- ...cEmitter.test.ts => SerialEmitter.test.ts} | 14 +- src/utils/emitters/SerialEmitter.ts | 124 ++++++++++++++++++ src/utils/emitters/SerialSyncEmitter.ts | 41 ------ src/utils/emitters/index.ts | 4 +- src/utils/emitters/syncEmitterCommons.ts | 18 --- src/utils/iterateSorted.test.ts | 23 ---- src/utils/iterateSorted.ts | 42 ------ src/utils/logger.ts | 22 ++-- 21 files changed, 167 insertions(+), 361 deletions(-) rename e2e/reporters/{recorder.ts => recorder.js} (96%) create mode 100644 environment-listener.js delete mode 100644 src/utils/emitters/ReadonlyEmitterBase.ts delete mode 100644 src/utils/emitters/SemiAsyncEmitter.ts delete mode 100644 src/utils/emitters/SerialAsyncEmitter.ts rename src/utils/emitters/{SerialSyncEmitter.test.ts => SerialEmitter.test.ts} (85%) create mode 100644 src/utils/emitters/SerialEmitter.ts delete mode 100644 src/utils/emitters/SerialSyncEmitter.ts delete mode 100644 src/utils/emitters/syncEmitterCommons.ts delete mode 100644 src/utils/iterateSorted.test.ts delete mode 100644 src/utils/iterateSorted.ts diff --git a/e2e/reporters/recorder.ts b/e2e/reporters/recorder.js similarity index 96% rename from e2e/reporters/recorder.ts rename to e2e/reporters/recorder.js index 8e454f4..a6501d5 100644 --- a/e2e/reporters/recorder.ts +++ b/e2e/reporters/recorder.js @@ -89,11 +89,6 @@ class E2eRecorderReporter extends JestMetadataReporter { await debugUtils.aggregateLogs(); } } - - static onTestEnvironmentCreate(env) { - console.log('I AM ALIVE'); - console.log(' ..--..'); - } } function sleep(ms) { diff --git a/environment-decorator.js b/environment-decorator.js index 3089cdc..721ceaf 100644 --- a/environment-decorator.js +++ b/environment-decorator.js @@ -1,2 +1,2 @@ /* Jest 27 fallback */ -module.exports = require('./dist/environment-listener'); +module.exports = require('./dist/environment-decorator'); diff --git a/environment-listener.js b/environment-listener.js new file mode 100644 index 0000000..3089cdc --- /dev/null +++ b/environment-listener.js @@ -0,0 +1,2 @@ +/* Jest 27 fallback */ +module.exports = require('./dist/environment-listener'); diff --git a/src/environment-listener.ts b/src/environment-listener.ts index 19c1b38..68585b2 100644 --- a/src/environment-listener.ts +++ b/src/environment-listener.ts @@ -4,9 +4,8 @@ import { JestMetadataError } from './errors'; import { detectDuplicateRealms, injectRealmIntoSandbox, realm } from './realms'; import { jestUtils, logger } from './utils'; -const log = logger.child({ cat: 'environment', tid: 'environment' }); - const listener: EnvironmentListenerFn = (context) => { + const log = logger.child({ cat: 'environment', tid: 'environment' }); const jestEnvironment = context.env; const jestEnvironmentConfig = context.config; const environmentContext = context.context; diff --git a/src/jest-reporter/__tests__/fallback-api.test.ts b/src/jest-reporter/__tests__/fallback-api.test.ts index 7b9215b..2bc2d03 100644 --- a/src/jest-reporter/__tests__/fallback-api.test.ts +++ b/src/jest-reporter/__tests__/fallback-api.test.ts @@ -8,7 +8,7 @@ import { MetadataFactoryImpl, WriteMetadataEventEmitter, } from '../../metadata'; -import { SerialSyncEmitter } from '../../utils'; +import { SerialEmitter } from '../../utils'; import { AssociateMetadata } from '../AssociateMetadata'; import { FallbackAPI } from '../FallbackAPI'; import { QueryMetadata } from '../QueryMetadata'; @@ -23,10 +23,10 @@ describe('Fallback API', () => { const IPCServer = jest.requireMock('../../ipc').IPCServer; const ipc: jest.Mocked = new IPCServer(); - const emitter = new SerialSyncEmitter('core').on('*', (event: MetadataEvent) => { + const emitter = new SerialEmitter('core').on('*', (event: MetadataEvent) => { metadataHandler.handle(event); }) as MetadataEventEmitter; - const setEmitter = new SerialSyncEmitter('set') as WriteMetadataEventEmitter; + const setEmitter = new SerialEmitter('set') as WriteMetadataEventEmitter; const metadataRegistry = new GlobalMetadataRegistry(); const metadataFactory = new MetadataFactoryImpl(metadataRegistry, setEmitter); const globalMetadata = metadataFactory.createGlobalMetadata(); diff --git a/src/metadata/__tests__/integration.test.ts b/src/metadata/__tests__/integration.test.ts index 6d98e1a..9810686 100644 --- a/src/metadata/__tests__/integration.test.ts +++ b/src/metadata/__tests__/integration.test.ts @@ -1,6 +1,6 @@ import fixtures from '../../../e2e'; -import { SerialSyncEmitter } from '../../utils'; +import { SerialEmitter } from '../../utils'; import { PlantSerializer } from '../__utils__'; import { GlobalMetadataRegistry, @@ -13,7 +13,7 @@ describe('metadata - integration test:', () => { test.each(Object.values(fixtures.metadata))( `e2e/__fixtures__/%s`, (_name: string, fixture: any[]) => { - const emitter: WriteMetadataEventEmitter = new SerialSyncEmitter('set'); + const emitter: WriteMetadataEventEmitter = new SerialEmitter('set'); const metadataRegistry = new GlobalMetadataRegistry(); const metadataFactory = new MetadataFactoryImpl(metadataRegistry, emitter); const globalMetadata = metadataFactory.createGlobalMetadata(); diff --git a/src/metadata/__tests__/run-traversal.test.ts b/src/metadata/__tests__/run-traversal.test.ts index 8471fb5..d9fdcd2 100644 --- a/src/metadata/__tests__/run-traversal.test.ts +++ b/src/metadata/__tests__/run-traversal.test.ts @@ -1,6 +1,6 @@ import fixtures from '../../../e2e'; -import { SerialSyncEmitter } from '../../utils'; +import { SerialEmitter } from '../../utils'; import { GlobalMetadataRegistry, Metadata, @@ -15,7 +15,7 @@ describe('file metadata traversal:', () => { }); test.each(lastFixtures)(`fixtures/%s`, (_name: string, fixture: any[]) => { - const emitter: WriteMetadataEventEmitter = new SerialSyncEmitter('set'); + const emitter: WriteMetadataEventEmitter = new SerialEmitter('set'); const metadataRegistry = new GlobalMetadataRegistry(); const metadataFactory = new MetadataFactoryImpl(metadataRegistry, emitter); const globalMetadata = metadataFactory.createGlobalMetadata(); diff --git a/src/realms/BaseRealm.ts b/src/realms/BaseRealm.ts index aad73a9..397b423 100644 --- a/src/realms/BaseRealm.ts +++ b/src/realms/BaseRealm.ts @@ -12,18 +12,16 @@ import { WriteMetadataEventEmitter, } from '../metadata'; -import { AggregatedEmitter, SerialSyncEmitter } from '../utils'; +import { AggregatedEmitter, SerialEmitter } from '../utils'; export abstract class BaseRealm { - readonly coreEmitter = new SerialSyncEmitter('core').on( + readonly coreEmitter = new SerialEmitter('core').on( '*', (event: MetadataEvent) => { this.metadataHandler.handle(event); }, ) as MetadataEventEmitter; - readonly setEmitter = new SerialSyncEmitter( - 'set', - ) as WriteMetadataEventEmitter; + readonly setEmitter = new SerialEmitter('set') as WriteMetadataEventEmitter; readonly events = new AggregatedEmitter('events').add(this.coreEmitter); readonly metadataRegistry = new GlobalMetadataRegistry(); diff --git a/src/utils/emitters/AggregatedEmitter.test.ts b/src/utils/emitters/AggregatedEmitter.test.ts index 25718ea..68d4afe 100644 --- a/src/utils/emitters/AggregatedEmitter.test.ts +++ b/src/utils/emitters/AggregatedEmitter.test.ts @@ -1,5 +1,5 @@ import { AggregatedEmitter } from './AggregatedEmitter'; -import { SerialSyncEmitter } from './SerialSyncEmitter'; +import { SerialEmitter } from './SerialEmitter'; describe('AggregatedEmitter', () => { let emitter: AggregatedEmitter<{ type: string }>; @@ -9,8 +9,8 @@ describe('AggregatedEmitter', () => { }); test('should re-emit events of added #emitters', () => { - const dummy1 = new SerialSyncEmitter('dummy1'); - const dummy2 = new SerialSyncEmitter('dummy2'); + const dummy1 = new SerialEmitter('dummy1'); + const dummy2 = new SerialEmitter('dummy2'); const listener = jest.fn(); const event1 = { type: 'test', id: 1 }; const event2 = { type: 'test', id: 2 }; @@ -26,8 +26,8 @@ describe('AggregatedEmitter', () => { }); test('should allow subscribing to events only once', () => { - const dummy1 = new SerialSyncEmitter('dummy1'); - const dummy2 = new SerialSyncEmitter('dummy2'); + const dummy1 = new SerialEmitter('dummy1'); + const dummy2 = new SerialEmitter('dummy2'); const listener = jest.fn(); const event1 = { type: 'test', id: 1 }; const event2 = { type: 'test', id: 2 }; @@ -42,7 +42,7 @@ describe('AggregatedEmitter', () => { }); test('should allow unsubscribing from events', () => { - const dummy = new SerialSyncEmitter('dummy1'); + const dummy = new SerialEmitter('dummy1'); const listener = jest.fn(); const event = { type: 'test', id: 1 }; diff --git a/src/utils/emitters/AggregatedEmitter.ts b/src/utils/emitters/AggregatedEmitter.ts index d5d7fd4..06a8e8c 100644 --- a/src/utils/emitters/AggregatedEmitter.ts +++ b/src/utils/emitters/AggregatedEmitter.ts @@ -1,12 +1,12 @@ import type { Emitter, ReadonlyEmitter } from '../../types'; -import { SerialSyncEmitter } from './SerialSyncEmitter'; +import { SerialEmitter } from './SerialEmitter'; export class AggregatedEmitter implements ReadonlyEmitter { readonly #emitters = new WeakSet>(); - readonly #rootEmitter: SerialSyncEmitter; + readonly #rootEmitter: SerialEmitter; constructor(name: string) { - this.#rootEmitter = new SerialSyncEmitter(name); + this.#rootEmitter = new SerialEmitter(name); } add(emitter: Emitter): this { diff --git a/src/utils/emitters/ReadonlyEmitterBase.ts b/src/utils/emitters/ReadonlyEmitterBase.ts deleted file mode 100644 index 5699424..0000000 --- a/src/utils/emitters/ReadonlyEmitterBase.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import type { ReadonlyEmitter } from '../../types'; -import { iterateSorted } from '../iterateSorted'; -import { logger, nologger, optimizeTracing } from '../logger'; - -//#region Optimized event helpers - -const __CATEGORY_LISTENERS = ['listeners']; -const __LISTENERS = optimizeTracing((listener: unknown) => ({ - cat: __CATEGORY_LISTENERS, - fn: `${listener}`, -})); - -//#endregion - -const ONCE: unique symbol = Symbol('ONCE'); - -export abstract class ReadonlyEmitterBase - implements ReadonlyEmitter -{ - protected readonly _log: typeof logger; - protected readonly _listeners: Map = new Map(); - - #listenersCounter = 0; - - constructor(name?: string, shouldLog = true) { - this._log = (shouldLog ? logger : nologger).child({ cat: `emitter`, tid: `emitter-${name}` }); - this._listeners.set('*', []); - } - - on( - type: E['type'] | '*', - listener: Function & { [ONCE]?: true }, - order?: number, - ): this { - if (!listener[ONCE]) { - this._log.trace(__LISTENERS(listener), `on(${type})`); - } - - if (!this._listeners.has(type)) { - this._listeners.set(type, []); - } - - const listeners = this._listeners.get(type)!; - listeners.push([listener, order ?? this.#listenersCounter++]); - listeners.sort((a, b) => getOrder(a) - getOrder(b)); - - return this; - } - - once(type: E['type'] | '*', listener: Function, order?: number): this { - this._log.trace(__LISTENERS(listener), `once(${type})`); - return this.on(type, this.#createOnceListener(type, listener), order); - } - - off( - type: E['type'] | '*', - listener: Function & { [ONCE]?: true }, - _order?: number, - ): this { - if (!listener[ONCE]) { - this._log.trace(__LISTENERS(listener), `off(${type})`); - } - - const listeners = this._listeners.get(type) || []; - const index = listeners.findIndex(([l]) => l === listener); - if (index !== -1) { - listeners.splice(index, 1); - } - return this; - } - - protected *_getListeners(type: Event['type']): Iterable { - const wildcard: [Function, number][] = this._listeners.get('*') ?? []; - const named: [Function, number][] = this._listeners.get(type) ?? []; - for (const [listener] of iterateSorted<[Function, number]>(getOrder, wildcard, named)) { - yield listener; - } - } - - #createOnceListener(type: Event['type'], listener: Function) { - const onceListener = ((event: Event) => { - this.off(type, onceListener); - listener(event); - }) as Function & { [ONCE]?: true }; - - onceListener[ONCE] = true as const; - return onceListener; - } -} - -function getOrder([_a, b]: [T, number]): number { - return b; -} diff --git a/src/utils/emitters/SemiAsyncEmitter.ts b/src/utils/emitters/SemiAsyncEmitter.ts deleted file mode 100644 index 52825e5..0000000 --- a/src/utils/emitters/SemiAsyncEmitter.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { ReadonlyAsyncEmitter } from '../../types'; -import { SerialAsyncEmitter } from './SerialAsyncEmitter'; -import { SerialSyncEmitter } from './SerialSyncEmitter'; - -export class SemiAsyncEmitter - implements ReadonlyAsyncEmitter -{ - readonly #asyncEmitter: SerialAsyncEmitter; - readonly #syncEmitter: SerialSyncEmitter; - readonly #syncEvents: Set; - - constructor(name: string, syncEvents: Event['type'][]) { - this.#asyncEmitter = new SerialAsyncEmitter(name, false); - this.#syncEmitter = new SerialSyncEmitter(name, false); - this.#syncEvents = new Set(syncEvents); - } - - on( - type: E['type'] | '*', - listener: (event: E) => unknown, - order?: number, - ): this { - return this.#invoke('on', type, listener, order); - } - - once( - type: E['type'] | '*', - listener: (event: E) => unknown, - order?: number, - ): this { - return this.#invoke('once', type, listener, order); - } - - off(type: E['type'] | '*', listener: (event: E) => unknown): this { - return this.#invoke('off', type, listener); - } - - emit(event: Event): void | Promise { - return this.#syncEvents.has(event.type as Event['type']) - ? this.#syncEmitter.emit(event) - : this.#asyncEmitter.emit(event); - } - - #invoke( - methodName: 'on' | 'once' | 'off', - type: E['type'] | '*', - listener: (event: E) => unknown, - order?: number, - ): this { - const isSync = this.#syncEvents.has(type); - - if (type === '*' || isSync) { - this.#syncEmitter[methodName](type, listener, order); - } - - if (type === '*' || !isSync) { - this.#asyncEmitter[methodName](type, listener as (event: Event) => Promise, order); - } - - return this; - } -} diff --git a/src/utils/emitters/SerialAsyncEmitter.ts b/src/utils/emitters/SerialAsyncEmitter.ts deleted file mode 100644 index 1ccdda1..0000000 --- a/src/utils/emitters/SerialAsyncEmitter.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { AsyncEmitter } from '../../types'; -import { ReadonlyEmitterBase } from './ReadonlyEmitterBase'; -import { __EMIT, __INVOKE } from './syncEmitterCommons'; - -export class SerialAsyncEmitter - extends ReadonlyEmitterBase - implements AsyncEmitter -{ - #promise = Promise.resolve(); - - emit(event: Event): Promise { - return this.#enqueue(event); - } - - #enqueue(event: Event) { - return (this.#promise = this.#promise.then(() => this.#doEmit(event))); - } - - async #doEmit(event: Event) { - const eventType = event.type; - const listeners = [...this._getListeners(eventType)]; - - await this._log.trace.complete(__EMIT(event), event.type, async () => { - if (listeners) { - for (const listener of listeners) { - this._log.trace(__INVOKE(listener), 'invoke'); - await listener(event); - } - } - }); - } -} diff --git a/src/utils/emitters/SerialSyncEmitter.test.ts b/src/utils/emitters/SerialEmitter.test.ts similarity index 85% rename from src/utils/emitters/SerialSyncEmitter.test.ts rename to src/utils/emitters/SerialEmitter.test.ts index 0ef56da..944e5f8 100644 --- a/src/utils/emitters/SerialSyncEmitter.test.ts +++ b/src/utils/emitters/SerialEmitter.test.ts @@ -1,8 +1,8 @@ -import { SerialSyncEmitter } from './SerialSyncEmitter'; +import { SerialEmitter } from './SerialEmitter'; -describe('SerialSyncEmitter', () => { +describe('SerialEmitter', () => { it('should emit events', () => { - const emitter = new SerialSyncEmitter(); + const emitter = new SerialEmitter(); const listener = jest.fn(); emitter.on('test', listener); @@ -16,7 +16,7 @@ describe('SerialSyncEmitter', () => { }); it('should allow subscribing to events only once', () => { - const emitter = new SerialSyncEmitter(); + const emitter = new SerialEmitter(); const listener = jest.fn(); emitter.once('test', listener); emitter.emit({ type: 'test', payload: 42 }); @@ -26,7 +26,7 @@ describe('SerialSyncEmitter', () => { }); it('should allow subscribing to all events', () => { - const emitter = new SerialSyncEmitter(); + const emitter = new SerialEmitter(); const listener = jest.fn(); emitter.on('*', listener); emitter.emit({ type: 'test', payload: 42 }); @@ -38,7 +38,7 @@ describe('SerialSyncEmitter', () => { }); it('should allow unsubscribing from events', () => { - const emitter = new SerialSyncEmitter(); + const emitter = new SerialEmitter(); const listener = jest.fn(); emitter.on('test', listener); emitter.emit({ type: 'test', payload: 42 }); @@ -49,7 +49,7 @@ describe('SerialSyncEmitter', () => { }); it('should delay emits within emits', () => { - const emitter = new SerialSyncEmitter(); + const emitter = new SerialEmitter(); const listener1 = jest.fn(() => emitter.emit({ type: 'test', payload: 84 })); const listener2 = jest.fn(); emitter.once('test', listener1); diff --git a/src/utils/emitters/SerialEmitter.ts b/src/utils/emitters/SerialEmitter.ts new file mode 100644 index 0000000..3bda9e5 --- /dev/null +++ b/src/utils/emitters/SerialEmitter.ts @@ -0,0 +1,124 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import type { Emitter } from '../../types'; + +import { logger, nologger, optimizeTracing } from '../logger'; + +//#region Optimized event helpers + +const CATEGORIES = { + ENQUEUE: ['enqueue'], + EMIT: ['emit'], + INVOKE: ['invoke'], + LISTENERS: ['listeners'], +}; + +const __EMIT = optimizeTracing((event: unknown) => ({ cat: CATEGORIES.EMIT, event })); +const __ENQUEUE = optimizeTracing((event: unknown) => ({ + cat: CATEGORIES.ENQUEUE, + event, +})); +const __INVOKE = optimizeTracing((listener: unknown, type?: '*') => ({ + cat: CATEGORIES.INVOKE, + fn: `${listener}`, + type, +})); +const __LISTENERS = optimizeTracing((listener: unknown) => ({ + cat: CATEGORIES.LISTENERS, + fn: `${listener}`, +})); + +//#endregion + +const ONCE: unique symbol = Symbol('ONCE'); + +/** + * An event emitter that emits events in the order they are received. + * If an event is emitted while another event is being emitted, the new event + * will be queued and emitted after the current event is finished. + */ +export class SerialEmitter implements Emitter { + protected readonly _log: typeof logger; + protected readonly _listeners: Map = new Map(); + + #queue: Event[] = []; + + constructor(name?: string, shouldLog = true) { + this._log = (shouldLog ? logger : nologger).child({ cat: `emitter`, tid: `emitter-${name}` }); + this._listeners.set('*', []); + } + + on(type: E['type'] | '*', listener: Function & { [ONCE]?: true }): this { + if (!listener[ONCE]) { + this._log.trace(__LISTENERS(listener), `on(${type})`); + } + + if (!this._listeners.has(type)) { + this._listeners.set(type, []); + } + + const listeners = this._listeners.get(type)!; + listeners.push(listener); + + return this; + } + + once(type: E['type'] | '*', listener: Function): this { + this._log.trace(__LISTENERS(listener), `once(${type})`); + return this.on(type, this.#createOnceListener(type, listener)); + } + + off(type: E['type'] | '*', listener: Function & { [ONCE]?: true }): this { + if (!listener[ONCE]) { + this._log.trace(__LISTENERS(listener), `off(${type})`); + } + + const listeners = this._listeners.get(type) || []; + const index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + return this; + } + + emit(nextEvent: Event): void { + this.#queue.push(Object.freeze(nextEvent)); + + if (this.#queue.length > 1) { + this._log.trace(__ENQUEUE(nextEvent), `enqueue(${nextEvent.type})`); + return; + } + + while (this.#queue.length > 0) { + const event = this.#queue[0]; + const eventType = event.type; + const listeners = this._getListeners(eventType); + + this._log.trace.complete(__EMIT(event), event.type, () => { + if (listeners) { + for (const listener of listeners) { + this._log.trace(__INVOKE(listener), 'invoke'); + listener(event); + } + } + }); + + this.#queue.shift(); + } + } + + protected _getListeners(type: Event['type']): Function[] { + const wildcard: Function[] = this._listeners.get('*') ?? []; + const named: Function[] = this._listeners.get(type) ?? []; + return [...wildcard, ...named]; + } + + #createOnceListener(type: Event['type'], listener: Function) { + const onceListener = ((event: Event) => { + this.off(type, onceListener); + listener(event); + }) as Function & { [ONCE]?: true }; + + onceListener[ONCE] = true as const; + return onceListener; + } +} diff --git a/src/utils/emitters/SerialSyncEmitter.ts b/src/utils/emitters/SerialSyncEmitter.ts deleted file mode 100644 index 1407377..0000000 --- a/src/utils/emitters/SerialSyncEmitter.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Emitter } from '../../types'; -import { ReadonlyEmitterBase } from './ReadonlyEmitterBase'; -import { __EMIT, __ENQUEUE, __INVOKE } from './syncEmitterCommons'; - -/** - * An event emitter that emits events in the order they are received. - * If an event is emitted while another event is being emitted, the new event - * will be queued and emitted after the current event is finished. - */ -export class SerialSyncEmitter - extends ReadonlyEmitterBase - implements Emitter -{ - #queue: Event[] = []; - - emit(nextEvent: Event): void { - this.#queue.push(Object.freeze(nextEvent)); - - if (this.#queue.length > 1) { - this._log.trace(__ENQUEUE(nextEvent), `enqueue(${nextEvent.type})`); - return; - } - - while (this.#queue.length > 0) { - const event = this.#queue[0]; - const eventType = event.type; - const listeners = [...this._getListeners(eventType)]; - - this._log.trace.complete(__EMIT(event), event.type, () => { - if (listeners) { - for (const listener of listeners) { - this._log.trace(__INVOKE(listener), 'invoke'); - listener(event); - } - } - }); - - this.#queue.shift(); - } - } -} diff --git a/src/utils/emitters/index.ts b/src/utils/emitters/index.ts index 2011a5f..c2073a3 100644 --- a/src/utils/emitters/index.ts +++ b/src/utils/emitters/index.ts @@ -1,4 +1,2 @@ export * from './AggregatedEmitter'; -export * from './SerialAsyncEmitter'; -export * from './SerialSyncEmitter'; -export * from './SemiAsyncEmitter'; +export * from './SerialEmitter'; diff --git a/src/utils/emitters/syncEmitterCommons.ts b/src/utils/emitters/syncEmitterCommons.ts deleted file mode 100644 index e77d68e..0000000 --- a/src/utils/emitters/syncEmitterCommons.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { optimizeTracing } from '../logger'; - -const CATEGORIES = { - ENQUEUE: ['enqueue'], - EMIT: ['emit'], - INVOKE: ['invoke'], -}; - -export const __ENQUEUE = optimizeTracing((event: unknown) => ({ - cat: CATEGORIES.ENQUEUE, - event, -})); -export const __EMIT = optimizeTracing((event: unknown) => ({ cat: CATEGORIES.EMIT, event })); -export const __INVOKE = optimizeTracing((listener: unknown, type?: '*') => ({ - cat: CATEGORIES.INVOKE, - fn: `${listener}`, - type, -})); diff --git a/src/utils/iterateSorted.test.ts b/src/utils/iterateSorted.test.ts deleted file mode 100644 index 1e8e69c..0000000 --- a/src/utils/iterateSorted.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { iterateSorted } from './iterateSorted'; - -describe('iterateSorted', () => { - const ordered = ['a', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']; - const getIndex = (x: string) => ordered.indexOf(x); - - it('should iterate over two sorted arrays', () => { - const a = ['a', 'fox', 'jumps', 'over', 'the', 'dog']; - const b = ['quick', 'brown', 'lazy']; - const c = [...iterateSorted(getIndex, a, b)]; - const d = [...iterateSorted(getIndex, b, a)]; - - expect(c).toEqual(ordered); - expect(d).toEqual(ordered); - }); - - it('should iterate once if arrays are the same', () => { - const a = ['a', 'fox', 'jumps', 'over', 'the', 'dog']; - const b = a; - const c = [...iterateSorted(getIndex, a, b)]; - expect(c).toEqual(a); - }); -}); diff --git a/src/utils/iterateSorted.ts b/src/utils/iterateSorted.ts deleted file mode 100644 index 8b6a80a..0000000 --- a/src/utils/iterateSorted.ts +++ /dev/null @@ -1,42 +0,0 @@ -export function* iterateSorted( - getIndex: (x: T) => number, - a: Iterable, - b: Iterable, -): IterableIterator { - if (a === b) { - yield* a; - return; - } - - const ia = a[Symbol.iterator](); - const ib = b[Symbol.iterator](); - - let ea = ia.next(); - let eb = ib.next(); - - while (!ea.done && !eb.done) { - const va = ea.value; - const vb = eb.value; - - const na = getIndex(va); - const nb = getIndex(vb); - - if (na <= nb) { - yield va; - ea = ia.next(); - } else { - yield vb; - eb = ib.next(); - } - } - - while (!ea.done) { - yield ea.value; - ea = ia.next(); - } - - while (!eb.done) { - yield eb.value; - eb = ib.next(); - } -} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index e5690f7..fbc73f6 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -8,16 +8,18 @@ import { noop } from './noop'; const logsDirectory = process.env.JEST_METADATA_DEBUG; bunyamin.logger = createBunyanImpl(isTraceEnabled()); -bunyamin.threadGroups.push( - { id: 'ipc-server', displayName: 'IPC Server (jest-metadata)' }, - { id: 'ipc-client', displayName: 'IPC Client (jest-metadata)' }, - { id: 'emitter-core', displayName: 'Core emitter (jest-metadata)' }, - { id: 'emitter-set', displayName: 'Set emitter (jest-metadata)' }, - { id: 'emitter-events', displayName: 'Events emitter (jest-metadata)' }, - { id: 'environment', displayName: 'Test Environment (jest-metadata)' }, - { id: 'metadata', displayName: 'Metadata (jest-metadata)' }, - { id: 'reporter', displayName: 'Reporter (jest-metadata)' }, -); +if (!bunyamin.threadGroups.some((x) => x.id === 'metadata')) { + bunyamin.threadGroups.push( + { id: 'ipc-server', displayName: 'IPC Server (jest-metadata)' }, + { id: 'ipc-client', displayName: 'IPC Client (jest-metadata)' }, + { id: 'emitter-core', displayName: 'Core emitter (jest-metadata)' }, + { id: 'emitter-set', displayName: 'Set emitter (jest-metadata)' }, + { id: 'emitter-events', displayName: 'Events emitter (jest-metadata)' }, + { id: 'environment', displayName: 'Test Environment (jest-metadata)' }, + { id: 'metadata', displayName: 'Metadata (jest-metadata)' }, + { id: 'reporter', displayName: 'Reporter (jest-metadata)' }, + ); +} // eslint-disable-next-line @typescript-eslint/no-explicit-any export const optimizeTracing: (f: F) => F = isTraceEnabled() ? (f) => f : ((() => noop) as any);