Skip to content

Commit 0aab92f

Browse files
MayaLekovaaddaleax
authored andcommitted
test: add test for async hooks parity for async/await
Add a basic test ensuring parity between before-after and init-promiseResolve hooks when using async/await. Add ability to initHooks and to checkInvocations utilities to transmit promiseResolve hook as well. See: #20516 PR-URL: #20626 Refs: #20516 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Benedikt Meurer <[email protected]>
1 parent 7a98008 commit 0aab92f

File tree

3 files changed

+106
-2
lines changed

3 files changed

+106
-2
lines changed

test/async-hooks/hook-checks.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ exports.checkInvocations = function checkInvocations(activity, hooks, stage) {
2525
);
2626

2727
// Check that actual invocations for all hooks match the expected invocations
28-
[ 'init', 'before', 'after', 'destroy' ].forEach(checkHook);
28+
[ 'init', 'before', 'after', 'destroy', 'promiseResolve' ].forEach(checkHook);
2929

3030
function checkHook(k) {
3131
const val = hooks[k];

test/async-hooks/init-hooks.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class ActivityCollector {
2525
onbefore,
2626
onafter,
2727
ondestroy,
28+
onpromiseResolve,
2829
logid = null,
2930
logtype = null
3031
} = {}) {
@@ -39,13 +40,16 @@ class ActivityCollector {
3940
this.onbefore = typeof onbefore === 'function' ? onbefore : noop;
4041
this.onafter = typeof onafter === 'function' ? onafter : noop;
4142
this.ondestroy = typeof ondestroy === 'function' ? ondestroy : noop;
43+
this.onpromiseResolve = typeof onpromiseResolve === 'function' ?
44+
onpromiseResolve : noop;
4245

4346
// Create the hook with which we'll collect activity data
4447
this._asyncHook = async_hooks.createHook({
4548
init: this._init.bind(this),
4649
before: this._before.bind(this),
4750
after: this._after.bind(this),
48-
destroy: this._destroy.bind(this)
51+
destroy: this._destroy.bind(this),
52+
promiseResolve: this._promiseResolve.bind(this)
4953
});
5054
}
5155

@@ -206,6 +210,13 @@ class ActivityCollector {
206210
this.ondestroy(uid);
207211
}
208212

213+
_promiseResolve(uid) {
214+
const h = this._getActivity(uid, 'promiseResolve');
215+
this._stamp(h, 'promiseResolve');
216+
this._maybeLog(uid, h && h.type, 'promiseResolve');
217+
this.onpromiseResolve(uid);
218+
}
219+
209220
_maybeLog(uid, type, name) {
210221
if (this._logid &&
211222
(type == null || this._logtype == null || this._logtype === type)) {
@@ -219,6 +230,7 @@ exports = module.exports = function initHooks({
219230
onbefore,
220231
onafter,
221232
ondestroy,
233+
onpromiseResolve,
222234
allowNoInit,
223235
logid,
224236
logtype
@@ -228,6 +240,7 @@ exports = module.exports = function initHooks({
228240
onbefore,
229241
onafter,
230242
ondestroy,
243+
onpromiseResolve,
231244
allowNoInit,
232245
logid,
233246
logtype

test/async-hooks/test-async-await.js

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
// This test ensures async hooks are being properly called
5+
// when using async-await mechanics. This involves:
6+
// 1. Checking that all initialized promises are being resolved
7+
// 2. Checking that for each 'before' corresponding hook 'after' hook is called
8+
9+
const assert = require('assert');
10+
const initHooks = require('./init-hooks');
11+
12+
const util = require('util');
13+
14+
const sleep = util.promisify(setTimeout);
15+
// either 'inited' or 'resolved'
16+
const promisesInitState = new Map();
17+
// either 'before' or 'after' AND asyncId must be present in the other map
18+
const promisesExecutionState = new Map();
19+
20+
const hooks = initHooks({
21+
oninit,
22+
onbefore,
23+
onafter,
24+
ondestroy: null, // Intentionally not tested, since it will be removed soon
25+
onpromiseResolve
26+
});
27+
hooks.enable();
28+
29+
function oninit(asyncId, type, triggerAsyncId, resource) {
30+
if (type === 'PROMISE') {
31+
promisesInitState.set(asyncId, 'inited');
32+
}
33+
}
34+
35+
function onbefore(asyncId) {
36+
if (!promisesInitState.has(asyncId)) {
37+
return;
38+
}
39+
promisesExecutionState.set(asyncId, 'before');
40+
}
41+
42+
function onafter(asyncId) {
43+
if (!promisesInitState.has(asyncId)) {
44+
return;
45+
}
46+
47+
assert.strictEqual(promisesExecutionState.get(asyncId), 'before',
48+
'after hook called for promise without prior call' +
49+
'to before hook');
50+
assert.strictEqual(promisesInitState.get(asyncId), 'resolved',
51+
'after hook called for promise without prior call' +
52+
'to resolve hook');
53+
promisesExecutionState.set(asyncId, 'after');
54+
}
55+
56+
function onpromiseResolve(asyncId) {
57+
assert(promisesInitState.has(asyncId),
58+
'resolve hook called for promise without prior call to init hook');
59+
60+
promisesInitState.set(asyncId, 'resolved');
61+
}
62+
63+
const timeout = common.platformTimeout(10);
64+
65+
function checkPromisesInitState() {
66+
for (const initState of promisesInitState.values()) {
67+
assert.strictEqual(initState, 'resolved',
68+
'promise initialized without being resolved');
69+
}
70+
}
71+
72+
function checkPromisesExecutionState() {
73+
for (const executionState of promisesExecutionState.values()) {
74+
assert.strictEqual(executionState, 'after',
75+
'mismatch between before and after hook calls');
76+
}
77+
}
78+
79+
process.on('beforeExit', common.mustCall(() => {
80+
hooks.disable();
81+
hooks.sanityCheck('PROMISE');
82+
83+
checkPromisesInitState();
84+
checkPromisesExecutionState();
85+
}));
86+
87+
async function asyncFunc() {
88+
await sleep(timeout);
89+
}
90+
91+
asyncFunc();

0 commit comments

Comments
 (0)