diff --git a/test/doctool/test-apilinks.mjs b/test/doctool/test-apilinks.mjs index 70b7b4ef8e..ebe1f60a0d 100644 --- a/test/doctool/test-apilinks.mjs +++ b/test/doctool/test-apilinks.mjs @@ -1,4 +1,4 @@ -import '../common/index.mjs'; +import { mustCallAtLeast } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import tmpdir from '../common/tmpdir.js'; @@ -14,7 +14,7 @@ const apilinks = fixtures.path('apilinks'); tmpdir.refresh(); -fs.readdirSync(apilinks).forEach((fixture) => { +fs.readdirSync(apilinks).forEach(mustCallAtLeast((fixture) => { if (!fixture.endsWith('.js')) return; const input = path.join(apilinks, fixture); @@ -40,4 +40,4 @@ fs.readdirSync(apilinks).forEach((fixture) => { Object.keys(actualLinks).length, 0, `unexpected links returned ${JSON.stringify(actualLinks)}`, ); -}); +})); diff --git a/test/eslint.config_partial.mjs b/test/eslint.config_partial.mjs index 0980ffac7a..f523d6f86f 100644 --- a/test/eslint.config_partial.mjs +++ b/test/eslint.config_partial.mjs @@ -163,13 +163,31 @@ export default [ 'benchmark', 'cctest', 'client-proxy', + 'doctool', + 'embedding', + 'fixtures', + 'fuzzers', + 'js-native-api', + 'known_issues', 'message', 'module-hooks', 'node-api', - 'pummel', + 'nop', + 'overlapped-checker', 'pseudo-tty', + 'pummel', + 'report', + 'sea', + 'sqlite', + 'system-ca', + 'test426', + 'testpy', + 'tick-processor', + 'tools', 'v8-updates', 'wasi', + 'wasm-allocation', + 'wpt', ].join(',')}}/**/*.{js,mjs,cjs}`, ], rules: { diff --git a/test/fixtures/test-runner/output/filtered-suite-delayed-build.js b/test/fixtures/test-runner/output/filtered-suite-delayed-build.js index c6b7060c2b..8e5f8e5acd 100644 --- a/test/fixtures/test-runner/output/filtered-suite-delayed-build.js +++ b/test/fixtures/test-runner/output/filtered-suite-delayed-build.js @@ -3,14 +3,14 @@ const common = require('../../../common'); const { suite, test } = require('node:test'); -suite('async suite', async () => { +suite('async suite', common.mustCall(async () => { await 1; test('enabled 1', common.mustCall()); await 1; test('not run', common.mustNotCall()); await 1; -}); +})); -suite('sync suite', () => { +suite('sync suite', common.mustCall(() => { test('enabled 2', common.mustCall()); -}); +})); diff --git a/test/fixtures/test-runner/output/hooks-with-no-global-test.js b/test/fixtures/test-runner/output/hooks-with-no-global-test.js index 02e5d94ce1..abe65ee044 100644 --- a/test/fixtures/test-runner/output/hooks-with-no-global-test.js +++ b/test/fixtures/test-runner/output/hooks-with-no-global-test.js @@ -9,6 +9,7 @@ before(() => testArr.push('global before')); after(() => { testArr.push('global after'); + // eslint-disable-next-line node-core/must-call-assert assert.deepStrictEqual(testArr, [ 'global before', 'describe before', diff --git a/test/fixtures/test-runner/output/hooks.js b/test/fixtures/test-runner/output/hooks.js index 77143a3fa3..23a8d2a7c4 100644 --- a/test/fixtures/test-runner/output/hooks.js +++ b/test/fixtures/test-runner/output/hooks.js @@ -7,7 +7,7 @@ const { setTimeout } = require('node:timers/promises'); before((t) => t.diagnostic('before 1 called')); after((t) => t.diagnostic('after 1 called')); -describe('describe hooks', () => { +describe('describe hooks', common.mustCall(() => { const testArr = []; before(function() { testArr.push('before ' + this.name); @@ -51,9 +51,9 @@ describe('describe hooks', () => { it('nested 1', () => testArr.push('nested 1')); test('nested 2', () => testArr.push('nested 2')); }); -}); +})); -describe('describe hooks - no subtests', () => { +describe('describe hooks - no subtests', common.mustCall(() => { const testArr = []; before(function() { testArr.push('before ' + this.name); @@ -67,7 +67,7 @@ describe('describe hooks - no subtests', () => { })); beforeEach(common.mustNotCall()); afterEach(common.mustNotCall()); -}); +})); describe('before throws', () => { before(() => { throw new Error('before'); }); @@ -75,10 +75,10 @@ describe('before throws', () => { test('2', () => {}); }); -describe('before throws - no subtests', () => { +describe('before throws - no subtests', common.mustCall(() => { before(() => { throw new Error('before'); }); after(common.mustCall()); -}); +})); describe('after throws', () => { after(() => { throw new Error('after'); }); @@ -102,11 +102,11 @@ describe('afterEach throws', () => { test('2', () => {}); }); -describe('afterEach when test fails', () => { +describe('afterEach when test fails', common.mustCall(() => { afterEach(common.mustCall(2)); it('1', () => { throw new Error('test'); }); test('2', () => {}); -}); +})); describe('afterEach throws and test fails', () => { afterEach(() => { throw new Error('afterEach'); }); @@ -246,13 +246,13 @@ test('t.after() is called if test body throws', (t) => { throw new Error('bye'); }); -describe('run after when before throws', () => { +describe('run after when before throws', common.mustCall(() => { after(common.mustCall(() => { console.log('- after() called'); })); before(() => { throw new Error('before'); }); it('1', () => {}); -}); +})); test('test hooks - async', async (t) => { diff --git a/test/fixtures/test-runner/output/name_pattern.js b/test/fixtures/test-runner/output/name_pattern.js index c9c4e062dc..70834aced1 100644 --- a/test/fixtures/test-runner/output/name_pattern.js +++ b/test/fixtures/test-runner/output/name_pattern.js @@ -32,7 +32,7 @@ test('top level test enabled', common.mustCall(async (t) => { ); })); -describe('top level describe enabled', () => { +describe('top level describe enabled', common.mustCall(() => { before(common.mustCall()); beforeEach(common.mustCall(3)); afterEach(common.mustCall(3)); @@ -44,7 +44,7 @@ describe('top level describe enabled', () => { describe('nested describe enabled', common.mustCall(() => { it('is enabled', common.mustCall()); })); -}); +})); describe('yes', function() { it('no', () => {}); @@ -76,13 +76,13 @@ describe('no with todo', { todo: true }, () => { }); }); -describe('DescribeForMatchWithAncestors', () => { +describe('DescribeForMatchWithAncestors', common.mustCall(() => { it('NestedTest', () => common.mustNotCall()); - describe('NestedDescribeForMatchWithAncestors', () => { + describe('NestedDescribeForMatchWithAncestors', common.mustCall(() => { it('NestedTest', common.mustCall()); - }); -}); + })); +})); describe('DescribeForMatchWithAncestors', () => { it('NestedTest', () => common.mustNotCall()); diff --git a/test/js-native-api/3_callbacks/test.js b/test/js-native-api/3_callbacks/test.js index b84d3fbc9d..9c61e18c98 100644 --- a/test/js-native-api/3_callbacks/test.js +++ b/test/js-native-api/3_callbacks/test.js @@ -3,14 +3,14 @@ const common = require('../../common'); const assert = require('assert'); const addon = require(`./build/${common.buildType}/3_callbacks`); -addon.RunCallback(function(msg) { +addon.RunCallback(common.mustCall((msg) => { assert.strictEqual(msg, 'hello world'); -}); +})); function testRecv(desiredRecv) { - addon.RunCallbackWithRecv(function() { + addon.RunCallbackWithRecv(common.mustCall(function() { assert.strictEqual(this, desiredRecv); - }, desiredRecv); + }), desiredRecv); } testRecv(undefined); diff --git a/test/js-native-api/test_promise/test.js b/test/js-native-api/test_promise/test.js index feaa4ebfef..c53dbb5459 100644 --- a/test/js-native-api/test_promise/test.js +++ b/test/js-native-api/test_promise/test.js @@ -50,9 +50,9 @@ test_promise.concludeCurrentPromise(undefined, true); const rejectPromise = Promise.reject(-1); const expected_reason = -1; assert.strictEqual(test_promise.isPromise(rejectPromise), true); -rejectPromise.catch((reason) => { +rejectPromise.catch(common.mustCall((reason) => { assert.strictEqual(reason, expected_reason); -}); +})); assert.strictEqual(test_promise.isPromise(2.4), false); assert.strictEqual(test_promise.isPromise('I promise!'), false); diff --git a/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js b/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js index 9d668b7028..7fc5447e2b 100644 --- a/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js +++ b/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js @@ -21,25 +21,25 @@ if (cluster.isPrimary) { if (err.code === 'ENOTSUP') throw err; }); - worker1.on('message', (msg) => { + worker1.on('message', common.mustCall((msg) => { if (typeof msg !== 'object') process.exit(0); if (msg.message !== 'success') process.exit(0); if (typeof msg.port1 !== 'number') process.exit(0); const worker2 = cluster.fork({ PRT1: msg.port1 }); worker2.on('message', () => process.exit(0)); - worker2.on('exit', (code, signal) => { + worker2.on('exit', common.mustCall((code, signal) => { // This is the droid we are looking for assert.strictEqual(code, 0); assert.strictEqual(signal, null); - }); + })); // cleanup anyway process.on('exit', () => { worker1.send(BYE); worker2.send(BYE); }); - }); + })); // end primary code } else { // worker code diff --git a/test/known_issues/test-fs-cp-non-utf8.js b/test/known_issues/test-fs-cp-non-utf8.js index 12c04a4227..814859be8e 100644 --- a/test/known_issues/test-fs-cp-non-utf8.js +++ b/test/known_issues/test-fs-cp-non-utf8.js @@ -9,7 +9,7 @@ if (!common.isLinux) { common.skip('This test is only applicable to Linux'); } -const { ok, strictEqual } = require('assert'); +const assert = require('assert'); const { join } = require('path'); const path = require('path'); const tmpdir = require('../common/tmpdir'); @@ -42,8 +42,8 @@ const name = Buffer.from([ const testPath = Buffer.concat([tmpdirPath, sepBuf, name]); writeFileSync(testPath, 'test content'); -ok(existsSync(testPath)); -strictEqual(readFileSync(testPath, 'utf8'), 'test content'); +assert.ok(existsSync(testPath)); +assert.strictEqual(readFileSync(testPath, 'utf8'), 'test content'); // The cpSync is expected to fail because the implementation does not // properly handle non-UTF8 names in the path. diff --git a/test/known_issues/test-http-clientrequest-end-contentlength.js b/test/known_issues/test-http-clientrequest-end-contentlength.js index 9d5370ad07..988dd08bd2 100644 --- a/test/known_issues/test-http-clientrequest-end-contentlength.js +++ b/test/known_issues/test-http-clientrequest-end-contentlength.js @@ -16,9 +16,9 @@ const response = 'content-length: 19\r\n'; const methods = [ 'GET', 'HEAD', 'DELETE', 'POST', 'PATCH', 'PUT', 'OPTIONS' ]; const server = http.createServer(common.mustCall(function(req, res) { - req.on('data', function(chunk) { + req.on('data', common.mustCall((chunk) => { assert.strictEqual(chunk, Buffer.from(upload)); - }); + })); res.setHeader('Content-Type', 'text/plain'); let payload = `${req.method}\r\n`; for (let i = 0; i < req.rawHeaders.length; i += 2) { @@ -30,22 +30,22 @@ const server = http.createServer(common.mustCall(function(req, res) { res.end(payload); }), methods.length); -server.listen(0, function tryNextRequest() { +server.listen(0, common.mustCall(function tryNextRequest() { const method = methods.pop(); if (method === undefined) return; const port = server.address().port; - const req = http.request({ method, port }, function(res) { + const req = http.request({ method, port }, common.mustCall((res) => { const chunks = []; res.on('data', function(chunk) { chunks.push(chunk); }); - res.on('end', function() { + res.on('end', common.mustCall(() => { const received = Buffer.concat(chunks).toString(); const expected = method.toLowerCase() + '\r\n' + response; assert.strictEqual(received.toLowerCase(), expected); tryNextRequest(); - }); - }); + })); + })); req.end(upload); -}).unref(); +})).unref(); diff --git a/test/known_issues/test-http-clientrequest-end-empty-response-body.js b/test/known_issues/test-http-clientrequest-end-empty-response-body.js index 2edbe239a3..36aad2ed04 100644 --- a/test/known_issues/test-http-clientrequest-end-empty-response-body.js +++ b/test/known_issues/test-http-clientrequest-end-empty-response-body.js @@ -17,9 +17,9 @@ const response = ''; const methods = [ 'GET', 'HEAD', 'DELETE', 'POST', 'PATCH', 'PUT', 'OPTIONS' ]; const server = http.createServer(common.mustCall(function(req, res) { - req.on('data', function(chunk) { + req.on('data', common.mustCall((chunk) => { assert.strictEqual(chunk.toString(), upload); - }); + })); res.setHeader('Content-Type', 'text/plain'); res.write(`${req.method}\r\n`); for (let i = 0; i < req.rawHeaders.length; i += 2) { @@ -31,22 +31,22 @@ const server = http.createServer(common.mustCall(function(req, res) { res.end(); }), methods.length); -server.listen(0, function tryNextRequest() { +server.listen(0, common.mustCall(function tryNextRequest() { const method = methods.pop(); if (method === undefined) return; const port = server.address().port; - const req = http.request({ method, port }, function(res) { + const req = http.request({ method, port }, common.mustCall((res) => { const chunks = []; res.on('data', function(chunk) { chunks.push(chunk); }); - res.on('end', function() { + res.on('end', common.mustCall(() => { const received = Buffer.concat(chunks).toString(); const expected = method.toLowerCase() + '\r\n' + response; assert.strictEqual(received.toLowerCase(), expected); tryNextRequest(); - }); - }); + })); + })); req.end(); -}).unref(); +})).unref(); diff --git a/test/known_issues/test-http-clientrequest-write-chunked.js b/test/known_issues/test-http-clientrequest-write-chunked.js index 2cf63a8be8..77d9f503fb 100644 --- a/test/known_issues/test-http-clientrequest-write-chunked.js +++ b/test/known_issues/test-http-clientrequest-write-chunked.js @@ -16,9 +16,9 @@ const response = 'transfer-encoding: chunked\r\n'; const methods = [ 'GET', 'HEAD', 'DELETE', 'POST', 'PATCH', 'PUT', 'OPTIONS' ]; const server = http.createServer(common.mustCall(function(req, res) { - req.on('data', function(chunk) { + req.on('data', common.mustCall((chunk) => { assert.strictEqual(chunk.toString(), upload); - }); + })); res.setHeader('Content-Type', 'text/plain'); res.write(`${req.method}\r\n`); for (let i = 0; i < req.rawHeaders.length; i += 2) { @@ -30,23 +30,23 @@ const server = http.createServer(common.mustCall(function(req, res) { res.end(); }), methods.length); -server.listen(0, function tryNextRequest() { +server.listen(0, common.mustCall(function tryNextRequest() { const method = methods.pop(); if (method === undefined) return; const port = server.address().port; - const req = http.request({ method, port }, function(res) { + const req = http.request({ method, port }, common.mustCall((res) => { const chunks = []; res.on('data', function(chunk) { chunks.push(chunk); }); - res.on('end', function() { + res.on('end', common.mustCall(() => { const received = Buffer.concat(chunks).toString(); const expected = method.toLowerCase() + '\r\n' + response; assert.strictEqual(received.toLowerCase(), expected); tryNextRequest(); - }); - }); + })); + })); req.write(upload); req.end(); -}).unref(); +})).unref(); diff --git a/test/known_issues/test-http-path-contains-unicode.js b/test/known_issues/test-http-path-contains-unicode.js index 1e063a3e70..dfa632922b 100644 --- a/test/known_issues/test-http-path-contains-unicode.js +++ b/test/known_issues/test-http-path-contains-unicode.js @@ -22,7 +22,7 @@ const server = http.createServer(common.mustCall(function(req, res) { })); -server.listen(0, () => { +server.listen(0, common.mustCall(() => { http.request({ port: server.address().port, path: expected, @@ -33,4 +33,4 @@ server.listen(0, () => { console.log(e.message); process.exit(1); }).end(); -}); +})); diff --git a/test/known_issues/test-inspector-cluster-port-clash.js b/test/known_issues/test-inspector-cluster-port-clash.js index e5daee61b7..14f09016c4 100644 --- a/test/known_issues/test-inspector-cluster-port-clash.js +++ b/test/known_issues/test-inspector-cluster-port-clash.js @@ -30,11 +30,11 @@ function serialFork() { const worker = cluster.fork(); worker.on('error', (err) => assert.fail(err)); // No common.mustCall since 1 out of 3 should fail. - worker.on('online', () => { + worker.on('online', common.mustCall(() => { worker.on('message', common.mustCall((message) => { ports.push(message.debugPort); })); - }); + })); worker.on('exit', common.mustCall((code, signal) => { assert.strictEqual(signal, null); // worker 2 should fail because of port clash with `server` diff --git a/test/parallel/test-eslint-must-call-assert.js b/test/parallel/test-eslint-must-call-assert.js index 356d168fd1..bc82a5f1b3 100644 --- a/test/parallel/test-eslint-must-call-assert.js +++ b/test/parallel/test-eslint-must-call-assert.js @@ -8,7 +8,7 @@ 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 message = 'Assertions must be wrapped into `common.mustSucceed`, `common.mustCall` or `common.mustCallAtLeast`'; const tester = new RuleTester(); tester.run('must-call-assert', rule, { @@ -18,6 +18,8 @@ tester.run('must-call-assert', rule, { '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))(); }));', + 'someAsyncTask(common.mustSucceed((code) => { (() => assert.strictEqual(code, 0))(); }));', + 'someAsyncTask(mustSucceed((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")', @@ -68,11 +70,42 @@ tester.run('must-call-assert', rule, { import assert from 'node:assert'; describe("whatever", () => { - it("should not be reported", async () => { + it("should not be reported", async (t) => { assert.strictEqual(2+2, 5); + await t.test("name", () => { + assert.ok(global.test); + }); }); }); `, + ` + process.on("message", common.mustCall(() => { + Promise.all([].map(async (val) => { + val = await asyncTask(val); + assert.strictEqual(val, 3); + })).then(common.mustCall()); + })); + `, + ` + spawnSyncAndAssert( + outputFile, + { + env: { + NODE_DEBUG_NATIVE: 'SEA,MKSNAPSHOT', + ...process.env, + }, + }, + { + trim: true, + stdout: 'Hello from snapshot', + stderr(output) { + assert.doesNotMatch( + output, + /Single executable application is an experimental feature/); + }, + }, + ); + `, ], invalid: [ { diff --git a/test/sea/test-single-executable-application-inspect.js b/test/sea/test-single-executable-application-inspect.js index 688b8386ea..bd885bd91e 100644 --- a/test/sea/test-single-executable-application-inspect.js +++ b/test/sea/test-single-executable-application-inspect.js @@ -3,7 +3,7 @@ // This tests the creation of a single executable application that can be // debugged using the inspector protocol with NODE_OPTIONS=--inspect-brk=0 -require('../common'); +const common = require('../common'); const assert = require('assert'); const { writeFileSync, existsSync } = require('fs'); const { spawn } = require('child_process'); @@ -64,7 +64,7 @@ seaProcess.stdout.on('data', (data) => { console.log(`[SEA][STDOUT] ${data}`); }); -seaProcess.stderr.on('data', (data) => { +seaProcess.stderr.on('data', common.mustCallAtLeast((data) => { console.log(`[SEA][STDERR] ${data}`); seaStderr += data; @@ -103,19 +103,19 @@ seaProcess.stderr.on('data', (data) => { console.log(`[INSPECT][STDERR] ${data}`); }); - inspectorProcess.on('close', (code) => { + inspectorProcess.on('close', common.mustCall((code) => { assert.strictEqual(code, 0, `Inspector process exited with code ${code}.`); - }); + })); inspectorProcess.on('error', (err) => { throw err; }); } -}); +})); -seaProcess.on('close', (code) => { +seaProcess.on('close', common.mustCall((code) => { assert.strictEqual(code, 0, `SEA process exited with code ${code}.`); -}); +})); seaProcess.on('error', (err) => { throw err; diff --git a/tools/eslint-rules/must-call-assert.js b/tools/eslint-rules/must-call-assert.js index 4a50e04a88..1ca4b39527 100644 --- a/tools/eslint-rules/must-call-assert.js +++ b/tools/eslint-rules/must-call-assert.js @@ -1,12 +1,19 @@ 'use strict'; const message = - 'Assertions must be wrapped into `common.mustCall` or `common.mustCallAtLeast`'; + 'Assertions must be wrapped into `common.mustSucceed`, `common.mustCall` or `common.mustCallAtLeast`'; const requireCall = 'CallExpression[callee.name="require"]'; const assertModuleSpecifier = '/^(node:)?assert(.strict)?$/'; +const isPromiseAllCallArg = (node) => + node.parent?.type === 'CallExpression' && + node.parent.callee.type === 'MemberExpression' && + node.parent.callee.object.type === 'Identifier' && node.parent.callee.object.name === 'Promise' && + node.parent.callee.property.type === 'Identifier' && node.parent.callee.property.name === 'all' && + node.parent.arguments.length === 1 && node.parent.arguments[0] === node; + function findEnclosingFunction(node) { while (true) { node = node.parent; @@ -20,10 +27,20 @@ function findEnclosingFunction(node) { if ( node.parent.callee.type === 'MemberExpression' && (node.parent.callee.object.type === 'ArrayExpression' || node.parent.callee.object.type === 'Identifier') && - node.parent.callee.property.name === 'forEach' + ( + node.parent.callee.property.name === 'forEach' || + (node.parent.callee.property.name === 'map' && isPromiseAllCallArg(node.parent)) + ) ) continue; // `[].forEach()` call } else if (node.parent?.type === 'NewExpression') { if (node.parent.callee.type === 'Identifier' && node.parent.callee.name === 'Promise') continue; + } else if (node.parent?.type === 'Property') { + const ancestor = node.parent.parent?.parent; + if (ancestor?.type === 'CallExpression' && + ancestor.callee.type === 'Identifier' && + /^spawnSyncAnd(Exit(WithoutError)?|Assert)$/.test(ancestor.callee.name)) { + continue; + } } break; } @@ -31,7 +48,7 @@ function findEnclosingFunction(node) { } function isMustCallOrMustCallAtLeast(str) { - return str === 'mustCall' || str === 'mustCallAtLeast'; + return str === 'mustCall' || str === 'mustCallAtLeast' || str === 'mustSucceed'; } function isMustCallOrTest(str) { @@ -68,6 +85,7 @@ module.exports = { parent.arguments[0].type === 'Literal' && parent.arguments[0].value === 'exit' ), + t: (name) => name === 'test', // t.test }[parent.callee.object.name]?.(parent.callee.property.name) ) { return;