| 'use strict'; |
| |
| const common = require('../common'); |
| const { EventEmitterAsyncResource } = require('events'); |
| const { |
| createHook, |
| executionAsyncId, |
| } = require('async_hooks'); |
| |
| const { |
| deepStrictEqual, |
| strictEqual, |
| throws, |
| } = require('assert'); |
| |
| const { |
| setImmediate: tick, |
| } = require('timers/promises'); |
| |
| function makeHook(trackedTypes) { |
| const eventMap = new Map(); |
| |
| function log(asyncId, name) { |
| const entry = eventMap.get(asyncId); |
| if (entry !== undefined) entry.push({ name }); |
| } |
| |
| const hook = createHook({ |
| init(asyncId, type, triggerAsyncId, resource) { |
| if (trackedTypes.includes(type)) { |
| eventMap.set(asyncId, [ |
| { |
| name: 'init', |
| type, |
| triggerAsyncId, |
| resource, |
| }, |
| ]); |
| } |
| }, |
| |
| before(asyncId) { log(asyncId, 'before'); }, |
| after(asyncId) { log(asyncId, 'after'); }, |
| destroy(asyncId) { log(asyncId, 'destroy'); }, |
| }).enable(); |
| |
| return { |
| done() { |
| hook.disable(); |
| return new Set(eventMap.values()); |
| }, |
| ids() { |
| return new Set(eventMap.keys()); |
| }, |
| }; |
| } |
| |
| // Tracks emit() calls correctly using async_hooks |
| (async () => { |
| const tracer = makeHook(['Foo']); |
| |
| class Foo extends EventEmitterAsyncResource {} |
| |
| const origExecutionAsyncId = executionAsyncId(); |
| const foo = new Foo(); |
| |
| foo.on('someEvent', common.mustCall()); |
| foo.emit('someEvent'); |
| |
| deepStrictEqual([foo.asyncId], [...tracer.ids()]); |
| strictEqual(foo.triggerAsyncId, origExecutionAsyncId); |
| strictEqual(foo.asyncResource.eventEmitter, foo); |
| |
| foo.emitDestroy(); |
| |
| await tick(); |
| |
| deepStrictEqual(tracer.done(), new Set([ |
| [ |
| { |
| name: 'init', |
| type: 'Foo', |
| triggerAsyncId: origExecutionAsyncId, |
| resource: foo.asyncResource, |
| }, |
| { name: 'before' }, |
| { name: 'after' }, |
| { name: 'destroy' }, |
| ], |
| ])); |
| })().then(common.mustCall()); |
| |
| // Can explicitly specify name as positional arg |
| (async () => { |
| const tracer = makeHook(['ResourceName']); |
| |
| const origExecutionAsyncId = executionAsyncId(); |
| class Foo extends EventEmitterAsyncResource {} |
| |
| const foo = new Foo('ResourceName'); |
| |
| deepStrictEqual(tracer.done(), new Set([ |
| [ |
| { |
| name: 'init', |
| type: 'ResourceName', |
| triggerAsyncId: origExecutionAsyncId, |
| resource: foo.asyncResource, |
| }, |
| ], |
| ])); |
| })().then(common.mustCall()); |
| |
| // Can explicitly specify name as option |
| (async () => { |
| const tracer = makeHook(['ResourceName']); |
| |
| const origExecutionAsyncId = executionAsyncId(); |
| class Foo extends EventEmitterAsyncResource {} |
| |
| const foo = new Foo({ name: 'ResourceName' }); |
| |
| deepStrictEqual(tracer.done(), new Set([ |
| [ |
| { |
| name: 'init', |
| type: 'ResourceName', |
| triggerAsyncId: origExecutionAsyncId, |
| resource: foo.asyncResource, |
| }, |
| ], |
| ])); |
| })().then(common.mustCall()); |
| |
| // Member methods ERR_INVALID_THIS |
| throws( |
| () => EventEmitterAsyncResource.prototype.emit(), |
| { code: 'ERR_INVALID_THIS' } |
| ); |
| |
| throws( |
| () => EventEmitterAsyncResource.prototype.emitDestroy(), |
| { code: 'ERR_INVALID_THIS' } |
| ); |
| |
| ['asyncId', 'triggerAsyncId', 'asyncResource'].forEach((getter) => { |
| throws( |
| () => Reflect.get(EventEmitterAsyncResource.prototype, getter, {}), |
| { |
| code: 'ERR_INVALID_THIS', |
| name: /TypeError/, |
| message: 'Value of "this" must be of type EventEmitterAsyncResource', |
| stack: new RegExp(`at get ${getter}`), |
| } |
| ); |
| }); |