tools: add lint rule to ensure assertions are reached

PR-URL: https://github.com/nodejs/node/pull/60125
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
This commit is contained in:
Antoine du Hamel 2025-10-07 14:40:05 +02:00 committed by GitHub
parent b757a8f2b4
commit ec26b1c01a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 456 additions and 215 deletions

View File

@ -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: [ files: [
'test/{common,fixtures,wpt}/**/*.{js,mjs,cjs}', 'test/{common,fixtures,wpt}/**/*.{js,mjs,cjs}',

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../common'); require('../common');
const assert = require('assert').strict; const assert = require('assert/strict');
assert.throws(() => { throw new Error('foo'); }, { bar: true }); assert.throws(() => { throw new Error('foo'); }, { bar: true });

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
require('../common'); require('../common');
const assert = require('node:assert').strict; const assert = require('node:assert/strict');
const childProcess = require('node:child_process'); const childProcess = require('node:child_process');
const fixtures = require('../common/fixtures'); const fixtures = require('../common/fixtures');

View File

@ -1,13 +1,13 @@
// This tests that module.registerHooks() can be used to support unknown formats, like // This tests that module.registerHooks() can be used to support unknown formats, like
// import(wasm) // import(wasm)
import '../common/index.mjs'; import { mustCall } from '../common/index.mjs';
import assert from 'node:assert'; import assert from 'node:assert';
import { registerHooks, createRequire } from 'node:module'; import { registerHooks, createRequire } from 'node:module';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
registerHooks({ registerHooks({
load(url, context, nextLoad) { load: mustCall((url, context, nextLoad) => {
assert.match(url, /simple\.wasm$/); assert.match(url, /simple\.wasm$/);
const source = const source =
`const buf = Buffer.from([${Array.from(readFileSync(new URL(url))).join(',')}]); `const buf = Buffer.from([${Array.from(readFileSync(new URL(url))).join(',')}]);
@ -21,7 +21,7 @@ registerHooks({
source, source,
format: 'module', format: 'module',
}; };
}, }),
}); });
// Checks that it works with require. // Checks that it works with require.

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const { registerHooks } = require('module'); const { registerHooks } = require('module');
@ -15,15 +15,7 @@ const hook1 = registerHooks({
resolve(specifier, context, nextResolve) { resolve(specifier, context, nextResolve) {
return { shortCircuit: true, url: `test://${specifier}` }; return { shortCircuit: true, url: `test://${specifier}` };
}, },
load(url, context, nextLoad) { load: common.mustNotCall(),
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;
},
}); });
const hook2 = registerHooks({ const hook2 = registerHooks({

View File

@ -1,30 +1,30 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const { registerHooks } = require('module'); const { registerHooks } = require('module');
// Test that multiple loaders works together. // Test that multiple loaders works together.
const hook1 = registerHooks({ const hook1 = registerHooks({
load(url, context, nextLoad) { load: common.mustCall((url, context, nextLoad) => {
const result = nextLoad(url, context); const result = nextLoad(url, context);
assert.strictEqual(result.source, ''); assert.strictEqual(result.source, '');
return { return {
source: 'exports.hello = "world"', source: 'exports.hello = "world"',
format: 'commonjs', format: 'commonjs',
}; };
}, }),
}); });
const hook2 = registerHooks({ const hook2 = registerHooks({
load(url, context, nextLoad) { load: common.mustCall((url, context, nextLoad) => {
const result = nextLoad(url, context); const result = nextLoad(url, context);
assert.strictEqual(result.source, 'exports.hello = "world"'); assert.strictEqual(result.source, 'exports.hello = "world"');
return { return {
source: 'export const hello = "world"', source: 'export const hello = "world"',
format: 'module', format: 'module',
}; };
}, }),
}); });
const mod = require('../fixtures/empty.js'); const mod = require('../fixtures/empty.js');

View File

@ -1,18 +1,18 @@
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const { registerHooks } = require('module'); const { registerHooks } = require('module');
// Test that module syntax detection works. // Test that module syntax detection works.
const hook = registerHooks({ const hook = registerHooks({
load(url, context, nextLoad) { load: common.mustCall((url, context, nextLoad) => {
const result = nextLoad(url, context); const result = nextLoad(url, context);
assert.strictEqual(result.source, ''); assert.strictEqual(result.source, '');
return { return {
source: 'export const hello = "world"', source: 'export const hello = "world"',
}; };
}, }),
}); });
const mod = require('../fixtures/empty.js'); const mod = require('../fixtures/empty.js');

View File

@ -7,13 +7,13 @@ import { fileURL } from '../common/fixtures.mjs';
// It changes `foo` package name into `redirected-fs` and then loads `redirected-fs` // It changes `foo` package name into `redirected-fs` and then loads `redirected-fs`
const hook = registerHooks({ const hook = registerHooks({
resolve(specifier, context, nextResolve) { resolve: mustCall((specifier, context, nextResolve) => {
assert.strictEqual(specifier, 'foo'); assert.strictEqual(specifier, 'foo');
return { return {
url: 'foo://bar', url: 'foo://bar',
shortCircuit: true, shortCircuit: true,
}; };
}, }),
load: mustCall(function load(url, context, nextLoad) { load: mustCall(function load(url, context, nextLoad) {
assert.strictEqual(url, 'foo://bar'); assert.strictEqual(url, 'foo://bar');
return nextLoad(fileURL('module-hooks', 'redirected-fs.js').href, context); return nextLoad(fileURL('module-hooks', 'redirected-fs.js').href, context);

View File

@ -9,13 +9,13 @@ const fixtures = require('../common/fixtures');
// It changes `foo` package name into `redirected-fs` and then loads `redirected-fs` // It changes `foo` package name into `redirected-fs` and then loads `redirected-fs`
const hook = registerHooks({ const hook = registerHooks({
resolve(specifier, context, nextResolve) { resolve: common.mustCall((specifier, context, nextResolve) => {
assert.strictEqual(specifier, 'foo'); assert.strictEqual(specifier, 'foo');
return { return {
url: 'foo://bar', url: 'foo://bar',
shortCircuit: true, shortCircuit: true,
}; };
}, }),
load: common.mustCall(function load(url, context, nextLoad) { load: common.mustCall(function load(url, context, nextLoad) {
assert.strictEqual(url, 'foo://bar'); assert.strictEqual(url, 'foo://bar');
return nextLoad( return nextLoad(

View File

@ -9,7 +9,7 @@ const { registerHooks } = require('module');
const { readFileSync } = require('fs'); const { readFileSync } = require('fs');
registerHooks({ registerHooks({
load(url, context, nextLoad) { load: common.mustCall((url, context, nextLoad) => {
assert.match(url, /simple\.wasm$/); assert.match(url, /simple\.wasm$/);
const source = const source =
`const buf = Buffer.from([${Array.from(readFileSync(new URL(url))).join(',')}]); `const buf = Buffer.from([${Array.from(readFileSync(new URL(url))).join(',')}]);
@ -20,7 +20,7 @@ registerHooks({
source, source,
format: 'commonjs', format: 'commonjs',
}; };
}, }, 2),
}); });
// Checks that it works with require. // Checks that it works with require.

View File

@ -21,6 +21,7 @@ const hook = registerHooks({
// Check assert, which is already loaded. // Check assert, which is already loaded.
// zlib.createGzip is a function. // zlib.createGzip is a function.
// eslint-disable-next-line node-core/must-call-assert
assert.strictEqual(typeof require('assert').createGzip, 'function'); assert.strictEqual(typeof require('assert').createGzip, 'function');
hook.deregister(); hook.deregister();

View File

@ -20,6 +20,7 @@ const hook = registerHooks({
}); });
// Check assert, which is already loaded. // Check assert, which is already loaded.
// eslint-disable-next-line node-core/must-call-assert
assert.strictEqual(require('assert').exports_for_test, 'redirected assert'); assert.strictEqual(require('assert').exports_for_test, 'redirected assert');
// Check zlib, which is not yet loaded. // Check zlib, which is not yet loaded.
assert.strictEqual(require('zlib').exports_for_test, 'redirected zlib'); assert.strictEqual(require('zlib').exports_for_test, 'redirected zlib');

View File

@ -46,17 +46,17 @@ for (let i = 0; i < arr.length; i++)
arr[i] = {}; arr[i] = {};
assert.strictEqual(hook_result.destroy_called, false); assert.strictEqual(hook_result.destroy_called, false);
setImmediate(() => { setImmediate(common.mustCall(() => {
assert.strictEqual(hook_result.destroy_called, false); assert.strictEqual(hook_result.destroy_called, false);
makeCallback(asyncResource, process, () => { makeCallback(asyncResource, process, common.mustCall(() => {
const executionAsyncResource = async_hooks.executionAsyncResource(); const executionAsyncResource = async_hooks.executionAsyncResource();
// Previous versions of Node-API would have gargbage-collected // Previous versions of Node-API would have gargbage-collected
// the `asyncResource` object, now we can just assert that it is intact. // the `asyncResource` object, now we can just assert that it is intact.
assert.strictEqual(typeof executionAsyncResource, 'object'); assert.strictEqual(typeof executionAsyncResource, 'object');
assert.strictEqual(executionAsyncResource.foo, 'bar'); assert.strictEqual(executionAsyncResource.foo, 'bar');
destroyAsyncResource(asyncResource); destroyAsyncResource(asyncResource);
setImmediate(() => { setImmediate(common.mustCall(() => {
assert.strictEqual(hook_result.destroy_called, true); assert.strictEqual(hook_result.destroy_called, true);
}); }));
}); }));
}); }));

View File

@ -40,7 +40,7 @@ const resourceWrap = createAsyncResource(
assert.strictEqual(hook_result.destroy_called, false); assert.strictEqual(hook_result.destroy_called, false);
const recv = {}; 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.destroy_called, false);
assert.strictEqual( assert.strictEqual(
hook_result.resource, hook_result.resource,
@ -48,7 +48,7 @@ makeCallback(resourceWrap, recv, function callback() {
); );
assert.strictEqual(this, recv); assert.strictEqual(this, recv);
setImmediate(() => { setImmediate(common.mustCall(() => {
assert.strictEqual(hook_result.destroy_called, false); assert.strictEqual(hook_result.destroy_called, false);
assert.notStrictEqual( assert.notStrictEqual(
hook_result.resource, hook_result.resource,
@ -56,8 +56,8 @@ makeCallback(resourceWrap, recv, function callback() {
); );
destroyAsyncResource(resourceWrap); destroyAsyncResource(resourceWrap);
setImmediate(() => { setImmediate(common.mustCall(() => {
assert.strictEqual(hook_result.destroy_called, true); assert.strictEqual(hook_result.destroy_called, true);
}); }));
}); }));
}); }));

View File

@ -11,6 +11,7 @@ function testFinalize(binding) {
x = null; x = null;
global.gc(); global.gc();
process.on('uncaughtException', (err) => { process.on('uncaughtException', (err) => {
// eslint-disable-next-line node-core/must-call-assert
assert.strictEqual(err.message, 'Error during Finalize'); assert.strictEqual(err.message, 'Error during Finalize');
}); });

View File

@ -39,15 +39,15 @@ test_hook.enable();
* slots. Testing with plain object here. * slots. Testing with plain object here.
*/ */
const resource = {}; const resource = {};
makeCallback(resource, process, function cb() { makeCallback(resource, process, common.mustCall(function cb() {
assert.strictEqual(this, process); assert.strictEqual(this, process);
assert.strictEqual(async_hooks.executionAsyncResource(), resource); assert.strictEqual(async_hooks.executionAsyncResource(), resource);
}); }));
assert.strictEqual(hook_result.init_called, true); assert.strictEqual(hook_result.init_called, true);
assert.strictEqual(hook_result.before_called, true); assert.strictEqual(hook_result.before_called, true);
assert.strictEqual(hook_result.after_called, true); assert.strictEqual(hook_result.after_called, true);
setImmediate(() => { setImmediate(common.mustCall(() => {
assert.strictEqual(hook_result.destroy_called, true); assert.strictEqual(hook_result.destroy_called, true);
test_hook.disable(); test_hook.disable();
}); }));

View File

@ -72,11 +72,11 @@ assert.throws(function() {
if (arg === 1) { if (arg === 1) {
// The tests are first run on bootstrap during LoadEnvironment() in // The tests are first run on bootstrap during LoadEnvironment() in
// src/node.cc. Now run the tests through node::MakeCallback(). // src/node.cc. Now run the tests through node::MakeCallback().
setImmediate(function() { setImmediate(common.mustCall(function() {
makeCallback({}, common.mustCall(function() { makeCallback({}, common.mustCall(function() {
verifyExecutionOrder(2); verifyExecutionOrder(2);
})); }));
}); }));
} else if (arg === 2) { } else if (arg === 2) {
// Make sure there are no conflicts using node::MakeCallback() // Make sure there are no conflicts using node::MakeCallback()
// within timers. // within timers.

View File

@ -7,7 +7,7 @@
// and symbol types, while in newer versions they can be created for // and symbol types, while in newer versions they can be created for
// any value type. // any value type.
// //
const { buildType } = require('../../common'); const { mustCall, buildType } = require('../../common');
const { gcUntil } = require('../../common/gc'); const { gcUntil } = require('../../common/gc');
const assert = require('assert'); const assert = require('assert');
const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`); 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); await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ false);
} }
runAllTests(); runAllTests().then(mustCall());

View File

@ -44,9 +44,9 @@ function testWithJSMarshaller({
array.push(value); array.push(value);
if (array.length === quitAfter) { if (array.length === quitAfter) {
setImmediate(() => { setImmediate(() => {
binding.StopThread(common.mustCall(() => { binding.StopThread(() => {
resolve(array); resolve(array);
}), !!abort); }, !!abort);
}); });
} }
}, !!abort, !!launchSecondary, maxQueueSize); }, !!abort, !!launchSecondary, maxQueueSize);
@ -79,19 +79,19 @@ function testUnref(queueSize) {
new Promise(function testWithoutJSMarshaller(resolve) { new Promise(function testWithoutJSMarshaller(resolve) {
let callCount = 0; let callCount = 0;
binding.StartThreadNoNative(function testCallback() { binding.StartThreadNoNative(common.mustCallAtLeast(function testCallback() {
callCount++; callCount++;
// The default call-into-JS implementation passes no arguments. // The default call-into-JS implementation passes no arguments.
assert.strictEqual(arguments.length, 0); assert.strictEqual(arguments.length, 0);
if (callCount === binding.ARRAY_LENGTH) { if (callCount === binding.ARRAY_LENGTH) {
setImmediate(() => { setImmediate(() => {
binding.StopThread(common.mustCall(() => { binding.StopThread(() => {
resolve(); resolve();
}), false); }, 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. // Start the thread in blocking mode, and assert that all values are passed.
@ -224,4 +224,6 @@ new Promise(function testWithoutJSMarshaller(resolve) {
.then(() => testUnref(binding.MAX_QUEUE_SIZE)) .then(() => testUnref(binding.MAX_QUEUE_SIZE))
// Start a child process with an infinite queue to test rapid teardown // Start a child process with an infinite queue to test rapid teardown
.then(() => testUnref(0)); .then(() => testUnref(0))
.then(common.mustCall());

View File

@ -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`' }],
},
]
});

View File

@ -5,9 +5,9 @@ require('../common');
const { internalBinding } = require('internal/test/binding'); const { internalBinding } = require('internal/test/binding');
const { TTY, isTTY } = internalBinding('tty_wrap'); 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); const handle = new TTY(0);
handle.readStart(); handle.readStart();
@ -17,14 +17,14 @@ function isHandleActive(handle) {
return process._getActiveHandles().some((active) => active === 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(); handle.unref();
strictEqual(isHandleActive(handle), false, 'TTY handle active after unref()'); assert.ok(!isHandleActive(handle), 'TTY handle active after unref()');
handle.ref(); handle.ref();
strictEqual(isHandleActive(handle), true, 'TTY handle inactive after ref()'); assert.ok(isHandleActive(handle), 'TTY handle inactive after ref()');
handle.unref(); handle.unref();

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../common'); require('../common');
const assert = require('assert').strict; const assert = require('assert/strict');
function setup() { function setup() {
process.env.FORCE_COLOR = '1'; process.env.FORCE_COLOR = '1';

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../common'); require('../common');
const assert = require('assert').strict; const assert = require('assert/strict');
process.env.NODE_DISABLE_COLORS = true; process.env.NODE_DISABLE_COLORS = true;

View File

@ -4,20 +4,16 @@
// See also test/parallel/test-handle-wrap-hasref.js // See also test/parallel/test-handle-wrap-hasref.js
const common = require('../common'); const common = require('../common');
const strictEqual = require('assert').strictEqual; const assert = require('assert');
const ReadStream = require('tty').ReadStream; const ReadStream = require('tty').ReadStream;
const tty = new ReadStream(0); const tty = new ReadStream(0);
const { internalBinding } = require('internal/test/binding'); const { internalBinding } = require('internal/test/binding');
const isTTY = internalBinding('tty_wrap').isTTY; const isTTY = internalBinding('tty_wrap').isTTY;
strictEqual(isTTY(0), true, 'tty_wrap: stdin is not a TTY'); assert.ok(isTTY(0), 'tty_wrap: stdin is not a TTY');
strictEqual(tty._handle.hasRef(), assert.ok(tty._handle.hasRef(), 'tty_wrap: not initially refed');
true, 'tty_wrap: not initially refed');
tty.unref(); tty.unref();
strictEqual(tty._handle.hasRef(), assert.ok(!tty._handle.hasRef(), 'tty_wrap: unref() ineffective');
false, 'tty_wrap: unref() ineffective');
tty.ref(); tty.ref();
strictEqual(tty._handle.hasRef(), assert.ok(tty._handle.hasRef(), 'tty_wrap: ref() ineffective');
true, 'tty_wrap: ref() ineffective');
tty._handle.close(common.mustCall(() => tty._handle.close(common.mustCall(() =>
strictEqual(tty._handle.hasRef(), assert.ok(!tty._handle.hasRef(), 'tty_wrap: not unrefed on close')));
false, 'tty_wrap: not unrefed on close')));

View File

@ -4,7 +4,7 @@ const common = require('../common');
const originalRefreshSizeStderr = process.stderr._refreshSize; const originalRefreshSizeStderr = process.stderr._refreshSize;
const originalRefreshSizeStdout = process.stdout._refreshSize; const originalRefreshSizeStdout = process.stdout._refreshSize;
const wrap = (fn, ioStream, string) => { const wrap = common.mustCallAtLeast((fn, ioStream, string) => {
const wrapped = common.mustCall(() => { const wrapped = common.mustCall(() => {
// The console.log() call prints a string that is in the .out file. In other // 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. // words, the console.log() is part of the test, not extraneous debugging.
@ -18,7 +18,7 @@ const wrap = (fn, ioStream, string) => {
} }
}); });
return wrapped; return wrapped;
}; });
process.stderr._refreshSize = wrap(originalRefreshSizeStderr, process.stderr._refreshSize = wrap(originalRefreshSizeStderr,
process.stderr, process.stderr,

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
const assert = require('assert').strict; const assert = require('assert/strict');
const { WriteStream } = require('tty'); const { WriteStream } = require('tty');
const { inspect } = require('util'); const { inspect } = require('util');

View File

@ -1,18 +1,17 @@
'use strict'; 'use strict';
require('../common'); require('../common');
const { strictEqual } = require('assert'); const assert = require('assert');
const { isatty } = require('tty'); const { isatty } = require('tty');
strictEqual(isatty(0), true, 'stdin reported to not be a tty, but it is'); assert.ok(isatty(0), 'stdin reported to not be a tty, but it is');
strictEqual(isatty(1), true, 'stdout reported to not be a tty, but it is'); assert.ok(isatty(1), '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(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'); assert.ok(!isatty(-1), '-1 reported to be a tty, but it is not');
strictEqual(isatty(55555), false, '55555 reported to be a tty, but it is not'); assert.ok(!isatty(55555), '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'); assert.ok(!isatty(2 ** 31), '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'); assert.ok(!isatty(1.1), '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'); assert.ok(!isatty('1'), '\'1\' reported to be a tty, but it is not');
strictEqual(isatty({}), false, '{} reported to be a tty, but it is not'); assert.ok(!isatty({}), '{} reported to be a tty, but it is not');
strictEqual(isatty(() => {}), false, assert.ok(!isatty(() => {}), '() => {} reported to be a tty, but it is not');
'() => {} reported to be a tty, but it is not');

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const { mustCall } = require('../common'); const { mustCall } = require('../common');
const { notStrictEqual } = require('assert'); const assert = require('assert');
// tty.WriteStream#_refreshSize() only emits the 'resize' event when the // tty.WriteStream#_refreshSize() only emits the 'resize' event when the
// window dimensions change. We cannot influence that from the script // window dimensions change. We cannot influence that from the script
@ -8,4 +8,4 @@ const { notStrictEqual } = require('assert');
process.stdout.columns = 9001; process.stdout.columns = 9001;
process.stdout.on('resize', mustCall()); process.stdout.on('resize', mustCall());
process.kill(process.pid, 'SIGWINCH'); process.kill(process.pid, 'SIGWINCH');
setImmediate(mustCall(() => notStrictEqual(process.stdout.columns, 9001))); setImmediate(mustCall(() => assert.notStrictEqual(process.stdout.columns, 9001)));

View File

@ -30,7 +30,7 @@ const SIZE = 1000 * 1024;
const N = 40; const N = 40;
let finished = false; let finished = false;
function doSpawn(i) { const doSpawn = common.mustCall((i) => {
const child = spawn(python, ['-c', `print(${SIZE} * "C")`]); const child = spawn(python, ['-c', `print(${SIZE} * "C")`]);
let count = 0; let count = 0;
@ -43,7 +43,7 @@ function doSpawn(i) {
console.log(`stderr: ${chunk}`); console.log(`stderr: ${chunk}`);
}); });
child.on('close', () => { child.on('close', common.mustCall(() => {
// + 1 for \n or + 2 for \r\n on Windows // + 1 for \n or + 2 for \r\n on Windows
assert.strictEqual(count, SIZE + (common.isWindows ? 2 : 1)); assert.strictEqual(count, SIZE + (common.isWindows ? 2 : 1));
if (i < N) { if (i < N) {
@ -51,8 +51,8 @@ function doSpawn(i) {
} else { } else {
finished = true; finished = true;
} }
}); }));
} }, N + 1);
doSpawn(0); doSpawn(0);

View File

@ -32,7 +32,7 @@ const a = Buffer.alloc(size, 'a');
let expectedSize = 0; let expectedSize = 0;
for (let i = 0; i < 201; i++) { for (let i = 0; i < 201; i++) {
stream.write(a, (err) => { assert.ifError(err); }); stream.write(a, common.mustSucceed());
expectedSize += a.length; expectedSize += a.length;
} }

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const fs = require('fs'); const fs = require('fs');
@ -38,7 +38,7 @@ try {
// swallow // swallow
} }
fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, function(curr, prev) { fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, common.mustCall((curr, prev) => {
console.log([curr, prev]); console.log([curr, prev]);
switch (++nevents) { switch (++nevents) {
case 1: case 1:
@ -55,7 +55,7 @@ fs.watchFile(FILENAME, { interval: TIMEOUT - 250 }, function(curr, prev) {
default: default:
assert(0); assert(0);
} }
}); }, 4));
process.on('exit', function() { process.on('exit', function() {
assert.strictEqual(nevents, 4); assert.strictEqual(nevents, 4);

View File

@ -38,19 +38,19 @@ const filepath = path.join(testsubdir, 'watch.txt');
fs.mkdirSync(testsubdir, 0o700); fs.mkdirSync(testsubdir, 0o700);
function doWatch() { const doWatch = common.mustCall(() => {
const watcher = fs.watch(testDir, { persistent: true }, (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 // This function may be called with the directory depending on timing but
// must not be called with the file.. // must not be called with the file..
assert.strictEqual(filename, 'testsubdir'); assert.strictEqual(filename, 'testsubdir');
}); }, 0));
setTimeout(() => { setTimeout(() => {
fs.writeFileSync(filepath, 'test'); fs.writeFileSync(filepath, 'test');
}, 100); }, 100);
setTimeout(() => { setTimeout(() => {
watcher.close(); watcher.close();
}, 500); }, 500);
} });
if (common.isMacOS) { if (common.isMacOS) {
// On macOS delay watcher start to avoid leaking previous events. // On macOS delay watcher start to avoid leaking previous events.

View File

@ -22,7 +22,7 @@ const server = http2.createServer();
server.on('stream', (stream) => { server.on('stream', (stream) => {
stream.respondWithFile(process.execPath); stream.respondWithFile(process.execPath);
}); });
server.listen(0, () => { server.listen(0, common.mustCall(() => {
const client = http2.connect(`http://localhost:${server.address().port}`); const client = http2.connect(`http://localhost:${server.address().port}`);
const req = client.request(); const req = client.request();
@ -73,4 +73,4 @@ server.listen(0, () => {
server.close(); server.close();
})); }));
req.end(); req.end();
}); }));

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const http = require('http'); const http = require('http');
@ -29,12 +29,12 @@ let responses = 0;
let requests = 0; let requests = 0;
let connection; let connection;
const server = http.Server(function(req, res) { const server = http.Server(common.mustCall((req, res) => {
requests++; requests++;
assert.strictEqual(req.connection, connection); assert.strictEqual(req.connection, connection);
res.writeHead(200); res.writeHead(200);
res.end('hello world\n'); res.end('hello world\n');
}); }, expected));
server.once('connection', function(c) { server.once('connection', function(c) {
connection = c; connection = c;

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const net = require('net'); const net = require('net');
@ -62,13 +62,7 @@ function runClient(port, callback) {
client.end(); client.end();
}); });
client.on('error', function(e) { client.on('close', common.mustCall(function(had_error) {
console.log('\n\nERROOOOOr');
throw e;
});
client.on('close', function(had_error) {
console.log('.');
assert.strictEqual(had_error, false); assert.strictEqual(had_error, false);
assert.strictEqual(client.recved.length, bytes); assert.strictEqual(client.recved.length, bytes);
@ -82,7 +76,7 @@ function runClient(port, callback) {
} else { } else {
callback(); callback();
} }
}); }, connections_per_client));
} }
server.listen(0, function() { server.listen(0, function() {

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const net = require('net'); const net = require('net');
@ -42,7 +42,7 @@ const server = net.createServer((connection) => {
write(0); write(0);
}); });
server.on('listening', () => { server.on('listening', common.mustCall(() => {
const client = net.createConnection(server.address().port); const client = net.createConnection(server.address().port);
client.setEncoding('ascii'); client.setEncoding('ascii');
client.on('data', (d) => { client.on('data', (d) => {
@ -50,39 +50,39 @@ server.on('listening', () => {
recv += d; recv += d;
}); });
setTimeout(() => { setTimeout(common.mustCall(() => {
chars_recved = recv.length; chars_recved = recv.length;
console.log(`pause at: ${chars_recved}`); console.log(`pause at: ${chars_recved}`);
assert.strictEqual(chars_recved > 1, true); assert.strictEqual(chars_recved > 1, true);
client.pause(); client.pause();
setTimeout(() => { setTimeout(common.mustCall(() => {
console.log(`resume at: ${chars_recved}`); console.log(`resume at: ${chars_recved}`);
assert.strictEqual(chars_recved, recv.length); assert.strictEqual(chars_recved, recv.length);
client.resume(); client.resume();
setTimeout(() => { setTimeout(common.mustCall(() => {
chars_recved = recv.length; chars_recved = recv.length;
console.log(`pause at: ${chars_recved}`); console.log(`pause at: ${chars_recved}`);
client.pause(); client.pause();
setTimeout(() => { setTimeout(common.mustCall(() => {
console.log(`resume at: ${chars_recved}`); console.log(`resume at: ${chars_recved}`);
assert.strictEqual(chars_recved, recv.length); assert.strictEqual(chars_recved, recv.length);
client.resume(); client.resume();
}, 500); }), 500);
}, 500); }), 500);
}, 500); }), 500);
}, 500); }), 500);
client.on('end', () => { client.on('end', () => {
server.close(); server.close();
client.end(); client.end();
}); });
}); }));
server.listen(0); server.listen(0);
process.on('exit', () => { process.on('exit', () => {

View File

@ -24,61 +24,58 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const net = require('net'); const net = require('net');
function pingPongTest(host, on_complete) { {
const N = 100; const N = 100;
const DELAY = 1; const DELAY = 1;
let count = 0; let count = 0;
let client_ended = false; 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.setEncoding('utf8');
socket.on('data', function(data) { socket.on('data', common.mustCallAtLeast((data) => {
console.log(data); console.log(data);
assert.strictEqual(data, 'PING'); assert.strictEqual(data, 'PING');
assert.strictEqual(socket.readyState, 'open'); assert.strictEqual(socket.readyState, 'open');
assert.strictEqual(count <= N, true); assert.strictEqual(count <= N, true);
setTimeout(function() { setTimeout(common.mustCall(() => {
assert.strictEqual(socket.readyState, 'open'); assert.strictEqual(socket.readyState, 'open');
socket.write('PONG'); socket.write('PONG');
}, DELAY); }), DELAY);
}); }));
socket.on('timeout', function() { socket.on('timeout', common.mustNotCall('server-side timeout!!'));
console.error('server-side timeout!!');
assert.strictEqual(false, true);
});
socket.on('end', function() { socket.on('end', common.mustCall(() => {
console.log('server-side socket EOF'); console.log('server-side socket EOF');
assert.strictEqual(socket.readyState, 'writeOnly'); assert.strictEqual(socket.readyState, 'writeOnly');
socket.end(); socket.end();
}); }));
socket.on('close', function(had_error) { socket.on('close', common.mustCall((had_error) => {
console.log('server-side socket.end'); console.log('server-side socket.end');
assert.strictEqual(had_error, false); assert.strictEqual(had_error, false);
assert.strictEqual(socket.readyState, 'closed'); assert.strictEqual(socket.readyState, 'closed');
socket.server.close(); socket.server.close();
}); }));
}); }));
server.listen(0, host, common.mustCall(function() { server.listen(0, undefined, common.mustCall(() => {
const client = net.createConnection(server.address().port, host); const client = net.createConnection(server.address().port, undefined);
client.setEncoding('utf8'); client.setEncoding('utf8');
client.on('connect', function() { client.on('connect', common.mustCall(() => {
assert.strictEqual(client.readyState, 'open'); assert.strictEqual(client.readyState, 'open');
client.write('PING'); client.write('PING');
}); }));
client.on('data', function(data) { client.on('data', common.mustCallAtLeast((data) => {
console.log(data); console.log(data);
assert.strictEqual(data, 'PONG'); assert.strictEqual(data, 'PONG');
assert.strictEqual(client.readyState, 'open'); assert.strictEqual(client.readyState, 'open');
setTimeout(function() { setTimeout(common.mustCall(() => {
assert.strictEqual(client.readyState, 'open'); assert.strictEqual(client.readyState, 'open');
if (count++ < N) { if (count++ < N) {
client.write('PING'); client.write('PING');
@ -87,21 +84,15 @@ function pingPongTest(host, on_complete) {
client.end(); client.end();
client_ended = true; client_ended = true;
} }
}, DELAY); }), DELAY);
}); }));
client.on('timeout', function() { client.on('timeout', common.mustNotCall('client-side timeout!!'));
console.error('client-side timeout!!');
assert.strictEqual(false, true);
});
client.on('close', common.mustCall(function() { client.on('close', common.mustCall(function() {
console.log('client.end'); console.log('client.end');
assert.strictEqual(count, N + 1); assert.strictEqual(count, N + 1);
assert.ok(client_ended); assert.ok(client_ended);
if (on_complete) on_complete();
})); }));
})); }));
} }
pingPongTest();

View File

@ -24,14 +24,12 @@ const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const net = require('net'); const net = require('net');
let tests_run = 0; const pingPongTest = common.mustCall((host, on_complete) => {
function pingPongTest(host, on_complete) {
const N = 1000; const N = 1000;
let count = 0; let count = 0;
let sent_final_ping = false; 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 !== null, true);
assert.strictEqual(socket.remoteAddress !== undefined, true); assert.strictEqual(socket.remoteAddress !== undefined, true);
const address = socket.remoteAddress; const address = socket.remoteAddress;
@ -49,38 +47,38 @@ function pingPongTest(host, on_complete) {
socket.setNoDelay(); socket.setNoDelay();
socket.timeout = 0; socket.timeout = 0;
socket.on('data', function(data) { socket.on('data', common.mustCallAtLeast((data) => {
console.log(`server got: ${JSON.stringify(data)}`); console.log(`server got: ${JSON.stringify(data)}`);
assert.strictEqual(socket.readyState, 'open'); assert.strictEqual(socket.readyState, 'open');
assert.strictEqual(count <= N, true); assert.strictEqual(count <= N, true);
if (/PING/.test(data)) { if (/PING/.test(data)) {
socket.write('PONG'); socket.write('PONG');
} }
}); }, N + 1));
socket.on('end', function() { socket.on('end', common.mustCall(() => {
assert.strictEqual(socket.readyState, 'writeOnly'); assert.strictEqual(socket.readyState, 'writeOnly');
socket.end(); socket.end();
}); }));
socket.on('close', function(had_error) { socket.on('close', common.mustCall((had_error) => {
assert.strictEqual(had_error, false); assert.strictEqual(had_error, false);
assert.strictEqual(socket.readyState, 'closed'); assert.strictEqual(socket.readyState, 'closed');
socket.server.close(); socket.server.close();
}); }));
}); }));
server.listen(0, host, function() { server.listen(0, host, common.mustCall(() => {
const client = net.createConnection(server.address().port, host); const client = net.createConnection(server.address().port, host);
client.setEncoding('utf8'); client.setEncoding('utf8');
client.on('connect', function() { client.on('connect', common.mustCall(() => {
assert.strictEqual(client.readyState, 'open'); assert.strictEqual(client.readyState, 'open');
client.write('PING'); client.write('PING');
}); }));
client.on('data', function(data) { client.on('data', common.mustCallAtLeast((data) => {
console.log(`client got: ${data}`); console.log(`client got: ${data}`);
assert.strictEqual(data, 'PONG'); assert.strictEqual(data, 'PONG');
@ -99,22 +97,17 @@ function pingPongTest(host, on_complete) {
client.write('PING'); client.write('PING');
client.end(); client.end();
} }
}); }));
client.on('close', function() { client.on('close', common.mustCall(() => {
assert.strictEqual(count, N + 1); assert.strictEqual(count, N + 1);
assert.strictEqual(sent_final_ping, true); assert.strictEqual(sent_final_ping, true);
if (on_complete) on_complete(); if (on_complete) on_complete();
tests_run += 1; }));
}); }));
}); }, common.hasIPv6 ? 3 : 2);
}
// All are run at once and will run on different ports. // All are run at once and will run on different ports.
pingPongTest(null); pingPongTest(null);
pingPongTest('127.0.0.1'); pingPongTest('127.0.0.1');
if (common.hasIPv6) pingPongTest('::1'); if (common.hasIPv6) pingPongTest('::1');
process.on('exit', function() {
assert.strictEqual(tests_run, common.hasIPv6 ? 3 : 2);
});

View File

@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'; 'use strict';
require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const net = require('net'); 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; const port = echo_server.address().port;
console.log(`server listening at ${port}`); console.log(`server listening at ${port}`);
@ -65,7 +65,7 @@ echo_server.listen(0, () => {
client.write('hello\r\n'); client.write('hello\r\n');
}); });
client.on('data', (chunk) => { client.on('data', common.mustCallAtLeast((chunk) => {
assert.strictEqual(chunk, 'hello\r\n'); assert.strictEqual(chunk, 'hello\r\n');
if (exchanges++ < 5) { if (exchanges++ < 5) {
setTimeout(() => { setTimeout(() => {
@ -79,7 +79,7 @@ echo_server.listen(0, () => {
console.dir(starttime); console.dir(starttime);
} }
} }
}); }));
client.on('timeout', () => { client.on('timeout', () => {
throw new Error("client timeout - this shouldn't happen"); throw new Error("client timeout - this shouldn't happen");
@ -94,7 +94,7 @@ echo_server.listen(0, () => {
console.log('client disconnect'); console.log('client disconnect');
echo_server.close(); echo_server.close();
}); });
}); }));
process.on('exit', () => { process.on('exit', () => {
assert.ok(starttime != null); assert.ok(starttime != null);

View File

@ -57,10 +57,10 @@ function makeRequest() {
const child = spawn(process.execPath, args); const child = spawn(process.execPath, args);
child.on('exit', function(code) { child.on('exit', common.mustCall((code) => {
assert.match(stderrBuffer, /DONE/); assert.match(stderrBuffer, /DONE/);
assert.strictEqual(code, 0); assert.strictEqual(code, 0);
}); }));
// The following two lines forward the stdio from the child // The following two lines forward the stdio from the child
// to parent process for debugging. // to parent process for debugging.
@ -83,7 +83,7 @@ const serverOptions = {
let uploadCount = 0; 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. // 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 // We need to make sure the server isn't keeping the event loop alive
// while the upload is in progress. // while the upload is in progress.
@ -94,12 +94,12 @@ const server = https.Server(serverOptions, function(req, res) {
uploadCount += d.length; uploadCount += d.length;
}); });
req.on('end', function() { req.on('end', common.mustCall(() => {
assert.strictEqual(uploadCount, bytesExpected); assert.strictEqual(uploadCount, bytesExpected);
res.writeHead(200, { 'content-type': 'text/plain' }); res.writeHead(200, { 'content-type': 'text/plain' });
res.end('successful upload\n'); res.end('successful upload\n');
}); }));
}); }));
server.listen(0, function() { server.listen(0, function() {
console.log(`expecting ${bytesExpected} bytes`); console.log(`expecting ${bytesExpected} bytes`);

View File

@ -72,41 +72,41 @@ const WINDOW = 200; // Why does this need to be so big?
// Single param: // Single param:
{ {
setTimeout(function(param) { setTimeout(common.mustCall(function(param) {
assert.strictEqual(param, 'test param'); assert.strictEqual(param, 'test param');
}, 1000, 'test param'); }), 1000, 'test param');
} }
{ {
let interval_count = 0; let interval_count = 0;
setInterval(function(param) { setInterval(common.mustCall(function(param) {
++interval_count; ++interval_count;
assert.strictEqual(param, 'test param'); assert.strictEqual(param, 'test param');
if (interval_count === 3) if (interval_count === 3)
clearInterval(this); clearInterval(this);
}, 1000, 'test param'); }, 3), 1000, 'test param');
} }
// Multiple param // Multiple param
{ {
setTimeout(function(param1, param2) { setTimeout(common.mustCall(function(param1, param2) {
assert.strictEqual(param1, 'param1'); assert.strictEqual(param1, 'param1');
assert.strictEqual(param2, 'param2'); assert.strictEqual(param2, 'param2');
}, 1000, 'param1', 'param2'); }), 1000, 'param1', 'param2');
} }
{ {
let interval_count = 0; let interval_count = 0;
setInterval(function(param1, param2) { setInterval(common.mustCall(function(param1, param2) {
++interval_count; ++interval_count;
assert.strictEqual(param1, 'param1'); assert.strictEqual(param1, 'param1');
assert.strictEqual(param2, 'param2'); assert.strictEqual(param2, 'param2');
if (interval_count === 3) if (interval_count === 3)
clearInterval(this); clearInterval(this);
}, 1000, 'param1', 'param2'); }, 3), 1000, 'param1', 'param2');
} }
// setInterval(cb, 0) should be called multiple times. // setInterval(cb, 0) should be called multiple times.

View File

@ -37,12 +37,9 @@ const options = {
}; };
class Mediator extends stream.Writable { class Mediator extends stream.Writable {
constructor() { buf = '';
super();
this.buf = '';
}
_write(data, enc, cb) { _write = common.mustCallAtLeast((data, enc, cb) => {
this.buf += data; this.buf += data;
setTimeout(cb, 0); setTimeout(cb, 0);
@ -50,7 +47,7 @@ class Mediator extends stream.Writable {
assert.strictEqual(this.buf, request.toString()); assert.strictEqual(this.buf, request.toString());
server.close(); server.close();
} }
} });
} }
const mediator = new Mediator(); const mediator = new Mediator();

View File

@ -35,7 +35,7 @@ const baselineRss = process.memoryUsage.rss();
const start = Date.now(); const start = Date.now();
const interval = setInterval(function() { const interval = setInterval(common.mustCallAtLeast(function() {
try { try {
vm.runInNewContext('throw 1;'); vm.runInNewContext('throw 1;');
} catch { } catch {
@ -53,7 +53,7 @@ const interval = setInterval(function() {
testContextLeak(); testContextLeak();
} }
}, 1); }), 1);
function testContextLeak() { function testContextLeak() {
// TODO: This needs a comment explaining what it's doing. Will it crash the // TODO: This needs a comment explaining what it's doing. Will it crash the

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
const tmpdir = require('../common/tmpdir'); const tmpdir = require('../common/tmpdir');
const { strictEqual } = require('assert'); const assert = require('assert');
const { closeSync, openSync, readFileSync, writeFileSync } = require('fs'); const { closeSync, openSync, readFileSync, writeFileSync } = require('fs');
const { join } = require('path'); const { join } = require('path');
const { WASI } = require('wasi'); const { WASI } = require('wasi');
@ -24,10 +24,10 @@ const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
(async () => { (async () => {
const { instance } = await WebAssembly.instantiate(buffer, importObject); const { instance } = await WebAssembly.instantiate(buffer, importObject);
strictEqual(wasi.start(instance), 0); assert.strictEqual(wasi.start(instance), 0);
closeSync(stdin); closeSync(stdin);
closeSync(stdout); closeSync(stdout);
closeSync(stderr); closeSync(stderr);
strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31)); assert.strictEqual(readFileSync(stdoutFile, 'utf8').trim(), 'x'.repeat(31));
strictEqual(readFileSync(stderrFile, 'utf8').trim(), ''); assert.strictEqual(readFileSync(stderrFile, 'utf8').trim(), '');
})().then(common.mustCall()); })().then(common.mustCall());

View File

@ -25,10 +25,10 @@ const bytecode = new Uint8Array([
if (!process.env.HAS_STARTED_WORKER) { if (!process.env.HAS_STARTED_WORKER) {
process.env.HAS_STARTED_WORKER = 1; process.env.HAS_STARTED_WORKER = 1;
const worker = new Worker(__filename); const worker = new Worker(__filename);
worker.once('message', (message) => { worker.once('message', common.mustCall((message) => {
assert.strictEqual(message, 'start'); assert.strictEqual(message, 'start');
setTimeout(() => worker.terminate(), common.platformTimeout(50)); setTimeout(() => worker.terminate(), common.platformTimeout(50));
}); }));
} else { } else {
go(); go();
} }

View File

@ -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`',
});
},
};
},
};