From 747d5e6dede25a39568927f44dcbdfdcb616747f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 5 Oct 2025 14:08:12 +0200 Subject: [PATCH 1/3] tools: add lint rule to ensure assertions are reached --- test/eslint.config_partial.mjs | 8 + test/message/assert_throws_stack.js | 2 +- test/message/node_run_non_existent.js | 2 +- .../test-module-hooks-import-wasm.mjs | 6 +- .../test-module-hooks-load-buffers.js | 12 +- .../test-module-hooks-load-chained.js | 10 +- .../test-module-hooks-load-detection.js | 6 +- ...st-module-hooks-load-url-change-import.mjs | 4 +- ...st-module-hooks-load-url-change-require.js | 4 +- .../test-module-hooks-require-wasm.js | 4 +- ...e-hooks-resolve-builtin-builtin-require.js | 1 + ...e-hooks-resolve-builtin-on-disk-require.js | 1 + .../test-gcable-callback.js | 12 +- test/node-api/test_async_context/test.js | 12 +- test/node-api/test_exception/test.js | 1 + .../test_make_callback/test-async-hooks.js | 8 +- .../test_make_callback_recurse/test.js | 4 +- .../test.js | 4 +- .../node-api/test_threadsafe_function/test.js | 12 +- test/parallel/test-eslint-must-call-assert.js | 166 ++++++++++++++++++ test/pseudo-tty/ref_keeps_node_running.js | 10 +- test/pseudo-tty/test-assert-colors.js | 2 +- test/pseudo-tty/test-assert-no-color.js | 2 +- .../pseudo-tty/test-handle-wrap-hasref-tty.js | 16 +- .../test-stderr-stdout-handle-sigwinch.js | 4 +- test/pseudo-tty/test-tty-color-support.js | 2 +- test/pseudo-tty/test-tty-isatty.js | 23 ++- test/pseudo-tty/test-tty-stdout-resize.js | 4 +- test/pummel/test-child-process-spawn-loop.js | 8 +- test/pummel/test-fs-readfile-tostring-fail.js | 2 +- test/pummel/test-fs-watch-file-slow.js | 6 +- test/pummel/test-fs-watch-non-recursive.js | 8 +- test/pummel/test-heapdump-http2.js | 4 +- .../test-http-many-keep-alive-connections.js | 6 +- test/pummel/test-net-many-clients.js | 12 +- test/pummel/test-net-pause.js | 22 +-- test/pummel/test-net-pingpong-delay.js | 51 +++--- test/pummel/test-net-pingpong.js | 43 ++--- test/pummel/test-net-timeout.js | 10 +- test/pummel/test-regress-GH-892.js | 12 +- test/pummel/test-timers.js | 16 +- test/pummel/test-tls-server-large-request.js | 9 +- test/pummel/test-vm-memleak.js | 4 +- test/wasi/test-wasi-stdio.js | 8 +- test/wasi/test-wasi-worker-terminate.js | 4 +- tools/eslint-rules/must-call-assert.js | 100 +++++++++++ 46 files changed, 454 insertions(+), 213 deletions(-) create mode 100644 test/parallel/test-eslint-must-call-assert.js create mode 100644 tools/eslint-rules/must-call-assert.js diff --git a/test/eslint.config_partial.mjs b/test/eslint.config_partial.mjs index 7e4f7a20541347..f9c78ebf9835c1 100644 --- a/test/eslint.config_partial.mjs +++ b/test/eslint.config_partial.mjs @@ -154,6 +154,14 @@ export default [ ], }, }, + { + files: [ + 'test/{message,module-hooks,node-api,pummel,pseudo-tty,v8-updates,wasi}/**/*.{js,mjs,cjs}', + ], + rules: { + 'node-core/must-call-assert': 'error', + }, + }, { files: [ 'test/{common,fixtures,wpt}/**/*.{js,mjs,cjs}', diff --git a/test/message/assert_throws_stack.js b/test/message/assert_throws_stack.js index 36bc5734cae37f..607ba10266df65 100644 --- a/test/message/assert_throws_stack.js +++ b/test/message/assert_throws_stack.js @@ -1,6 +1,6 @@ 'use strict'; require('../common'); -const assert = require('assert').strict; +const assert = require('assert/strict'); assert.throws(() => { throw new Error('foo'); }, { bar: true }); diff --git a/test/message/node_run_non_existent.js b/test/message/node_run_non_existent.js index 7809aa207f0ed9..c40eac49f7abc9 100644 --- a/test/message/node_run_non_existent.js +++ b/test/message/node_run_non_existent.js @@ -1,7 +1,7 @@ 'use strict'; require('../common'); -const assert = require('node:assert').strict; +const assert = require('node:assert/strict'); const childProcess = require('node:child_process'); const fixtures = require('../common/fixtures'); diff --git a/test/module-hooks/test-module-hooks-import-wasm.mjs b/test/module-hooks/test-module-hooks-import-wasm.mjs index 00e6cd41265077..9ee5d3a78ab4de 100644 --- a/test/module-hooks/test-module-hooks-import-wasm.mjs +++ b/test/module-hooks/test-module-hooks-import-wasm.mjs @@ -1,13 +1,13 @@ // This tests that module.registerHooks() can be used to support unknown formats, like // import(wasm) -import '../common/index.mjs'; +import { mustCall } from '../common/index.mjs'; import assert from 'node:assert'; import { registerHooks, createRequire } from 'node:module'; import { readFileSync } from 'node:fs'; registerHooks({ - load(url, context, nextLoad) { + load: mustCall((url, context, nextLoad) => { assert.match(url, /simple\.wasm$/); const source = `const buf = Buffer.from([${Array.from(readFileSync(new URL(url))).join(',')}]); @@ -21,7 +21,7 @@ registerHooks({ source, format: 'module', }; - }, + }), }); // Checks that it works with require. diff --git a/test/module-hooks/test-module-hooks-load-buffers.js b/test/module-hooks/test-module-hooks-load-buffers.js index 07f7374fd96161..c62c6c4d9881ad 100644 --- a/test/module-hooks/test-module-hooks-load-buffers.js +++ b/test/module-hooks/test-module-hooks-load-buffers.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const { registerHooks } = require('module'); @@ -15,15 +15,7 @@ const hook1 = registerHooks({ resolve(specifier, context, nextResolve) { return { shortCircuit: true, url: `test://${specifier}` }; }, - load(url, context, nextLoad) { - const result = nextLoad(url, context); - if (url === 'test://array_buffer') { - assert.deepStrictEqual(result.source, encoder.encode(arrayBufferSource).buffer); - } else if (url === 'test://array_buffer_view') { - assert.deepStrictEqual(result.source, encoder.encode(arrayBufferViewSource)); - } - return result; - }, + load: common.mustNotCall(), }); const hook2 = registerHooks({ diff --git a/test/module-hooks/test-module-hooks-load-chained.js b/test/module-hooks/test-module-hooks-load-chained.js index 5227658262a752..27219eb599e8e6 100644 --- a/test/module-hooks/test-module-hooks-load-chained.js +++ b/test/module-hooks/test-module-hooks-load-chained.js @@ -1,30 +1,30 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const { registerHooks } = require('module'); // Test that multiple loaders works together. const hook1 = registerHooks({ - load(url, context, nextLoad) { + load: common.mustCall((url, context, nextLoad) => { const result = nextLoad(url, context); assert.strictEqual(result.source, ''); return { source: 'exports.hello = "world"', format: 'commonjs', }; - }, + }), }); const hook2 = registerHooks({ - load(url, context, nextLoad) { + load: common.mustCall((url, context, nextLoad) => { const result = nextLoad(url, context); assert.strictEqual(result.source, 'exports.hello = "world"'); return { source: 'export const hello = "world"', format: 'module', }; - }, + }), }); const mod = require('../fixtures/empty.js'); diff --git a/test/module-hooks/test-module-hooks-load-detection.js b/test/module-hooks/test-module-hooks-load-detection.js index 9915b98440355b..37934023683ff8 100644 --- a/test/module-hooks/test-module-hooks-load-detection.js +++ b/test/module-hooks/test-module-hooks-load-detection.js @@ -1,18 +1,18 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const { registerHooks } = require('module'); // Test that module syntax detection works. const hook = registerHooks({ - load(url, context, nextLoad) { + load: common.mustCall((url, context, nextLoad) => { const result = nextLoad(url, context); assert.strictEqual(result.source, ''); return { source: 'export const hello = "world"', }; - }, + }), }); const mod = require('../fixtures/empty.js'); diff --git a/test/module-hooks/test-module-hooks-load-url-change-import.mjs b/test/module-hooks/test-module-hooks-load-url-change-import.mjs index a65975ab1fad16..914072b0ff8e56 100644 --- a/test/module-hooks/test-module-hooks-load-url-change-import.mjs +++ b/test/module-hooks/test-module-hooks-load-url-change-import.mjs @@ -7,13 +7,13 @@ import { fileURL } from '../common/fixtures.mjs'; // It changes `foo` package name into `redirected-fs` and then loads `redirected-fs` const hook = registerHooks({ - resolve(specifier, context, nextResolve) { + resolve: mustCall((specifier, context, nextResolve) => { assert.strictEqual(specifier, 'foo'); return { url: 'foo://bar', shortCircuit: true, }; - }, + }), load: mustCall(function load(url, context, nextLoad) { assert.strictEqual(url, 'foo://bar'); return nextLoad(fileURL('module-hooks', 'redirected-fs.js').href, context); diff --git a/test/module-hooks/test-module-hooks-load-url-change-require.js b/test/module-hooks/test-module-hooks-load-url-change-require.js index 7f10110cda8c50..4f74471d8772f8 100644 --- a/test/module-hooks/test-module-hooks-load-url-change-require.js +++ b/test/module-hooks/test-module-hooks-load-url-change-require.js @@ -9,13 +9,13 @@ const fixtures = require('../common/fixtures'); // It changes `foo` package name into `redirected-fs` and then loads `redirected-fs` const hook = registerHooks({ - resolve(specifier, context, nextResolve) { + resolve: common.mustCall((specifier, context, nextResolve) => { assert.strictEqual(specifier, 'foo'); return { url: 'foo://bar', shortCircuit: true, }; - }, + }), load: common.mustCall(function load(url, context, nextLoad) { assert.strictEqual(url, 'foo://bar'); return nextLoad( diff --git a/test/module-hooks/test-module-hooks-require-wasm.js b/test/module-hooks/test-module-hooks-require-wasm.js index 1cf7da6451f218..d9f865b673cf0a 100644 --- a/test/module-hooks/test-module-hooks-require-wasm.js +++ b/test/module-hooks/test-module-hooks-require-wasm.js @@ -9,7 +9,7 @@ const { registerHooks } = require('module'); const { readFileSync } = require('fs'); registerHooks({ - load(url, context, nextLoad) { + load: common.mustCall((url, context, nextLoad) => { assert.match(url, /simple\.wasm$/); const source = `const buf = Buffer.from([${Array.from(readFileSync(new URL(url))).join(',')}]); @@ -20,7 +20,7 @@ registerHooks({ source, format: 'commonjs', }; - }, + }, 2), }); // Checks that it works with require. diff --git a/test/module-hooks/test-module-hooks-resolve-builtin-builtin-require.js b/test/module-hooks/test-module-hooks-resolve-builtin-builtin-require.js index 6de7b0d23d6675..35d5ccdbb946f6 100644 --- a/test/module-hooks/test-module-hooks-resolve-builtin-builtin-require.js +++ b/test/module-hooks/test-module-hooks-resolve-builtin-builtin-require.js @@ -21,6 +21,7 @@ const hook = registerHooks({ // Check assert, which is already loaded. // zlib.createGzip is a function. +// eslint-disable-next-line node-core/must-call-assert assert.strictEqual(typeof require('assert').createGzip, 'function'); hook.deregister(); diff --git a/test/module-hooks/test-module-hooks-resolve-builtin-on-disk-require.js b/test/module-hooks/test-module-hooks-resolve-builtin-on-disk-require.js index 0006975867ce9c..a424889cdafa01 100644 --- a/test/module-hooks/test-module-hooks-resolve-builtin-on-disk-require.js +++ b/test/module-hooks/test-module-hooks-resolve-builtin-on-disk-require.js @@ -20,6 +20,7 @@ const hook = registerHooks({ }); // Check assert, which is already loaded. +// eslint-disable-next-line node-core/must-call-assert assert.strictEqual(require('assert').exports_for_test, 'redirected assert'); // Check zlib, which is not yet loaded. assert.strictEqual(require('zlib').exports_for_test, 'redirected zlib'); diff --git a/test/node-api/test_async_context/test-gcable-callback.js b/test/node-api/test_async_context/test-gcable-callback.js index a37245525daa11..b1e4d29c5f41e5 100644 --- a/test/node-api/test_async_context/test-gcable-callback.js +++ b/test/node-api/test_async_context/test-gcable-callback.js @@ -46,17 +46,17 @@ for (let i = 0; i < arr.length; i++) arr[i] = {}; assert.strictEqual(hook_result.destroy_called, false); -setImmediate(() => { +setImmediate(common.mustCall(() => { assert.strictEqual(hook_result.destroy_called, false); - makeCallback(asyncResource, process, () => { + makeCallback(asyncResource, process, common.mustCall(() => { const executionAsyncResource = async_hooks.executionAsyncResource(); // Previous versions of Node-API would have gargbage-collected // the `asyncResource` object, now we can just assert that it is intact. assert.strictEqual(typeof executionAsyncResource, 'object'); assert.strictEqual(executionAsyncResource.foo, 'bar'); destroyAsyncResource(asyncResource); - setImmediate(() => { + setImmediate(common.mustCall(() => { assert.strictEqual(hook_result.destroy_called, true); - }); - }); -}); + })); + })); +})); diff --git a/test/node-api/test_async_context/test.js b/test/node-api/test_async_context/test.js index 9dae6345137f0a..e4e46afbf2eb83 100644 --- a/test/node-api/test_async_context/test.js +++ b/test/node-api/test_async_context/test.js @@ -40,7 +40,7 @@ const resourceWrap = createAsyncResource( assert.strictEqual(hook_result.destroy_called, false); const recv = {}; -makeCallback(resourceWrap, recv, function callback() { +makeCallback(resourceWrap, recv, common.mustCall(function callback() { assert.strictEqual(hook_result.destroy_called, false); assert.strictEqual( hook_result.resource, @@ -48,7 +48,7 @@ makeCallback(resourceWrap, recv, function callback() { ); assert.strictEqual(this, recv); - setImmediate(() => { + setImmediate(common.mustCall(() => { assert.strictEqual(hook_result.destroy_called, false); assert.notStrictEqual( hook_result.resource, @@ -56,8 +56,8 @@ makeCallback(resourceWrap, recv, function callback() { ); destroyAsyncResource(resourceWrap); - setImmediate(() => { + setImmediate(common.mustCall(() => { assert.strictEqual(hook_result.destroy_called, true); - }); - }); -}); + })); + })); +})); diff --git a/test/node-api/test_exception/test.js b/test/node-api/test_exception/test.js index 1373d8c06fb747..8a2ffdcd27d2d3 100644 --- a/test/node-api/test_exception/test.js +++ b/test/node-api/test_exception/test.js @@ -11,6 +11,7 @@ function testFinalize(binding) { x = null; global.gc(); process.on('uncaughtException', (err) => { + // eslint-disable-next-line node-core/must-call-assert assert.strictEqual(err.message, 'Error during Finalize'); }); diff --git a/test/node-api/test_make_callback/test-async-hooks.js b/test/node-api/test_make_callback/test-async-hooks.js index ca31ac4995ae5b..d6053c5058a1b0 100644 --- a/test/node-api/test_make_callback/test-async-hooks.js +++ b/test/node-api/test_make_callback/test-async-hooks.js @@ -39,15 +39,15 @@ test_hook.enable(); * slots. Testing with plain object here. */ const resource = {}; -makeCallback(resource, process, function cb() { +makeCallback(resource, process, common.mustCall(function cb() { assert.strictEqual(this, process); assert.strictEqual(async_hooks.executionAsyncResource(), resource); -}); +})); assert.strictEqual(hook_result.init_called, true); assert.strictEqual(hook_result.before_called, true); assert.strictEqual(hook_result.after_called, true); -setImmediate(() => { +setImmediate(common.mustCall(() => { assert.strictEqual(hook_result.destroy_called, true); test_hook.disable(); -}); +})); diff --git a/test/node-api/test_make_callback_recurse/test.js b/test/node-api/test_make_callback_recurse/test.js index f948103de1f4ec..6a35f970e34eae 100644 --- a/test/node-api/test_make_callback_recurse/test.js +++ b/test/node-api/test_make_callback_recurse/test.js @@ -72,11 +72,11 @@ assert.throws(function() { if (arg === 1) { // The tests are first run on bootstrap during LoadEnvironment() in // src/node.cc. Now run the tests through node::MakeCallback(). - setImmediate(function() { + setImmediate(common.mustCall(function() { makeCallback({}, common.mustCall(function() { verifyExecutionOrder(2); })); - }); + })); } else if (arg === 2) { // Make sure there are no conflicts using node::MakeCallback() // within timers. diff --git a/test/node-api/test_reference_by_node_api_version/test.js b/test/node-api/test_reference_by_node_api_version/test.js index 6a9078600e16d8..a1172b492a73dc 100644 --- a/test/node-api/test_reference_by_node_api_version/test.js +++ b/test/node-api/test_reference_by_node_api_version/test.js @@ -7,7 +7,7 @@ // and symbol types, while in newer versions they can be created for // any value type. // -const { buildType } = require('../../common'); +const { mustCall, buildType } = require('../../common'); const { gcUntil } = require('../../common/gc'); const assert = require('assert'); const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`); @@ -122,4 +122,4 @@ async function runAllTests() { await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ false); } -runAllTests(); +runAllTests().then(mustCall()); diff --git a/test/node-api/test_threadsafe_function/test.js b/test/node-api/test_threadsafe_function/test.js index 8cb033419bf41a..cdd637805a5e9a 100644 --- a/test/node-api/test_threadsafe_function/test.js +++ b/test/node-api/test_threadsafe_function/test.js @@ -44,9 +44,9 @@ function testWithJSMarshaller({ array.push(value); if (array.length === quitAfter) { setImmediate(() => { - binding.StopThread(common.mustCall(() => { + binding.StopThread(() => { resolve(array); - }), !!abort); + }, !!abort); }); } }, !!abort, !!launchSecondary, maxQueueSize); @@ -86,9 +86,9 @@ new Promise(function testWithoutJSMarshaller(resolve) { assert.strictEqual(arguments.length, 0); if (callCount === binding.ARRAY_LENGTH) { setImmediate(() => { - binding.StopThread(common.mustCall(() => { + binding.StopThread(() => { resolve(); - }), false); + }, false); }); } }, false /* abort */, false /* launchSecondary */, binding.MAX_QUEUE_SIZE); @@ -224,4 +224,6 @@ new Promise(function testWithoutJSMarshaller(resolve) { .then(() => testUnref(binding.MAX_QUEUE_SIZE)) // Start a child process with an infinite queue to test rapid teardown -.then(() => testUnref(0)); +.then(() => testUnref(0)) + +.then(common.mustCall()); diff --git a/test/parallel/test-eslint-must-call-assert.js b/test/parallel/test-eslint-must-call-assert.js new file mode 100644 index 00000000000000..5cf488271fd5f8 --- /dev/null +++ b/test/parallel/test-eslint-must-call-assert.js @@ -0,0 +1,166 @@ +'use strict'; +const common = require('../common'); +if ((!common.hasCrypto) || (!common.hasIntl)) { + common.skip('ESLint tests require crypto and Intl'); +} +common.skipIfEslintMissing(); + +const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester; +const rule = require('../../tools/eslint-rules/must-call-assert'); + +const message = 'Assertions must be wrapped into `common.mustCall` or `common.mustCallAtLeast`'; + +const tester = new RuleTester(); +tester.run('must-call-assert', rule, { + valid: [ + 'assert.strictEqual(2+2, 4)', + 'process.on("message", common.mustCallAtLeast((code) => {assert.strictEqual(code, 0)}));', + 'process.once("message", common.mustCall((code) => {assert.strictEqual(code, 0)}));', + 'process.once("message", common.mustCall((code) => {if(2+2 === 5) { assert.strictEqual(code, 0)} }));', + 'process.once("message", common.mustCall((code) => { (() => assert.strictEqual(code, 0))(); }));', + '(async () => {await assert.rejects(fun())})().then()', + '[1, true].forEach((val) => assert.strictEqual(fun(val), 0));', + 'const assert = require("node:assert")', + 'const assert = require("assert")', + 'const assert = require("assert/strict")', + 'const assert = require("node:assert/strict")', + 'import assert from "assert"', + 'import * as assert from "assert"', + 'import assert from "assert/strict"', + 'import * as assert from "assert/strict"', + 'import assert from "node:assert"', + 'import * as assert from "node:assert"', + 'import assert from "node:assert/strict"', + 'import * as assert from "node:assert/strict"', + ` + assert.throws(() => {}, (err) => { + assert.strictEqual(err, 5); + }); + process.on('exit', () => { + assert.ok(); + }); + process.once('exit', () => { + assert.ok(); + }); + process.on('message', () => { + assert.fail('error message'); + }); + Promise.resolve().then((arg) => { + assert.ok(arg); + }).then(common.mustCall()); + new Promise(() => { + assert.ok(global.prop); + }).then(common.mustCall()); + `, + ` + import test from 'node:test'; + import assert from 'node:assert'; + + test("whatever", () => { + assert.strictEqual(2+2, 5); + }); + `, + ` + import test from 'node:test'; + import assert from 'node:assert'; + + describe("whatever", () => { + it("should not be reported", async () => { + assert.strictEqual(2+2, 5); + }); + }); + `, + ], + invalid: [ + { + code: 'process.on("message", (code) => assert.strictEqual(code, 0))', + errors: [{ message }], + }, + { + code: ` + process.once("message", () => { + process.once("message", common.mustCall((code) => { + assert.strictEqual(code, 0); + })); + }); + `, + errors: [{ message }], + }, + { + code: 'function test() { process.on("message", (code) => assert.strictEqual(code, 0)) }', + errors: [{ message }], + }, + { + code: 'process.once("message", (code) => {if(2+2 === 5) { assert.strictEqual(code, 0)} });', + errors: [{ message }], + }, + { + code: 'process.once("message", (code) => { (() => { assert.strictEqual(code, 0)})(); });', + errors: [{ message }], + }, + { + code: 'child.once("exit", common.mustCall((code) => {setImmediate(() => { assert.strictEqual(code, 0)}); }));', + errors: [{ message }], + }, + { + code: 'require("node:assert").strictEqual(2+2, 5)', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'const { strictEqual } = require("node:assert")', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'const { strictEqual } = require("node:assert/strict")', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'const { strictEqual } = require("assert")', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'const { strictEqual } = require("assert/strict")', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'const someOtherName = require("assert")', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import assert, { strictEqual } from "assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import * as someOtherName from "assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import someOtherName from "assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import "assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import { strictEqual } from "node:assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import assert, { strictEqual } from "node:assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import * as someOtherName from "node:assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import someOtherName from "node:assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + { + code: 'import "node:assert"', + errors: [{ message: 'Only assign `node:assert` to `assert`' }], + }, + ] +}); diff --git a/test/pseudo-tty/ref_keeps_node_running.js b/test/pseudo-tty/ref_keeps_node_running.js index 52761c140eddac..c752db857d6da7 100644 --- a/test/pseudo-tty/ref_keeps_node_running.js +++ b/test/pseudo-tty/ref_keeps_node_running.js @@ -5,9 +5,9 @@ require('../common'); const { internalBinding } = require('internal/test/binding'); const { TTY, isTTY } = internalBinding('tty_wrap'); -const strictEqual = require('assert').strictEqual; +const assert = require('assert'); -strictEqual(isTTY(0), true, 'fd 0 is not a TTY'); +assert.ok(isTTY(0), 'fd 0 is not a TTY'); const handle = new TTY(0); handle.readStart(); @@ -17,14 +17,14 @@ function isHandleActive(handle) { return process._getActiveHandles().some((active) => active === handle); } -strictEqual(isHandleActive(handle), true, 'TTY handle not initially active'); +assert.ok(isHandleActive(handle), 'TTY handle not initially active'); handle.unref(); -strictEqual(isHandleActive(handle), false, 'TTY handle active after unref()'); +assert.ok(!isHandleActive(handle), 'TTY handle active after unref()'); handle.ref(); -strictEqual(isHandleActive(handle), true, 'TTY handle inactive after ref()'); +assert.ok(isHandleActive(handle), 'TTY handle inactive after ref()'); handle.unref(); diff --git a/test/pseudo-tty/test-assert-colors.js b/test/pseudo-tty/test-assert-colors.js index 846326a9c4936a..58359744d0e806 100644 --- a/test/pseudo-tty/test-assert-colors.js +++ b/test/pseudo-tty/test-assert-colors.js @@ -1,6 +1,6 @@ 'use strict'; require('../common'); -const assert = require('assert').strict; +const assert = require('assert/strict'); function setup() { process.env.FORCE_COLOR = '1'; diff --git a/test/pseudo-tty/test-assert-no-color.js b/test/pseudo-tty/test-assert-no-color.js index d6765d6f06c598..6cbd3e40029321 100644 --- a/test/pseudo-tty/test-assert-no-color.js +++ b/test/pseudo-tty/test-assert-no-color.js @@ -1,6 +1,6 @@ 'use strict'; require('../common'); -const assert = require('assert').strict; +const assert = require('assert/strict'); process.env.NODE_DISABLE_COLORS = true; diff --git a/test/pseudo-tty/test-handle-wrap-hasref-tty.js b/test/pseudo-tty/test-handle-wrap-hasref-tty.js index 16eb7199cf9b5d..c8044234a5d82f 100644 --- a/test/pseudo-tty/test-handle-wrap-hasref-tty.js +++ b/test/pseudo-tty/test-handle-wrap-hasref-tty.js @@ -4,20 +4,16 @@ // See also test/parallel/test-handle-wrap-hasref.js const common = require('../common'); -const strictEqual = require('assert').strictEqual; +const assert = require('assert'); const ReadStream = require('tty').ReadStream; const tty = new ReadStream(0); const { internalBinding } = require('internal/test/binding'); const isTTY = internalBinding('tty_wrap').isTTY; -strictEqual(isTTY(0), true, 'tty_wrap: stdin is not a TTY'); -strictEqual(tty._handle.hasRef(), - true, 'tty_wrap: not initially refed'); +assert.ok(isTTY(0), 'tty_wrap: stdin is not a TTY'); +assert.ok(tty._handle.hasRef(), 'tty_wrap: not initially refed'); tty.unref(); -strictEqual(tty._handle.hasRef(), - false, 'tty_wrap: unref() ineffective'); +assert.ok(!tty._handle.hasRef(), 'tty_wrap: unref() ineffective'); tty.ref(); -strictEqual(tty._handle.hasRef(), - true, 'tty_wrap: ref() ineffective'); +assert.ok(tty._handle.hasRef(), 'tty_wrap: ref() ineffective'); tty._handle.close(common.mustCall(() => - strictEqual(tty._handle.hasRef(), - false, 'tty_wrap: not unrefed on close'))); + assert.ok(!tty._handle.hasRef(), 'tty_wrap: not unrefed on close'))); diff --git a/test/pseudo-tty/test-stderr-stdout-handle-sigwinch.js b/test/pseudo-tty/test-stderr-stdout-handle-sigwinch.js index d5f187d1c3948d..b14e78d669d844 100644 --- a/test/pseudo-tty/test-stderr-stdout-handle-sigwinch.js +++ b/test/pseudo-tty/test-stderr-stdout-handle-sigwinch.js @@ -4,7 +4,7 @@ const common = require('../common'); const originalRefreshSizeStderr = process.stderr._refreshSize; const originalRefreshSizeStdout = process.stdout._refreshSize; -const wrap = (fn, ioStream, string) => { +const wrap = common.mustCallAtLeast((fn, ioStream, string) => { const wrapped = common.mustCall(() => { // The console.log() call prints a string that is in the .out file. In other // words, the console.log() is part of the test, not extraneous debugging. @@ -18,7 +18,7 @@ const wrap = (fn, ioStream, string) => { } }); return wrapped; -}; +}); process.stderr._refreshSize = wrap(originalRefreshSizeStderr, process.stderr, diff --git a/test/pseudo-tty/test-tty-color-support.js b/test/pseudo-tty/test-tty-color-support.js index 6a2e2a3a88fdf1..caf3c9a39fdcaf 100644 --- a/test/pseudo-tty/test-tty-color-support.js +++ b/test/pseudo-tty/test-tty-color-support.js @@ -1,7 +1,7 @@ 'use strict'; const common = require('../common'); -const assert = require('assert').strict; +const assert = require('assert/strict'); const { WriteStream } = require('tty'); const { inspect } = require('util'); diff --git a/test/pseudo-tty/test-tty-isatty.js b/test/pseudo-tty/test-tty-isatty.js index ad81a4c6eff92b..3dfc442e76e981 100644 --- a/test/pseudo-tty/test-tty-isatty.js +++ b/test/pseudo-tty/test-tty-isatty.js @@ -1,18 +1,17 @@ 'use strict'; require('../common'); -const { strictEqual } = require('assert'); +const assert = require('assert'); const { isatty } = require('tty'); -strictEqual(isatty(0), true, 'stdin reported to not be a tty, but it is'); -strictEqual(isatty(1), true, 'stdout reported to not be a tty, but it is'); -strictEqual(isatty(2), true, 'stderr reported to not be a tty, but it is'); +assert.ok(isatty(0), 'stdin reported to not be a tty, but it is'); +assert.ok(isatty(1), 'stdout reported to not be a tty, but it is'); +assert.ok(isatty(2), 'stderr reported to not be a tty, but it is'); -strictEqual(isatty(-1), false, '-1 reported to be a tty, but it is not'); -strictEqual(isatty(55555), false, '55555 reported to be a tty, but it is not'); -strictEqual(isatty(2 ** 31), false, '2^31 reported to be a tty, but it is not'); -strictEqual(isatty(1.1), false, '1.1 reported to be a tty, but it is not'); -strictEqual(isatty('1'), false, '\'1\' reported to be a tty, but it is not'); -strictEqual(isatty({}), false, '{} reported to be a tty, but it is not'); -strictEqual(isatty(() => {}), false, - '() => {} reported to be a tty, but it is not'); +assert.ok(!isatty(-1), '-1 reported to be a tty, but it is not'); +assert.ok(!isatty(55555), '55555 reported to be a tty, but it is not'); +assert.ok(!isatty(2 ** 31), '2^31 reported to be a tty, but it is not'); +assert.ok(!isatty(1.1), '1.1 reported to be a tty, but it is not'); +assert.ok(!isatty('1'), '\'1\' reported to be a tty, but it is not'); +assert.ok(!isatty({}), '{} reported to be a tty, but it is not'); +assert.ok(!isatty(() => {}), '() => {} reported to be a tty, but it is not'); diff --git a/test/pseudo-tty/test-tty-stdout-resize.js b/test/pseudo-tty/test-tty-stdout-resize.js index c92db615020c78..542cfffca9d3dd 100644 --- a/test/pseudo-tty/test-tty-stdout-resize.js +++ b/test/pseudo-tty/test-tty-stdout-resize.js @@ -1,6 +1,6 @@ 'use strict'; const { mustCall } = require('../common'); -const { notStrictEqual } = require('assert'); +const assert = require('assert'); // tty.WriteStream#_refreshSize() only emits the 'resize' event when the // window dimensions change. We cannot influence that from the script @@ -8,4 +8,4 @@ const { notStrictEqual } = require('assert'); process.stdout.columns = 9001; process.stdout.on('resize', mustCall()); process.kill(process.pid, 'SIGWINCH'); -setImmediate(mustCall(() => notStrictEqual(process.stdout.columns, 9001))); +setImmediate(mustCall(() => assert.notStrictEqual(process.stdout.columns, 9001))); diff --git a/test/pummel/test-child-process-spawn-loop.js b/test/pummel/test-child-process-spawn-loop.js index 7577dd72447193..761d21bc08c20e 100644 --- a/test/pummel/test-child-process-spawn-loop.js +++ b/test/pummel/test-child-process-spawn-loop.js @@ -30,7 +30,7 @@ const SIZE = 1000 * 1024; const N = 40; let finished = false; -function doSpawn(i) { +const doSpawn = common.mustCall((i) => { const child = spawn(python, ['-c', `print(${SIZE} * "C")`]); let count = 0; @@ -43,7 +43,7 @@ function doSpawn(i) { console.log(`stderr: ${chunk}`); }); - child.on('close', () => { + child.on('close', common.mustCall(() => { // + 1 for \n or + 2 for \r\n on Windows assert.strictEqual(count, SIZE + (common.isWindows ? 2 : 1)); if (i < N) { @@ -51,8 +51,8 @@ function doSpawn(i) { } else { finished = true; } - }); -} + })); +}, N); doSpawn(0); diff --git a/test/pummel/test-fs-readfile-tostring-fail.js b/test/pummel/test-fs-readfile-tostring-fail.js index 5863de251f0945..c1e14a41fd6da4 100644 --- a/test/pummel/test-fs-readfile-tostring-fail.js +++ b/test/pummel/test-fs-readfile-tostring-fail.js @@ -32,7 +32,7 @@ const a = Buffer.alloc(size, 'a'); let expectedSize = 0; for (let i = 0; i < 201; i++) { - stream.write(a, (err) => { assert.ifError(err); }); + stream.write(a, common.mustSucceed()); expectedSize += a.length; } diff --git a/test/pummel/test-fs-watch-file-slow.js b/test/pummel/test-fs-watch-file-slow.js index c6d148df05db47..99a31332fea2aa 100644 --- a/test/pummel/test-fs-watch-file-slow.js +++ b/test/pummel/test-fs-watch-file-slow.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const fs = require('fs'); @@ -38,7 +38,7 @@ try { // swallow } -fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, function(curr, prev) { +fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, common.mustCall((curr, prev) => { console.log([curr, prev]); switch (++nevents) { case 1: @@ -55,7 +55,7 @@ fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, function(curr, prev) { default: assert(0); } -}); +})); process.on('exit', function() { assert.strictEqual(nevents, 4); diff --git a/test/pummel/test-fs-watch-non-recursive.js b/test/pummel/test-fs-watch-non-recursive.js index 218f485d83c0bf..5570caf688bb7b 100644 --- a/test/pummel/test-fs-watch-non-recursive.js +++ b/test/pummel/test-fs-watch-non-recursive.js @@ -38,19 +38,19 @@ const filepath = path.join(testsubdir, 'watch.txt'); fs.mkdirSync(testsubdir, 0o700); -function doWatch() { - const watcher = fs.watch(testDir, { persistent: true }, (event, filename) => { +const doWatch = common.mustCall(() => { + const watcher = fs.watch(testDir, { persistent: true }, common.mustCall((event, filename) => { // This function may be called with the directory depending on timing but // must not be called with the file.. assert.strictEqual(filename, 'testsubdir'); - }); + })); setTimeout(() => { fs.writeFileSync(filepath, 'test'); }, 100); setTimeout(() => { watcher.close(); }, 500); -} +}); if (common.isMacOS) { // On macOS delay watcher start to avoid leaking previous events. diff --git a/test/pummel/test-heapdump-http2.js b/test/pummel/test-heapdump-http2.js index 74e9fdd5ff9264..6709a0209403aa 100644 --- a/test/pummel/test-heapdump-http2.js +++ b/test/pummel/test-heapdump-http2.js @@ -22,7 +22,7 @@ const server = http2.createServer(); server.on('stream', (stream) => { stream.respondWithFile(process.execPath); }); -server.listen(0, () => { +server.listen(0, common.mustCall(() => { const client = http2.connect(`http://localhost:${server.address().port}`); const req = client.request(); @@ -73,4 +73,4 @@ server.listen(0, () => { server.close(); })); req.end(); -}); +})); diff --git a/test/pummel/test-http-many-keep-alive-connections.js b/test/pummel/test-http-many-keep-alive-connections.js index a5b7f34502169d..6591b66155b31a 100644 --- a/test/pummel/test-http-many-keep-alive-connections.js +++ b/test/pummel/test-http-many-keep-alive-connections.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const http = require('http'); @@ -29,12 +29,12 @@ let responses = 0; let requests = 0; let connection; -const server = http.Server(function(req, res) { +const server = http.Server(common.mustCall((req, res) => { requests++; assert.strictEqual(req.connection, connection); res.writeHead(200); res.end('hello world\n'); -}); +})); server.once('connection', function(c) { connection = c; diff --git a/test/pummel/test-net-many-clients.js b/test/pummel/test-net-many-clients.js index 4d114922a92ad8..6e795c04c8b236 100644 --- a/test/pummel/test-net-many-clients.js +++ b/test/pummel/test-net-many-clients.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const net = require('net'); @@ -62,13 +62,7 @@ function runClient(port, callback) { client.end(); }); - client.on('error', function(e) { - console.log('\n\nERROOOOOr'); - throw e; - }); - - client.on('close', function(had_error) { - console.log('.'); + client.on('close', common.mustCall((had_error) => { assert.strictEqual(had_error, false); assert.strictEqual(client.recved.length, bytes); @@ -82,7 +76,7 @@ function runClient(port, callback) { } else { callback(); } - }); + })); } server.listen(0, function() { diff --git a/test/pummel/test-net-pause.js b/test/pummel/test-net-pause.js index 76237c17214d23..b7cbac312c1718 100644 --- a/test/pummel/test-net-pause.js +++ b/test/pummel/test-net-pause.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const net = require('net'); @@ -42,7 +42,7 @@ const server = net.createServer((connection) => { write(0); }); -server.on('listening', () => { +server.on('listening', common.mustCall(() => { const client = net.createConnection(server.address().port); client.setEncoding('ascii'); client.on('data', (d) => { @@ -50,39 +50,39 @@ server.on('listening', () => { recv += d; }); - setTimeout(() => { + setTimeout(common.mustCall(() => { chars_recved = recv.length; console.log(`pause at: ${chars_recved}`); assert.strictEqual(chars_recved > 1, true); client.pause(); - setTimeout(() => { + setTimeout(common.mustCall(() => { console.log(`resume at: ${chars_recved}`); assert.strictEqual(chars_recved, recv.length); client.resume(); - setTimeout(() => { + setTimeout(common.mustCall(() => { chars_recved = recv.length; console.log(`pause at: ${chars_recved}`); client.pause(); - setTimeout(() => { + setTimeout(common.mustCall(() => { console.log(`resume at: ${chars_recved}`); assert.strictEqual(chars_recved, recv.length); client.resume(); - }, 500); + }), 500); - }, 500); + }), 500); - }, 500); + }), 500); - }, 500); + }), 500); client.on('end', () => { server.close(); client.end(); }); -}); +})); server.listen(0); process.on('exit', () => { diff --git a/test/pummel/test-net-pingpong-delay.js b/test/pummel/test-net-pingpong-delay.js index bddcd4de72d664..c6da3a4dc17de9 100644 --- a/test/pummel/test-net-pingpong-delay.js +++ b/test/pummel/test-net-pingpong-delay.js @@ -24,61 +24,58 @@ const common = require('../common'); const assert = require('assert'); const net = require('net'); -function pingPongTest(host, on_complete) { +{ const N = 100; const DELAY = 1; let count = 0; let client_ended = false; - const server = net.createServer({ allowHalfOpen: true }, function(socket) { + const server = net.createServer({ allowHalfOpen: true }, common.mustCall((socket) => { socket.setEncoding('utf8'); - socket.on('data', function(data) { + socket.on('data', common.mustCallAtLeast((data) => { console.log(data); assert.strictEqual(data, 'PING'); assert.strictEqual(socket.readyState, 'open'); assert.strictEqual(count <= N, true); - setTimeout(function() { + setTimeout(common.mustCall(() => { assert.strictEqual(socket.readyState, 'open'); socket.write('PONG'); - }, DELAY); - }); + }), DELAY); + })); - socket.on('timeout', function() { - console.error('server-side timeout!!'); - assert.strictEqual(false, true); - }); + socket.on('timeout', common.mustNotCall('server-side timeout!!')); - socket.on('end', function() { + socket.on('end', common.mustCall(() => { console.log('server-side socket EOF'); assert.strictEqual(socket.readyState, 'writeOnly'); socket.end(); - }); + })); - socket.on('close', function(had_error) { + socket.on('close', common.mustCall((had_error) => { console.log('server-side socket.end'); assert.strictEqual(had_error, false); assert.strictEqual(socket.readyState, 'closed'); socket.server.close(); - }); - }); + })); + })); - server.listen(0, host, common.mustCall(function() { - const client = net.createConnection(server.address().port, host); + server.listen(0, undefined, common.mustCall(() => { + const client = net.createConnection(server.address().port, undefined); client.setEncoding('utf8'); - client.on('connect', function() { + client.on('connect', common.mustCall(() => { assert.strictEqual(client.readyState, 'open'); client.write('PING'); - }); + })); - client.on('data', function(data) { + client.on('data', common.mustCallAtLeast((data) => { console.log(data); assert.strictEqual(data, 'PONG'); assert.strictEqual(client.readyState, 'open'); - setTimeout(function() { + setTimeout(common.mustCall(() => { assert.strictEqual(client.readyState, 'open'); if (count++ < N) { client.write('PING'); @@ -87,21 +84,15 @@ function pingPongTest(host, on_complete) { client.end(); client_ended = true; } - }, DELAY); - }); + }), DELAY); + })); - client.on('timeout', function() { - console.error('client-side timeout!!'); - assert.strictEqual(false, true); - }); + client.on('timeout', common.mustNotCall('client-side timeout!!')); client.on('close', common.mustCall(function() { console.log('client.end'); assert.strictEqual(count, N + 1); assert.ok(client_ended); - if (on_complete) on_complete(); })); })); } - -pingPongTest(); diff --git a/test/pummel/test-net-pingpong.js b/test/pummel/test-net-pingpong.js index 7db902ddbed20a..08d049ea5e4f37 100644 --- a/test/pummel/test-net-pingpong.js +++ b/test/pummel/test-net-pingpong.js @@ -24,14 +24,12 @@ const common = require('../common'); const assert = require('assert'); const net = require('net'); -let tests_run = 0; - -function pingPongTest(host, on_complete) { +const pingPongTest = common.mustCall((host, on_complete) => { const N = 1000; let count = 0; let sent_final_ping = false; - const server = net.createServer({ allowHalfOpen: true }, function(socket) { + const server = net.createServer({ allowHalfOpen: true }, common.mustCall((socket) => { assert.strictEqual(socket.remoteAddress !== null, true); assert.strictEqual(socket.remoteAddress !== undefined, true); const address = socket.remoteAddress; @@ -49,38 +47,38 @@ function pingPongTest(host, on_complete) { socket.setNoDelay(); socket.timeout = 0; - socket.on('data', function(data) { + socket.on('data', common.mustCall((data) => { console.log(`server got: ${JSON.stringify(data)}`); assert.strictEqual(socket.readyState, 'open'); assert.strictEqual(count <= N, true); if (/PING/.test(data)) { socket.write('PONG'); } - }); + })); - socket.on('end', function() { + socket.on('end', common.mustCall(() => { assert.strictEqual(socket.readyState, 'writeOnly'); socket.end(); - }); + })); - socket.on('close', function(had_error) { + socket.on('close', common.mustCall((had_error) => { assert.strictEqual(had_error, false); assert.strictEqual(socket.readyState, 'closed'); socket.server.close(); - }); - }); + })); + })); - server.listen(0, host, function() { + server.listen(0, host, common.mustCall(() => { const client = net.createConnection(server.address().port, host); client.setEncoding('utf8'); - client.on('connect', function() { + client.on('connect', common.mustCall(() => { assert.strictEqual(client.readyState, 'open'); client.write('PING'); - }); + })); - client.on('data', function(data) { + client.on('data', common.mustCallAtLeast((data) => { console.log(`client got: ${data}`); assert.strictEqual(data, 'PONG'); @@ -99,22 +97,17 @@ function pingPongTest(host, on_complete) { client.write('PING'); client.end(); } - }); + })); - client.on('close', function() { + client.on('close', common.mustCall(() => { assert.strictEqual(count, N + 1); assert.strictEqual(sent_final_ping, true); if (on_complete) on_complete(); - tests_run += 1; - }); - }); -} + })); + })); +}, common.hasIPv6 ? 3 : 2); // All are run at once and will run on different ports. pingPongTest(null); pingPongTest('127.0.0.1'); if (common.hasIPv6) pingPongTest('::1'); - -process.on('exit', function() { - assert.strictEqual(tests_run, common.hasIPv6 ? 3 : 2); -}); diff --git a/test/pummel/test-net-timeout.js b/test/pummel/test-net-timeout.js index f110254a50499d..99b0b65589ce62 100644 --- a/test/pummel/test-net-timeout.js +++ b/test/pummel/test-net-timeout.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const net = require('net'); @@ -53,7 +53,7 @@ const echo_server = net.createServer((socket) => { }); }); -echo_server.listen(0, () => { +echo_server.listen(0, common.mustCall(() => { const port = echo_server.address().port; console.log(`server listening at ${port}`); @@ -65,7 +65,7 @@ echo_server.listen(0, () => { client.write('hello\r\n'); }); - client.on('data', (chunk) => { + client.on('data', common.mustCallAtLeast((chunk) => { assert.strictEqual(chunk, 'hello\r\n'); if (exchanges++ < 5) { setTimeout(() => { @@ -79,7 +79,7 @@ echo_server.listen(0, () => { console.dir(starttime); } } - }); + })); client.on('timeout', () => { throw new Error("client timeout - this shouldn't happen"); @@ -94,7 +94,7 @@ echo_server.listen(0, () => { console.log('client disconnect'); echo_server.close(); }); -}); +})); process.on('exit', () => { assert.ok(starttime != null); diff --git a/test/pummel/test-regress-GH-892.js b/test/pummel/test-regress-GH-892.js index 23a56a68546302..bac012b348cd9e 100644 --- a/test/pummel/test-regress-GH-892.js +++ b/test/pummel/test-regress-GH-892.js @@ -57,10 +57,10 @@ function makeRequest() { const child = spawn(process.execPath, args); - child.on('exit', function(code) { + child.on('exit', common.mustCall((code) => { assert.match(stderrBuffer, /DONE/); assert.strictEqual(code, 0); - }); + })); // The following two lines forward the stdio from the child // to parent process for debugging. @@ -83,7 +83,7 @@ const serverOptions = { let uploadCount = 0; -const server = https.Server(serverOptions, function(req, res) { +const server = https.Server(serverOptions, common.mustCall((req, res) => { // Close the server immediately. This test is only doing a single upload. // We need to make sure the server isn't keeping the event loop alive // while the upload is in progress. @@ -94,12 +94,12 @@ const server = https.Server(serverOptions, function(req, res) { uploadCount += d.length; }); - req.on('end', function() { + req.on('end', common.mustCall(() => { assert.strictEqual(uploadCount, bytesExpected); res.writeHead(200, { 'content-type': 'text/plain' }); res.end('successful upload\n'); - }); -}); + })); +})); server.listen(0, function() { console.log(`expecting ${bytesExpected} bytes`); diff --git a/test/pummel/test-timers.js b/test/pummel/test-timers.js index 74d9f4eb41e684..b51c395d53dea7 100644 --- a/test/pummel/test-timers.js +++ b/test/pummel/test-timers.js @@ -72,41 +72,41 @@ const WINDOW = 200; // Why does this need to be so big? // Single param: { - setTimeout(function(param) { + setTimeout(common.mustCall(function(param) { assert.strictEqual(param, 'test param'); - }, 1000, 'test param'); + }), 1000, 'test param'); } { let interval_count = 0; - setInterval(function(param) { + setInterval(common.mustCall(function(param) { ++interval_count; assert.strictEqual(param, 'test param'); if (interval_count === 3) clearInterval(this); - }, 1000, 'test param'); + }, 3), 1000, 'test param'); } // Multiple param { - setTimeout(function(param1, param2) { + setTimeout(common.mustCall(function(param1, param2) { assert.strictEqual(param1, 'param1'); assert.strictEqual(param2, 'param2'); - }, 1000, 'param1', 'param2'); + }), 1000, 'param1', 'param2'); } { let interval_count = 0; - setInterval(function(param1, param2) { + setInterval(common.mustCall(function(param1, param2) { ++interval_count; assert.strictEqual(param1, 'param1'); assert.strictEqual(param2, 'param2'); if (interval_count === 3) clearInterval(this); - }, 1000, 'param1', 'param2'); + }, 3), 1000, 'param1', 'param2'); } // setInterval(cb, 0) should be called multiple times. diff --git a/test/pummel/test-tls-server-large-request.js b/test/pummel/test-tls-server-large-request.js index 158017ef2f02b9..77098a3091c0d1 100644 --- a/test/pummel/test-tls-server-large-request.js +++ b/test/pummel/test-tls-server-large-request.js @@ -37,12 +37,9 @@ const options = { }; class Mediator extends stream.Writable { - constructor() { - super(); - this.buf = ''; - } + buf = ''; - _write(data, enc, cb) { + _write = common.mustCall((data, enc, cb) => { this.buf += data; setTimeout(cb, 0); @@ -50,7 +47,7 @@ class Mediator extends stream.Writable { assert.strictEqual(this.buf, request.toString()); server.close(); } - } + }); } const mediator = new Mediator(); diff --git a/test/pummel/test-vm-memleak.js b/test/pummel/test-vm-memleak.js index 179dc3e5705c2e..9a8be1e38dc391 100644 --- a/test/pummel/test-vm-memleak.js +++ b/test/pummel/test-vm-memleak.js @@ -35,7 +35,7 @@ const baselineRss = process.memoryUsage.rss(); const start = Date.now(); -const interval = setInterval(function() { +const interval = setInterval(common.mustCallAtLeast(function() { try { vm.runInNewContext('throw 1;'); } catch { @@ -53,7 +53,7 @@ const interval = setInterval(function() { testContextLeak(); } -}, 1); +}), 1); function testContextLeak() { // TODO: This needs a comment explaining what it's doing. Will it crash the diff --git a/test/wasi/test-wasi-stdio.js b/test/wasi/test-wasi-stdio.js index d4c65f238df890..5089d7599c8b25 100644 --- a/test/wasi/test-wasi-stdio.js +++ b/test/wasi/test-wasi-stdio.js @@ -1,7 +1,7 @@ 'use strict'; const common = require('../common'); const tmpdir = require('../common/tmpdir'); -const { strictEqual } = require('assert'); +const assert = require('assert'); const { closeSync, openSync, readFileSync, writeFileSync } = require('fs'); const { join } = require('path'); const { WASI } = require('wasi'); @@ -24,10 +24,10 @@ const importObject = { wasi_snapshot_preview1: wasi.wasiImport }; (async () => { const { instance } = await WebAssembly.instantiate(buffer, importObject); - strictEqual(wasi.start(instance), 0); + assert.strictEqual(wasi.start(instance), 0); closeSync(stdin); closeSync(stdout); closeSync(stderr); - strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31)); - strictEqual(readFileSync(stderrFile, 'utf8').trim(), ''); + assert.strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31)); + assert.strictEqual(readFileSync(stderrFile, 'utf8').trim(), ''); })().then(common.mustCall()); diff --git a/test/wasi/test-wasi-worker-terminate.js b/test/wasi/test-wasi-worker-terminate.js index 52985703001aa6..62e15253868296 100644 --- a/test/wasi/test-wasi-worker-terminate.js +++ b/test/wasi/test-wasi-worker-terminate.js @@ -25,10 +25,10 @@ const bytecode = new Uint8Array([ if (!process.env.HAS_STARTED_WORKER) { process.env.HAS_STARTED_WORKER = 1; const worker = new Worker(__filename); - worker.once('message', (message) => { + worker.once('message', common.mustCall((message) => { assert.strictEqual(message, 'start'); setTimeout(() => worker.terminate(), common.platformTimeout(50)); - }); + })); } else { go(); } diff --git a/tools/eslint-rules/must-call-assert.js b/tools/eslint-rules/must-call-assert.js new file mode 100644 index 00000000000000..20b8dc49cb542d --- /dev/null +++ b/tools/eslint-rules/must-call-assert.js @@ -0,0 +1,100 @@ +'use strict'; + +const message = + 'Assertions must be wrapped into `common.mustCall` or `common.mustCallAtLeast`'; + + +const requireCall = 'CallExpression[callee.name="require"]'; +const assertModuleSpecifier = '/^(node:)?assert(.strict)?$/'; + +function findEnclosingFunction(node) { + while (true) { + node = node.parent; + if (!node) break; + + if (node.type !== 'ArrowFunctionExpression' && node.type !== 'FunctionExpression') continue; + + if (node.parent?.type === 'CallExpression') { + if (node.parent.callee === node) continue; // IIFE + + if ( + node.parent.callee.type === 'MemberExpression' && + (node.parent.callee.object.type === 'ArrayExpression' || node.parent.callee.object.type === 'Identifier') && + node.parent.callee.property.name === 'forEach' + ) continue; // `[].forEach()` call + } else if (node.parent?.type === 'NewExpression') { + if (node.parent.callee.type === 'Identifier' && node.parent.callee.name === 'Promise') continue; + } + break; + } + return node; +} + +function isMustCallOrMustCallAtLeast(str) { + return str === 'mustCall' || str === 'mustCallAtLeast'; +} + +function isMustCallOrTest(str) { + return str === 'test' || str === 'it' || isMustCallOrMustCallAtLeast(str); +} + +module.exports = { + meta: { + fixable: 'code', + }, + create: function(context) { + return { + [`:function CallExpression:matches(${[ + '[callee.type="Identifier"][callee.value=/^mustCall(AtLeast)?$/]', + '[callee.object.name="assert"][callee.property.name!="fail"]', + '[callee.object.name="common"][callee.property.name=/^mustCall(AtLeast)?$/]', + ].join(',')})`]: (node) => { + const enclosingFn = findEnclosingFunction(node); + const parent = enclosingFn?.parent; + if (!parent) return; // Top-level + if (parent.type === 'CallExpression') { + switch (parent.callee.type) { + case 'MemberExpression': + if ( + parent.callee.property.name === 'then' || + { + 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', + }[parent.callee.object.name]?.(parent.callee.property.name) + ) { + return; + } + break; + + case 'Identifier': + if (isMustCallOrTest(parent.callee.name)) return; + break; + } + } + context.report({ + node, + message, + }); + }, + + [[ + `ImportDeclaration[source.value=${assertModuleSpecifier}]:not(${[ + 'length=1', + '0.type=/^Import(Default|Namespace)Specifier$/', + '0.local.name="assert"', + ].map((selector) => `[specifiers.${selector}]`).join('')})`, + `:not(VariableDeclarator[id.name="assert"])>${requireCall}[arguments.0.value=${assertModuleSpecifier}]`, + ].join(',')]: (node) => { + context.report({ + node, + message: 'Only assign `node:assert` to `assert`', + }); + }, + }; + }, +}; From bd14a06202b3f688af30577957a7cfd1bf60d8e8 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 5 Oct 2025 16:12:04 +0200 Subject: [PATCH 2/3] fixup! tools: add lint rule to ensure assertions are reached --- test/node-api/test_threadsafe_function/test.js | 4 ++-- test/pummel/test-child-process-spawn-loop.js | 2 +- test/pummel/test-fs-watch-file-slow.js | 2 +- test/pummel/test-fs-watch-non-recursive.js | 4 ++-- test/pummel/test-http-many-keep-alive-connections.js | 2 +- test/pummel/test-net-many-clients.js | 2 +- test/pummel/test-tls-server-large-request.js | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/node-api/test_threadsafe_function/test.js b/test/node-api/test_threadsafe_function/test.js index cdd637805a5e9a..69d37ca221e9f9 100644 --- a/test/node-api/test_threadsafe_function/test.js +++ b/test/node-api/test_threadsafe_function/test.js @@ -79,7 +79,7 @@ function testUnref(queueSize) { new Promise(function testWithoutJSMarshaller(resolve) { let callCount = 0; - binding.StartThreadNoNative(function testCallback() { + binding.StartThreadNoNative(common.mustCallAtLeast(function testCallback() { callCount++; // The default call-into-JS implementation passes no arguments. @@ -91,7 +91,7 @@ new Promise(function testWithoutJSMarshaller(resolve) { }, false); }); } - }, false /* abort */, false /* launchSecondary */, binding.MAX_QUEUE_SIZE); + }), false /* abort */, false /* launchSecondary */, binding.MAX_QUEUE_SIZE); }) // Start the thread in blocking mode, and assert that all values are passed. diff --git a/test/pummel/test-child-process-spawn-loop.js b/test/pummel/test-child-process-spawn-loop.js index 761d21bc08c20e..99da7be9203efe 100644 --- a/test/pummel/test-child-process-spawn-loop.js +++ b/test/pummel/test-child-process-spawn-loop.js @@ -52,7 +52,7 @@ const doSpawn = common.mustCall((i) => { finished = true; } })); -}, N); +}, N + 1); doSpawn(0); diff --git a/test/pummel/test-fs-watch-file-slow.js b/test/pummel/test-fs-watch-file-slow.js index 99a31332fea2aa..2b57d994cfd338 100644 --- a/test/pummel/test-fs-watch-file-slow.js +++ b/test/pummel/test-fs-watch-file-slow.js @@ -55,7 +55,7 @@ fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, common.mustCall((curr, prev) default: assert(0); } -})); +}, 4)); process.on('exit', function() { assert.strictEqual(nevents, 4); diff --git a/test/pummel/test-fs-watch-non-recursive.js b/test/pummel/test-fs-watch-non-recursive.js index 5570caf688bb7b..2803c65c8c41ae 100644 --- a/test/pummel/test-fs-watch-non-recursive.js +++ b/test/pummel/test-fs-watch-non-recursive.js @@ -39,11 +39,11 @@ const filepath = path.join(testsubdir, 'watch.txt'); fs.mkdirSync(testsubdir, 0o700); const doWatch = common.mustCall(() => { - const watcher = fs.watch(testDir, { persistent: true }, common.mustCall((event, filename) => { + const watcher = fs.watch(testDir, { persistent: true }, common.mustCallAtLeast((event, filename) => { // This function may be called with the directory depending on timing but // must not be called with the file.. assert.strictEqual(filename, 'testsubdir'); - })); + }, 0)); setTimeout(() => { fs.writeFileSync(filepath, 'test'); }, 100); diff --git a/test/pummel/test-http-many-keep-alive-connections.js b/test/pummel/test-http-many-keep-alive-connections.js index 6591b66155b31a..efa7ae4657a5d1 100644 --- a/test/pummel/test-http-many-keep-alive-connections.js +++ b/test/pummel/test-http-many-keep-alive-connections.js @@ -34,7 +34,7 @@ const server = http.Server(common.mustCall((req, res) => { assert.strictEqual(req.connection, connection); res.writeHead(200); res.end('hello world\n'); -})); +}, expected)); server.once('connection', function(c) { connection = c; diff --git a/test/pummel/test-net-many-clients.js b/test/pummel/test-net-many-clients.js index 6e795c04c8b236..16095560076f95 100644 --- a/test/pummel/test-net-many-clients.js +++ b/test/pummel/test-net-many-clients.js @@ -62,7 +62,7 @@ function runClient(port, callback) { client.end(); }); - client.on('close', common.mustCall((had_error) => { + client.on('close', common.mustCall(function(had_error) { assert.strictEqual(had_error, false); assert.strictEqual(client.recved.length, bytes); diff --git a/test/pummel/test-tls-server-large-request.js b/test/pummel/test-tls-server-large-request.js index 77098a3091c0d1..378b5b0629a847 100644 --- a/test/pummel/test-tls-server-large-request.js +++ b/test/pummel/test-tls-server-large-request.js @@ -39,7 +39,7 @@ const options = { class Mediator extends stream.Writable { buf = ''; - _write = common.mustCall((data, enc, cb) => { + _write = common.mustCallAtLeast((data, enc, cb) => { this.buf += data; setTimeout(cb, 0); From 16ea1317d124756515a8511c0095cf9ac2f78706 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 5 Oct 2025 18:41:35 +0200 Subject: [PATCH 3/3] fixup! tools: add lint rule to ensure assertions are reached --- test/pummel/test-net-many-clients.js | 2 +- test/pummel/test-net-pingpong.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/pummel/test-net-many-clients.js b/test/pummel/test-net-many-clients.js index 16095560076f95..d64458386c66f4 100644 --- a/test/pummel/test-net-many-clients.js +++ b/test/pummel/test-net-many-clients.js @@ -76,7 +76,7 @@ function runClient(port, callback) { } else { callback(); } - })); + }, connections_per_client)); } server.listen(0, function() { diff --git a/test/pummel/test-net-pingpong.js b/test/pummel/test-net-pingpong.js index 08d049ea5e4f37..dde17b23d1825b 100644 --- a/test/pummel/test-net-pingpong.js +++ b/test/pummel/test-net-pingpong.js @@ -47,14 +47,14 @@ const pingPongTest = common.mustCall((host, on_complete) => { socket.setNoDelay(); socket.timeout = 0; - socket.on('data', common.mustCall((data) => { + socket.on('data', common.mustCallAtLeast((data) => { console.log(`server got: ${JSON.stringify(data)}`); assert.strictEqual(socket.readyState, 'open'); assert.strictEqual(count <= N, true); if (/PING/.test(data)) { socket.write('PONG'); } - })); + }, N + 1)); socket.on('end', common.mustCall(() => { assert.strictEqual(socket.readyState, 'writeOnly');