1'use strict'; 2 3const common = require('../common'); 4const { EventEmitterAsyncResource } = require('events'); 5const { 6 createHook, 7 executionAsyncId, 8} = require('async_hooks'); 9 10const { 11 deepStrictEqual, 12 strictEqual, 13 throws, 14} = require('assert'); 15 16const { 17 setImmediate: tick, 18} = require('timers/promises'); 19 20function makeHook(trackedTypes) { 21 const eventMap = new Map(); 22 23 function log(asyncId, name) { 24 const entry = eventMap.get(asyncId); 25 if (entry !== undefined) entry.push({ name }); 26 } 27 28 const hook = createHook({ 29 init(asyncId, type, triggerAsyncId, resource) { 30 if (trackedTypes.includes(type)) { 31 eventMap.set(asyncId, [ 32 { 33 name: 'init', 34 type, 35 triggerAsyncId, 36 resource, 37 }, 38 ]); 39 } 40 }, 41 42 before(asyncId) { log(asyncId, 'before'); }, 43 after(asyncId) { log(asyncId, 'after'); }, 44 destroy(asyncId) { log(asyncId, 'destroy'); }, 45 }).enable(); 46 47 return { 48 done() { 49 hook.disable(); 50 return new Set(eventMap.values()); 51 }, 52 ids() { 53 return new Set(eventMap.keys()); 54 }, 55 }; 56} 57 58// Tracks emit() calls correctly using async_hooks 59(async () => { 60 const tracer = makeHook(['Foo']); 61 62 class Foo extends EventEmitterAsyncResource {} 63 64 const origExecutionAsyncId = executionAsyncId(); 65 const foo = new Foo(); 66 67 foo.on('someEvent', common.mustCall()); 68 foo.emit('someEvent'); 69 70 deepStrictEqual([foo.asyncId], [...tracer.ids()]); 71 strictEqual(foo.triggerAsyncId, origExecutionAsyncId); 72 strictEqual(foo.asyncResource.eventEmitter, foo); 73 74 foo.emitDestroy(); 75 76 await tick(); 77 78 deepStrictEqual(tracer.done(), new Set([ 79 [ 80 { 81 name: 'init', 82 type: 'Foo', 83 triggerAsyncId: origExecutionAsyncId, 84 resource: foo.asyncResource, 85 }, 86 { name: 'before' }, 87 { name: 'after' }, 88 { name: 'destroy' }, 89 ], 90 ])); 91})().then(common.mustCall()); 92 93// Can explicitly specify name as positional arg 94(async () => { 95 const tracer = makeHook(['ResourceName']); 96 97 const origExecutionAsyncId = executionAsyncId(); 98 class Foo extends EventEmitterAsyncResource {} 99 100 const foo = new Foo('ResourceName'); 101 102 deepStrictEqual(tracer.done(), new Set([ 103 [ 104 { 105 name: 'init', 106 type: 'ResourceName', 107 triggerAsyncId: origExecutionAsyncId, 108 resource: foo.asyncResource, 109 }, 110 ], 111 ])); 112})().then(common.mustCall()); 113 114// Can explicitly specify name as option 115(async () => { 116 const tracer = makeHook(['ResourceName']); 117 118 const origExecutionAsyncId = executionAsyncId(); 119 class Foo extends EventEmitterAsyncResource {} 120 121 const foo = new Foo({ name: 'ResourceName' }); 122 123 deepStrictEqual(tracer.done(), new Set([ 124 [ 125 { 126 name: 'init', 127 type: 'ResourceName', 128 triggerAsyncId: origExecutionAsyncId, 129 resource: foo.asyncResource, 130 }, 131 ], 132 ])); 133})().then(common.mustCall()); 134 135// Member methods ERR_INVALID_THIS 136throws( 137 () => EventEmitterAsyncResource.prototype.emit(), 138 { code: 'ERR_INVALID_THIS' } 139); 140 141throws( 142 () => EventEmitterAsyncResource.prototype.emitDestroy(), 143 { code: 'ERR_INVALID_THIS' } 144); 145 146['asyncId', 'triggerAsyncId', 'asyncResource'].forEach((getter) => { 147 throws( 148 () => Reflect.get(EventEmitterAsyncResource.prototype, getter, {}), 149 { 150 code: 'ERR_INVALID_THIS', 151 name: /TypeError/, 152 message: 'Value of "this" must be of type EventEmitterAsyncResource', 153 stack: new RegExp(`at get ${getter}`), 154 } 155 ); 156}); 157