test: ensure assertions are reachable in test/async-hooks

PR-URL: https://github.com/nodejs/node/pull/60150
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
This commit is contained in:
Antoine du Hamel 2025-10-09 23:57:24 +02:00 committed by GitHub
parent 712cee951c
commit bfc81ca228
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 142 additions and 168 deletions

View File

@ -1,5 +1,5 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
/**
@ -15,7 +15,7 @@ const assert = require('assert');
* @param {string} stage the name of the stage in the test at which we are
* checking the invocations
*/
exports.checkInvocations = function checkInvocations(activity, hooks, stage) {
exports.checkInvocations = common.mustCallAtLeast(function checkInvocations(activity, hooks, stage) {
const stageInfo = `Checking invocations at stage "${stage}":\n `;
assert.ok(activity != null,
@ -24,9 +24,7 @@ exports.checkInvocations = function checkInvocations(activity, hooks, stage) {
);
// Check that actual invocations for all hooks match the expected invocations
[ 'init', 'before', 'after', 'destroy', 'promiseResolve' ].forEach(checkHook);
function checkHook(k) {
[ 'init', 'before', 'after', 'destroy', 'promiseResolve' ].forEach((k) => {
const val = hooks[k];
// Not expected ... all good
if (val == null) return;
@ -49,5 +47,5 @@ exports.checkInvocations = function checkInvocations(activity, hooks, stage) {
`time(s), but expected ${val} invocation(s).`;
assert.strictEqual(activity[k].length, val, msg2);
}
}
};
});
}, 0);

View File

@ -1,5 +1,5 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const {
executionAsyncResource,
@ -22,16 +22,16 @@ const server = http.createServer((req, res) => {
}, 1000);
});
server.listen(0, () => {
server.listen(0, common.mustCallAtLeast(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
http.get({ port: server.address().port }, (res) => {
http.get({ port: server.address().port }, common.mustCallAtLeast((res) => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
res.on('data', () => {
res.on('data', common.mustCallAtLeast(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
});
res.on('end', () => {
}));
res.on('end', common.mustCall(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
server.close();
});
});
});
}));
}));
}));

View File

@ -1,6 +1,6 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const {
executionAsyncResource,
@ -20,11 +20,11 @@ const server = http.createServer((req, res) => {
res.end('ok');
});
server.listen(0, () => {
server.listen(0, common.mustCall(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
http.get({ port: server.address().port }, () => {
http.get({ port: server.address().port }, common.mustCall(() => {
assert.strictEqual(executionAsyncResource(), hooked[executionAsyncId()]);
server.close();
});
});
}));
}));

View File

@ -1,13 +1,13 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run({}, (runArg) => {
asyncLocalStorage.run({}, common.mustCall((runArg) => {
assert.strictEqual(runArg, 'foo');
asyncLocalStorage.exit((exitArg) => {
asyncLocalStorage.exit(common.mustCall((exitArg) => {
assert.strictEqual(exitArg, 'bar');
}, 'bar');
}, 'foo');
}), 'bar');
}), 'foo');

View File

@ -1,6 +1,6 @@
'use strict';
require('../common');
const common = require('../common');
// Regression tests for https://github.com/nodejs/node/issues/40693
@ -10,17 +10,17 @@ const { AsyncLocalStorage } = require('async_hooks');
dgram.createSocket('udp4')
.on('message', function(msg, rinfo) { this.send(msg, rinfo.port); })
.on('listening', function() {
.on('listening', common.mustCall(function() {
const asyncLocalStorage = new AsyncLocalStorage();
const store = { val: 'abcd' };
asyncLocalStorage.run(store, () => {
asyncLocalStorage.run(store, common.mustCall(() => {
const client = dgram.createSocket('udp4');
client.on('message', (msg, rinfo) => {
client.on('message', common.mustCall((msg, rinfo) => {
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
client.close();
this.close();
});
}));
client.send('Hello, world!', this.address().port);
});
})
}));
}))
.bind(0);

View File

@ -1,11 +1,11 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.run(new Map(), common.mustCall(() => {
asyncLocalStorage.getStore().set('foo', 'bar');
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar');
@ -17,16 +17,16 @@ asyncLocalStorage.run(new Map(), () => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
// Calls to exit() should not mess with enabled status
asyncLocalStorage.exit(() => {
asyncLocalStorage.exit(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
});
}));
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
asyncLocalStorage.run(new Map().set('bar', 'foo'), () => {
asyncLocalStorage.run(new Map().set('bar', 'foo'), common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('bar'), 'foo');
});
}));
});
});
});
}));

View File

@ -1,20 +1,20 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
setImmediate(() => {
setImmediate(common.mustCall(() => {
const store = { foo: 'bar' };
asyncLocalStorage.enterWith(store);
assert.strictEqual(asyncLocalStorage.getStore(), store);
setTimeout(() => {
setTimeout(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), store);
}, 10);
});
}), 10);
}));
setTimeout(() => {
setTimeout(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}, 10);
}), 10);

View File

@ -11,11 +11,11 @@ const { onGC } = require('../common/gc');
let asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run({}, () => {
asyncLocalStorage.run({}, common.mustCall(() => {
asyncLocalStorage.disable();
onGC(asyncLocalStorage, { ongc: common.mustCall() });
});
}));
if (AsyncContextFrame.enabled) {
// This disable() is needed to remove reference form AsyncContextFrame

View File

@ -21,7 +21,7 @@ server.listen(0, common.mustCall(() => {
const port = server.address().port;
for (let i = 0; i < N; i++) {
asyncLocalStorage.run(i, () => {
asyncLocalStorage.run(i, common.mustCall(() => {
http.get({ agent, port }, common.mustCall((res) => {
assert.strictEqual(asyncLocalStorage.getStore(), i);
if (++responses === N) {
@ -30,6 +30,6 @@ server.listen(0, common.mustCall(() => {
}
res.resume();
}));
});
}));
}
}));

View File

@ -1,5 +1,5 @@
'use strict';
require('../common');
const { mustCall } = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
@ -13,9 +13,9 @@ server.listen(0, () => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'world');
http.get({ host: 'localhost', port: server.address().port }, () => {
http.get({ host: 'localhost', port: server.address().port }, mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
server.close();
});
}));
});
});

View File

@ -1,15 +1,15 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
asyncLocalStorage.run('hello node', () => {
asyncLocalStorage.run('hello node', common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), 'hello node');
});
}));
const runStore = { hello: 'node' };
asyncLocalStorage.run(runStore, () => {
asyncLocalStorage.run(runStore, common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), runStore);
});
}));

View File

@ -1,5 +1,5 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
@ -10,14 +10,14 @@ const inner = {};
function testInner() {
assert.strictEqual(asyncLocalStorage.getStore(), outer);
asyncLocalStorage.run(inner, () => {
asyncLocalStorage.run(inner, common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), inner);
});
}));
assert.strictEqual(asyncLocalStorage.getStore(), outer);
asyncLocalStorage.exit(() => {
asyncLocalStorage.exit(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
});
}));
assert.strictEqual(asyncLocalStorage.getStore(), outer);
}

View File

@ -1,38 +1,38 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
const asyncLocalStorage2 = new AsyncLocalStorage();
setTimeout(() => {
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage2.run(new Map(), () => {
setTimeout(common.mustCall(() => {
asyncLocalStorage.run(new Map(), common.mustCall(() => {
asyncLocalStorage2.run(new Map(), common.mustCall(() => {
const store = asyncLocalStorage.getStore();
const store2 = asyncLocalStorage2.getStore();
store.set('hello', 'world');
store2.set('hello', 'foo');
setTimeout(() => {
setTimeout(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
asyncLocalStorage.exit(() => {
asyncLocalStorage.exit(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
});
}));
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
}, 200);
});
});
}, 100);
}), 200);
}));
}));
}), 100);
setTimeout(() => {
asyncLocalStorage.run(new Map(), () => {
setTimeout(common.mustCall(() => {
asyncLocalStorage.run(new Map(), common.mustCall(() => {
const store = asyncLocalStorage.getStore();
store.set('hello', 'earth');
setTimeout(() => {
setTimeout(common.mustCall(() => {
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'earth');
}, 100);
});
}, 100);
}), 100);
}));
}), 100);

View File

@ -1,5 +1,5 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const { AsyncLocalStorage } = require('async_hooks');
@ -11,18 +11,14 @@ async function main() {
assert.strictEqual(asyncLocalStorage.getStore().get('a'), 1);
throw err;
});
await new Promise((resolve, reject) => {
await assert.rejects(new Promise((resolve, reject) => {
asyncLocalStorage.run(new Map(), () => {
const store = asyncLocalStorage.getStore();
store.set('a', 1);
next().then(resolve, reject);
});
})
.catch((e) => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
assert.strictEqual(e, err);
});
}), err);
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
}
main();
main().then(common.mustCall());

View File

@ -1,6 +1,6 @@
'use strict';
require('../common');
const common = require('../common');
// Regression tests for https://github.com/nodejs/node/issues/40693
@ -13,15 +13,15 @@ net
socket.write('Hello, world!');
socket.pipe(socket);
})
.listen(0, function() {
.listen(0, common.mustCall(function() {
const asyncLocalStorage = new AsyncLocalStorage();
const store = { val: 'abcd' };
asyncLocalStorage.run(store, () => {
asyncLocalStorage.run(store, common.mustCall(() => {
const client = net.connect({ port: this.address().port });
client.on('data', () => {
client.on('data', common.mustCall(() => {
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
client.end();
this.close();
});
});
});
}));
}));
}));

View File

@ -3,7 +3,7 @@
const common = require('../common');
const { Readable, finished } = require('stream');
const { AsyncLocalStorage } = require('async_hooks');
const { strictEqual } = require('assert');
const assert = require('assert');
// This test verifies that AsyncLocalStorage context is maintained
// when using stream.finished()
@ -11,10 +11,10 @@ const { strictEqual } = require('assert');
const readable = new Readable();
const als = new AsyncLocalStorage();
als.run(321, () => {
als.run(321, common.mustCall(() => {
finished(readable, common.mustCall(() => {
strictEqual(als.getStore(), 321);
assert.strictEqual(als.getStore(), 321);
}));
});
}));
readable.destroy();

View File

@ -22,32 +22,32 @@ function thenable() {
}
// Await a thenable
store.run(data, async () => {
store.run(data, common.mustCall(async () => {
assert.strictEqual(store.getStore(), data);
await thenable();
assert.strictEqual(store.getStore(), data);
});
}));
// Returning a thenable in an async function
store.run(data, async () => {
store.run(data, common.mustCall(async () => {
try {
assert.strictEqual(store.getStore(), data);
return thenable();
} finally {
assert.strictEqual(store.getStore(), data);
}
});
}));
// Resolving a thenable
store.run(data, () => {
store.run(data, common.mustCall(() => {
assert.strictEqual(store.getStore(), data);
Promise.resolve(thenable());
assert.strictEqual(store.getStore(), data);
});
}));
// Returning a thenable in a then handler
store.run(data, () => {
store.run(data, common.mustCall(() => {
assert.strictEqual(store.getStore(), data);
Promise.resolve().then(() => thenable());
assert.strictEqual(store.getStore(), data);
});
}));

View File

@ -22,15 +22,15 @@ tls
socket.write('Hello, world!');
socket.pipe(socket);
})
.listen(0, function() {
.listen(0, common.mustCall(function() {
const asyncLocalStorage = new AsyncLocalStorage();
const store = { val: 'abcd' };
asyncLocalStorage.run(store, () => {
asyncLocalStorage.run(store, common.mustCall(() => {
const client = tls.connect({ port: this.address().port, ...options });
client.on('data', () => {
client.on('data', common.mustCall(() => {
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
client.end();
this.close();
});
});
});
}));
}));
}));

View File

@ -13,12 +13,12 @@ const { createHook, AsyncResource } = require('async_hooks');
const resType = 'MyResource';
let activeId = -1;
createHook({
init(id, type) {
init: common.mustCallAtLeast((id, type) => {
if (type === resType) {
assert.strictEqual(activeId, -1);
activeId = id;
}
},
}),
destroy(id) {
if (activeId === id) {
activeId = -1;
@ -94,4 +94,4 @@ testNextTick();
tick(2, testQueueMicrotask);
tick(4, testImmediate);
tick(6, testPromise);
tick(8, () => testAwait().then(common.mustCall()));
tick(8, common.mustCall(() => testAwait().then(common.mustCall())));

View File

@ -31,7 +31,7 @@ server.on('stream', (stream) => {
});
});
server.on('close', common.mustCall(() => fs.closeSync(fd)));
server.listen(0, () => {
server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request();
@ -42,7 +42,7 @@ server.listen(0, () => {
server.close();
}));
req.end();
});
}));
process.on('exit', onExit);

View File

@ -24,7 +24,7 @@ const agent = new http.Agent({
maxSockets: 1,
});
const verifyRequest = (idx) => (res) => {
const verifyRequest = common.mustCall((idx) => common.mustCall((res) => {
reqAsyncIds[idx] = res.socket[async_id_symbol];
assert.ok(reqAsyncIds[idx] > 0, `${reqAsyncIds[idx]} > 0`);
if (socket) {
@ -42,7 +42,7 @@ const verifyRequest = (idx) => (res) => {
agent.destroy();
}
}));
};
}), 2);
const server = http.createServer(common.mustCall((req, res) => {
req.once('data', common.mustCallAtLeast(() => {
@ -59,13 +59,13 @@ const server = http.createServer(common.mustCall((req, res) => {
// First request.
const r1 = http.request({
agent, port, method: 'POST',
}, common.mustCall(verifyRequest(0)));
}, verifyRequest(0));
r1.end(payload);
// Second request. Sent in parallel with the first one.
const r2 = http.request({
agent, port, method: 'POST',
}, common.mustCall(verifyRequest(1)));
}, verifyRequest(1));
r2.end(payload);
}));

View File

@ -16,20 +16,20 @@ const fnsToTest = [setTimeout, (cb) => {
hook.disable();
});
});
}, (cb) => {
setImmediate(() => {
}, common.mustCall((cb) => {
setImmediate(common.mustCall(() => {
process.nextTick(() => {
cb();
// We need to keep the event loop open for this to actually work
// since destroy hooks are triggered in unrefed Immediates
setImmediate(() => {
setImmediate(common.mustCall(() => {
hook.disable();
assert.strictEqual(fnsToTest.length, 0);
});
}));
});
});
}];
}));
})];
const hook = async_hooks.createHook({
before: common.mustNotCall(),

View File

@ -24,13 +24,13 @@ const onchangex = (x) => (curr, prev) => {
console.log('previous stat data:', prev);
};
const checkWatcherStart = (name, watcher) => {
const checkWatcherStart = common.mustCall((name, watcher) => {
assert.strictEqual(watcher.type, 'STATWATCHER');
assert.strictEqual(typeof watcher.uid, 'number');
assert.strictEqual(watcher.triggerAsyncId, 1);
checkInvocations(watcher, { init: 1 },
`${name}: when started to watch file`);
};
}, 2);
const hooks = initHooks();
hooks.enable();
@ -67,7 +67,7 @@ w1.on('change', common.mustCallAtLeast((curr, prev) => {
if (prev.size !== 0 || curr.size !== 5)
return;
setImmediate(() => {
setImmediate(common.mustCall(() => {
checkInvocations(statwatcher1,
{ init: 1, before: w1HookCount, after: w1HookCount },
'watcher1: when unwatched first file');
@ -93,7 +93,7 @@ w1.on('change', common.mustCallAtLeast((curr, prev) => {
fs.unwatchFile(file2);
});
}));
});
}));
}));
process.once('exit', () => {

View File

@ -1,8 +1,7 @@
'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const util = require('util');
function findInGraph(graph, type, n) {
let found = 0;
@ -56,7 +55,7 @@ function pruneTickObjects(activities) {
return activities;
}
module.exports = function verifyGraph(hooks, graph) {
module.exports = common.mustCallAtLeast(function verifyGraph(hooks, graph) {
pruneTickObjects(hooks);
// Map actual ids to standin ids defined in the graph
@ -111,29 +110,4 @@ module.exports = function verifyGraph(hooks, graph) {
`Type '${type}': expecting: ${expTypes[type]} ` +
`found: ${typeSeen[type]}`);
}
};
//
// Helper to generate the input to the verifyGraph tests
//
function inspect(obj, depth) {
console.error(util.inspect(obj, false, depth || 5, true));
}
module.exports.printGraph = function printGraph(hooks) {
const ids = {};
const uidtoid = {};
const activities = pruneTickObjects(hooks.activities);
const graph = [];
activities.forEach(processNode);
function processNode(x) {
const key = x.type.replace(/WRAP/, '').toLowerCase();
ids[key] ||= 1;
const id = `${key}:${ids[key]++}`;
uidtoid[x.uid] = id;
const triggerAsyncId = uidtoid[x.triggerAsyncId] || null;
graph.push({ type: x.type, id, triggerAsyncId });
}
inspect(graph);
};
}, 0);

View File

@ -156,7 +156,7 @@ export default [
},
{
files: [
'test/{message,module-hooks,node-api,pummel,pseudo-tty,v8-updates,wasi}/**/*.{js,mjs,cjs}',
'test/{async-hooks,message,module-hooks,node-api,pummel,pseudo-tty,v8-updates,wasi}/**/*.{js,mjs,cjs}',
],
rules: {
'node-core/must-call-assert': 'error',

View File

@ -51,6 +51,9 @@ tester.run('must-call-assert', rule, {
new Promise(() => {
assert.ok(global.prop);
}).then(common.mustCall());
process.nextTick(() => {
assert.ok(String);
});
`,
`
import test from 'node:test';

View File

@ -60,11 +60,14 @@ module.exports = {
{
assert: (name) => name === 'rejects' || name === 'throws', // assert.throws or assert.rejects
common: isMustCallOrMustCallAtLeast, // common.mustCall or common.mustCallAtLeast
process: (name) => // process.on('exit', …)
(name === 'on' || name === 'once') &&
enclosingFn === parent.arguments[1] &&
parent.arguments[0].type === 'Literal' &&
parent.arguments[0].value === 'exit',
process: (name) =>
(name === 'nextTick' && enclosingFn === parent.arguments[0]) || // process.nextTick
( // process.on('exit', …)
(name === 'on' || name === 'once') &&
enclosingFn === parent.arguments[1] &&
parent.arguments[0].type === 'Literal' &&
parent.arguments[0].value === 'exit'
),
}[parent.callee.object.name]?.(parent.callee.property.name)
) {
return;