test.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import EventEmitterAsyncResource from '..';
  2. import { createHook, executionAsyncId, executionAsyncResource } from 'async_hooks';
  3. import { test } from 'tap';
  4. import { promisify } from 'util';
  5. const tick = promisify(setImmediate);
  6. interface InitAsyncEvent {
  7. name : 'init',
  8. type : string,
  9. triggerAsyncId : number,
  10. resource: object
  11. }
  12. interface OtherAsyncEvent {
  13. name : 'before' | 'after' | 'destroy'
  14. }
  15. type AsyncEvent = InitAsyncEvent | OtherAsyncEvent;
  16. function makeHook (trackedTypes : string[]) {
  17. const eventMap = new Map<number, AsyncEvent[]>();
  18. function log (asyncId : number, name : 'before' | 'after' | 'destroy') {
  19. const entry = eventMap.get(asyncId);
  20. if (entry !== undefined) entry.push({ name });
  21. }
  22. const hook = createHook({
  23. init (asyncId, type, triggerAsyncId, resource) {
  24. if (trackedTypes.includes(type)) {
  25. eventMap.set(asyncId, [
  26. { name: 'init', type, triggerAsyncId, resource }
  27. ]);
  28. }
  29. },
  30. before (asyncId) { log(asyncId, 'before'); },
  31. after (asyncId) { log(asyncId, 'after'); },
  32. destroy (asyncId) { log(asyncId, 'destroy'); }
  33. }).enable();
  34. return {
  35. done () {
  36. hook.disable();
  37. return new Set(eventMap.values());
  38. },
  39. ids () {
  40. return new Set(eventMap.keys());
  41. }
  42. };
  43. }
  44. test('tracks emit() calls correctly using async_hooks', async ({ is, isDeep }) => {
  45. const tracer = makeHook(['Foo']);
  46. class Foo extends EventEmitterAsyncResource {}
  47. const origExecutionAsyncId = executionAsyncId();
  48. const foo = new Foo();
  49. let called = false;
  50. foo.on('someEvent', () => {
  51. if (typeof executionAsyncResource === 'function') { // Node.js 12+ only
  52. is(executionAsyncResource(), foo.asyncResource);
  53. }
  54. called = true;
  55. });
  56. foo.emit('someEvent');
  57. is(called, true);
  58. isDeep([foo.asyncId()], [...tracer.ids()]);
  59. is(foo.triggerAsyncId(), origExecutionAsyncId);
  60. is(foo.asyncResource.eventEmitter, foo);
  61. foo.emitDestroy();
  62. await tick();
  63. isDeep(tracer.done(), new Set<AsyncEvent[]>([
  64. [
  65. { name: 'init', type: 'Foo', triggerAsyncId: origExecutionAsyncId, resource: foo.asyncResource },
  66. { name: 'before' },
  67. { name: 'after' },
  68. { name: 'destroy' }
  69. ]
  70. ]));
  71. });
  72. test('can explicitly specify name as positional arg', async ({ isDeep }) => {
  73. const tracer = makeHook(['ResourceName']);
  74. const origExecutionAsyncId = executionAsyncId();
  75. class Foo extends EventEmitterAsyncResource {}
  76. const foo = new Foo('ResourceName');
  77. isDeep(tracer.done(), new Set<AsyncEvent[]>([
  78. [
  79. { name: 'init', type: 'ResourceName', triggerAsyncId: origExecutionAsyncId, resource: foo.asyncResource }
  80. ]
  81. ]));
  82. });
  83. test('can explicitly specify name as option', async ({ isDeep }) => {
  84. const tracer = makeHook(['ResourceName']);
  85. const origExecutionAsyncId = executionAsyncId();
  86. class Foo extends EventEmitterAsyncResource {}
  87. const foo = new Foo({ name: 'ResourceName' });
  88. isDeep(tracer.done(), new Set<AsyncEvent[]>([
  89. [
  90. { name: 'init', type: 'ResourceName', triggerAsyncId: origExecutionAsyncId, resource: foo.asyncResource }
  91. ]
  92. ]));
  93. });
  94. test('is provided as export on itself', async ({ is }) => {
  95. is(EventEmitterAsyncResource.EventEmitterAsyncResource, EventEmitterAsyncResource);
  96. });