• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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