diff --git a/doc/api/assert.md b/doc/api/assert.md index 1f97f37e2cc95d..b43ddefdb7456f 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -215,331 +215,6 @@ try { } ``` -## Class: `assert.CallTracker` - - - -> Stability: 0 - Deprecated - -This feature is deprecated and will be removed in a future version. -Please consider using alternatives such as the -[`mock`][] helper function. - -### `new assert.CallTracker()` - - - -Creates a new [`CallTracker`][] object which can be used to track if functions -were called a specific number of times. The `tracker.verify()` must be called -for the verification to take place. The usual pattern would be to call it in a -[`process.on('exit')`][] handler. - -```mjs -import assert from 'node:assert'; -import process from 'node:process'; - -const tracker = new assert.CallTracker(); - -function func() {} - -// callsfunc() must be called exactly 1 time before tracker.verify(). -const callsfunc = tracker.calls(func, 1); - -callsfunc(); - -// Calls tracker.verify() and verifies if all tracker.calls() functions have -// been called exact times. -process.on('exit', () => { - tracker.verify(); -}); -``` - -```cjs -const assert = require('node:assert'); -const process = require('node:process'); - -const tracker = new assert.CallTracker(); - -function func() {} - -// callsfunc() must be called exactly 1 time before tracker.verify(). -const callsfunc = tracker.calls(func, 1); - -callsfunc(); - -// Calls tracker.verify() and verifies if all tracker.calls() functions have -// been called exact times. -process.on('exit', () => { - tracker.verify(); -}); -``` - -### `tracker.calls([fn][, exact])` - - - -* `fn` {Function} **Default:** A no-op function. -* `exact` {number} **Default:** `1`. -* Returns: {Function} A function that wraps `fn`. - -The wrapper function is expected to be called exactly `exact` times. If the -function has not been called exactly `exact` times when -[`tracker.verify()`][] is called, then [`tracker.verify()`][] will throw an -error. - -```mjs -import assert from 'node:assert'; - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func); -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func); -``` - -### `tracker.getCalls(fn)` - - - -* `fn` {Function} - -* Returns: {Array} An array with all the calls to a tracked function. - -* Object {Object} - * `thisArg` {Object} - * `arguments` {Array} the arguments passed to the tracked function - -```mjs -import assert from 'node:assert'; - -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); -callsfunc(1, 2, 3); - -assert.deepStrictEqual(tracker.getCalls(callsfunc), - [{ thisArg: undefined, arguments: [1, 2, 3] }]); -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); -callsfunc(1, 2, 3); - -assert.deepStrictEqual(tracker.getCalls(callsfunc), - [{ thisArg: undefined, arguments: [1, 2, 3] }]); -``` - -### `tracker.report()` - - - -* Returns: {Array} An array of objects containing information about the wrapper - functions returned by [`tracker.calls()`][]. -* Object {Object} - * `message` {string} - * `actual` {number} The actual number of times the function was called. - * `expected` {number} The number of times the function was expected to be - called. - * `operator` {string} The name of the function that is wrapped. - * `stack` {Object} A stack trace of the function. - -The arrays contains information about the expected and actual number of calls of -the functions that have not been called the expected number of times. - -```mjs -import assert from 'node:assert'; - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -// Returns an array containing information on callsfunc() -console.log(tracker.report()); -// [ -// { -// message: 'Expected the func function to be executed 2 time(s) but was -// executed 0 time(s).', -// actual: 0, -// expected: 2, -// operator: 'func', -// stack: stack trace -// } -// ] -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -// Returns an array containing information on callsfunc() -console.log(tracker.report()); -// [ -// { -// message: 'Expected the func function to be executed 2 time(s) but was -// executed 0 time(s).', -// actual: 0, -// expected: 2, -// operator: 'func', -// stack: stack trace -// } -// ] -``` - -### `tracker.reset([fn])` - - - -* `fn` {Function} a tracked function to reset. - -Reset calls of the call tracker. -If a tracked function is passed as an argument, the calls will be reset for it. -If no arguments are passed, all tracked functions will be reset. - -```mjs -import assert from 'node:assert'; - -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); - -callsfunc(); -// Tracker was called once -assert.strictEqual(tracker.getCalls(callsfunc).length, 1); - -tracker.reset(callsfunc); -assert.strictEqual(tracker.getCalls(callsfunc).length, 0); -``` - -```cjs -const assert = require('node:assert'); - -const tracker = new assert.CallTracker(); - -function func() {} -const callsfunc = tracker.calls(func); - -callsfunc(); -// Tracker was called once -assert.strictEqual(tracker.getCalls(callsfunc).length, 1); - -tracker.reset(callsfunc); -assert.strictEqual(tracker.getCalls(callsfunc).length, 0); -``` - -### `tracker.verify()` - - - -Iterates through the list of functions passed to -[`tracker.calls()`][] and will throw an error for functions that -have not been called the expected number of times. - -```mjs -import assert from 'node:assert'; - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -callsfunc(); - -// Will throw an error since callsfunc() was only called once. -tracker.verify(); -``` - -```cjs -const assert = require('node:assert'); - -// Creates call tracker. -const tracker = new assert.CallTracker(); - -function func() {} - -// Returns a function that wraps func() that must be called exact times -// before tracker.verify(). -const callsfunc = tracker.calls(func, 2); - -callsfunc(); - -// Will throw an error since callsfunc() was only called once. -tracker.verify(); -``` - ## `assert(value[, message])` -Type: Runtime +Type: End-of-Life -In a future version of Node.js, [`assert.CallTracker`][], -will be removed. -Consider using alternatives such as the [`mock`][] helper function. +The `assert.CallTracker` API has been removed. ### DEP0174: calling `promisify` on a function that returns a `Promise` @@ -3924,7 +3925,6 @@ upon `require('node:module').builtinModules`. [`SlowBuffer`]: buffer.md#class-slowbuffer [`String.prototype.toWellFormed`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toWellFormed [`WriteStream.open()`]: fs.md#class-fswritestream -[`assert.CallTracker`]: assert.md#class-assertcalltracker [`assert`]: assert.md [`asyncResource.runInAsyncScope()`]: async_context.md#asyncresourceruninasyncscopefn-thisarg-args [`buffer.subarray`]: buffer.md#bufsubarraystart-end @@ -3986,7 +3986,6 @@ upon `require('node:module').builtinModules`. [`message.socket`]: http.md#messagesocket [`message.trailersDistinct`]: http.md#messagetrailersdistinct [`message.trailers`]: http.md#messagetrailers -[`mock`]: test.md#mocking [`module.createRequire()`]: module.md#modulecreaterequirefilename [`os.networkInterfaces()`]: os.md#osnetworkinterfaces [`os.tmpdir()`]: os.md#ostmpdir diff --git a/lib/assert.js b/lib/assert.js index 657ac6225d9833..09c32693805b9e 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -54,10 +54,9 @@ const { isPromise, isRegExp, } = require('internal/util/types'); -const { isError, deprecate } = require('internal/util'); +const { isError } = require('internal/util'); const { innerOk } = require('internal/assert/utils'); -const CallTracker = require('internal/assert/calltracker'); const { validateFunction, } = require('internal/validators'); @@ -823,8 +822,6 @@ assert.doesNotMatch = function doesNotMatch(string, regexp, message) { internalMatch(string, regexp, message, doesNotMatch); }; -assert.CallTracker = deprecate(CallTracker, 'assert.CallTracker is deprecated.', 'DEP0173'); - /** * Expose a strict only variant of assert. * @param {...any} args diff --git a/lib/internal/assert/calltracker.js b/lib/internal/assert/calltracker.js deleted file mode 100644 index 0cbfea6d23d038..00000000000000 --- a/lib/internal/assert/calltracker.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -const { - ArrayPrototypePush, - ArrayPrototypeSlice, - Error, - FunctionPrototype, - ObjectFreeze, - Proxy, - ReflectApply, - SafeSet, - SafeWeakMap, -} = primordials; - -const { - codes: { - ERR_INVALID_ARG_VALUE, - ERR_UNAVAILABLE_DURING_EXIT, - }, -} = require('internal/errors'); -const AssertionError = require('internal/assert/assertion_error'); -const { - validateUint32, -} = require('internal/validators'); - -const noop = FunctionPrototype; - -class CallTrackerContext { - #expected; - #calls; - #name; - #stackTrace; - constructor({ expected, stackTrace, name }) { - this.#calls = []; - this.#expected = expected; - this.#stackTrace = stackTrace; - this.#name = name; - } - - track(thisArg, args) { - const argsClone = ObjectFreeze(ArrayPrototypeSlice(args)); - ArrayPrototypePush(this.#calls, ObjectFreeze({ thisArg, arguments: argsClone })); - } - - get delta() { - return this.#calls.length - this.#expected; - } - - reset() { - this.#calls = []; - } - getCalls() { - return ObjectFreeze(ArrayPrototypeSlice(this.#calls)); - } - - report() { - if (this.delta !== 0) { - const message = `Expected the ${this.#name} function to be ` + - `executed ${this.#expected} time(s) but was ` + - `executed ${this.#calls.length} time(s).`; - return { - message, - actual: this.#calls.length, - expected: this.#expected, - operator: this.#name, - stack: this.#stackTrace, - }; - } - } -} - -class CallTracker { - - #callChecks = new SafeSet(); - #trackedFunctions = new SafeWeakMap(); - - #getTrackedFunction(tracked) { - if (!this.#trackedFunctions.has(tracked)) { - throw new ERR_INVALID_ARG_VALUE('tracked', tracked, 'is not a tracked function'); - } - return this.#trackedFunctions.get(tracked); - } - - reset(tracked) { - if (tracked === undefined) { - this.#callChecks.forEach((check) => check.reset()); - return; - } - - this.#getTrackedFunction(tracked).reset(); - } - - getCalls(tracked) { - return this.#getTrackedFunction(tracked).getCalls(); - } - - calls(fn, expected = 1) { - if (process._exiting) - throw new ERR_UNAVAILABLE_DURING_EXIT(); - if (typeof fn === 'number') { - expected = fn; - fn = noop; - } else if (fn === undefined) { - fn = noop; - } - - validateUint32(expected, 'expected', true); - - const context = new CallTrackerContext({ - expected, - // eslint-disable-next-line no-restricted-syntax - stackTrace: new Error(), - name: fn.name || 'calls', - }); - const tracked = new Proxy(fn, { - __proto__: null, - apply(fn, thisArg, argList) { - context.track(thisArg, argList); - return ReflectApply(fn, thisArg, argList); - }, - }); - this.#callChecks.add(context); - this.#trackedFunctions.set(tracked, context); - return tracked; - } - - report() { - const errors = []; - for (const context of this.#callChecks) { - const message = context.report(); - if (message !== undefined) { - ArrayPrototypePush(errors, message); - } - } - return errors; - } - - verify() { - const errors = this.report(); - if (errors.length === 0) { - return; - } - const message = errors.length === 1 ? - errors[0].message : - 'Functions were not called the expected number of times'; - throw new AssertionError({ - message, - details: errors, - }); - } -} - -module.exports = CallTracker; diff --git a/test/parallel/test-assert-calltracker-calls.js b/test/parallel/test-assert-calltracker-calls.js deleted file mode 100644 index 7b73f3fefaf6ab..00000000000000 --- a/test/parallel/test-assert-calltracker-calls.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; -const common = require('../common'); -const assert = require('assert'); - -// This test ensures that assert.CallTracker.calls() works as intended. - -const tracker = new assert.CallTracker(); - -function bar() {} - -const err = { - code: 'ERR_INVALID_ARG_TYPE', -}; - -// Ensures calls() throws on invalid input types. -assert.throws(() => { - const callsbar = tracker.calls(bar, '1'); - callsbar(); -}, err -); - -assert.throws(() => { - const callsbar = tracker.calls(bar, 0.1); - callsbar(); -}, { code: 'ERR_OUT_OF_RANGE' } -); - -assert.throws(() => { - const callsbar = tracker.calls(bar, true); - callsbar(); -}, err -); - -assert.throws(() => { - const callsbar = tracker.calls(bar, () => {}); - callsbar(); -}, err -); - -assert.throws(() => { - const callsbar = tracker.calls(bar, null); - callsbar(); -}, err -); - -// Expects an error as tracker.calls() cannot be called within a process exit -// handler. -process.on('exit', () => { - assert.throws(() => tracker.calls(bar, 1), { - code: 'ERR_UNAVAILABLE_DURING_EXIT', - }); -}); - -const msg = 'Expected to throw'; - -function func() { - throw new Error(msg); -} - -const callsfunc = tracker.calls(func, 1); - -// Expects callsfunc() to call func() which throws an error. -assert.throws( - () => callsfunc(), - { message: msg } -); - -{ - const tracker = new assert.CallTracker(); - const callsNoop = tracker.calls(1); - callsNoop(); - tracker.verify(); -} - -{ - const tracker = new assert.CallTracker(); - const callsNoop = tracker.calls(undefined, 1); - callsNoop(); - tracker.verify(); -} - -{ - function func() {} - const tracker = new assert.CallTracker(); - const callsfunc = tracker.calls(func); - assert.strictEqual(callsfunc.length, 0); -} - -{ - function func(a, b, c = 2) {} - const tracker = new assert.CallTracker(); - const callsfunc = tracker.calls(func); - assert.strictEqual(callsfunc.length, 2); -} - -{ - function func(a, b, c = 2) {} - delete func.length; - const tracker = new assert.CallTracker(); - const callsfunc = tracker.calls(func); - assert.strictEqual(Object.hasOwn(callsfunc, 'length'), false); -} - -{ - const ArrayIteratorPrototype = Reflect.getPrototypeOf( - Array.prototype.values() - ); - const { next } = ArrayIteratorPrototype; - ArrayIteratorPrototype.next = common.mustNotCall( - '%ArrayIteratorPrototype%.next' - ); - Object.prototype.get = common.mustNotCall('%Object.prototype%.get'); - - const customPropertyValue = Symbol(); - function func(a, b, c = 2) { - return a + b + c; - } - func.customProperty = customPropertyValue; - Object.defineProperty(func, 'length', { get: common.mustNotCall() }); - const tracker = new assert.CallTracker(); - const callsfunc = tracker.calls(func); - assert.strictEqual(Object.hasOwn(callsfunc, 'length'), true); - assert.strictEqual(callsfunc.customProperty, customPropertyValue); - assert.strictEqual(callsfunc(1, 2, 3), 6); - - ArrayIteratorPrototype.next = next; - delete Object.prototype.get; -} diff --git a/test/parallel/test-assert-calltracker-getCalls.js b/test/parallel/test-assert-calltracker-getCalls.js deleted file mode 100644 index ddf5d554167fe0..00000000000000 --- a/test/parallel/test-assert-calltracker-getCalls.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; -require('../common'); -const assert = require('assert'); -const { describe, it } = require('node:test'); - - -describe('assert.CallTracker.getCalls()', { concurrency: !process.env.TEST_PARALLEL }, () => { - const tracker = new assert.CallTracker(); - - it('should return empty list when no calls', () => { - const fn = tracker.calls(); - assert.deepStrictEqual(tracker.getCalls(fn), []); - }); - - it('should return calls', () => { - const fn = tracker.calls(() => {}); - const arg1 = {}; - const arg2 = {}; - fn(arg1, arg2); - fn.call(arg2, arg2); - assert.deepStrictEqual(tracker.getCalls(fn), [ - { arguments: [arg1, arg2], thisArg: undefined }, - { arguments: [arg2], thisArg: arg2 }]); - }); - - it('should throw when getting calls of a non-tracked function', () => { - [() => {}, 1, true, null, undefined, {}, []].forEach((fn) => { - assert.throws(() => tracker.getCalls(fn), { code: 'ERR_INVALID_ARG_VALUE' }); - }); - }); - - it('should return a frozen object', () => { - const fn = tracker.calls(); - fn(); - const calls = tracker.getCalls(fn); - assert.throws(() => calls.push(1), /object is not extensible/); - assert.throws(() => Object.assign(calls[0], { foo: 'bar' }), /object is not extensible/); - assert.throws(() => calls[0].arguments.push(1), /object is not extensible/); - }); -}); - -describe('assert.CallTracker.reset()', () => { - const tracker = new assert.CallTracker(); - - it('should reset calls', () => { - const fn = tracker.calls(); - fn(); - fn(); - fn(); - assert.strictEqual(tracker.getCalls(fn).length, 3); - tracker.reset(fn); - assert.deepStrictEqual(tracker.getCalls(fn), []); - }); - - it('should reset all calls', () => { - const fn1 = tracker.calls(); - const fn2 = tracker.calls(); - fn1(); - fn2(); - assert.strictEqual(tracker.getCalls(fn1).length, 1); - assert.strictEqual(tracker.getCalls(fn2).length, 1); - tracker.reset(); - assert.deepStrictEqual(tracker.getCalls(fn1), []); - assert.deepStrictEqual(tracker.getCalls(fn2), []); - }); - - - it('should throw when resetting a non-tracked function', () => { - [() => {}, 1, true, null, {}, []].forEach((fn) => { - assert.throws(() => tracker.reset(fn), { code: 'ERR_INVALID_ARG_VALUE' }); - }); - }); -}); diff --git a/test/parallel/test-assert-calltracker-report.js b/test/parallel/test-assert-calltracker-report.js deleted file mode 100644 index 87ef0bff17fc0b..00000000000000 --- a/test/parallel/test-assert-calltracker-report.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; -require('../common'); -const assert = require('assert'); - -// This test ensures that the assert.CallTracker.report() works as intended. - -const tracker = new assert.CallTracker(); - -function foo() {} - -const callsfoo = tracker.calls(foo, 1); - -// Ensures that foo was added to the callChecks array. -assert.strictEqual(tracker.report()[0].operator, 'foo'); - -callsfoo(); - -// Ensures that foo was removed from the callChecks array after being called the -// expected number of times. -assert.strictEqual(typeof tracker.report()[0], 'undefined'); - -callsfoo(); - -// Ensures that foo was added back to the callChecks array after being called -// more than the expected number of times. -assert.strictEqual(tracker.report()[0].operator, 'foo'); diff --git a/test/parallel/test-assert-calltracker-verify.js b/test/parallel/test-assert-calltracker-verify.js deleted file mode 100644 index 118f04f7352780..00000000000000 --- a/test/parallel/test-assert-calltracker-verify.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; -require('../common'); -const assert = require('assert'); - -// This test ensures that assert.CallTracker.verify() works as intended. - -const tracker = new assert.CallTracker(); - -const generic_msg = 'Functions were not called the expected number of times'; - -function foo() {} - -function bar() {} - -const callsfoo = tracker.calls(foo, 1); -const callsbar = tracker.calls(bar, 1); - -// Expects an error as callsfoo() and callsbar() were called less than one time. -assert.throws( - () => tracker.verify(), - { message: generic_msg } -); - -callsfoo(); - -// Expects an error as callsbar() was called less than one time. -assert.throws( - () => tracker.verify(), - { message: 'Expected the bar function to be executed 1 time(s) but was executed 0 time(s).' } -); -callsbar(); - -// Will throw an error if callsfoo() and callsbar isn't called exactly once. -tracker.verify(); - -const callsfoobar = tracker.calls(foo, 1); - -callsfoo(); - -// Expects an error as callsfoo() was called more than once and callsfoobar() was called less than one time. -assert.throws( - () => tracker.verify(), - { message: generic_msg } -); - -callsfoobar(); - - -// Expects an error as callsfoo() was called more than once -assert.throws( - () => tracker.verify(), - { message: 'Expected the foo function to be executed 1 time(s) but was executed 2 time(s).' } -); diff --git a/test/parallel/test-runner-assert.js b/test/parallel/test-runner-assert.js index 74384947278e4e..2c495baca0afd2 100644 --- a/test/parallel/test-runner-assert.js +++ b/test/parallel/test-runner-assert.js @@ -6,7 +6,6 @@ const test = require('node:test'); test('expected methods are on t.assert', (t) => { const uncopiedKeys = [ 'AssertionError', - 'CallTracker', 'strict', ]; const assertKeys = Object.keys(assert).filter((key) => !uncopiedKeys.includes(key)); diff --git a/test/parallel/test-whatwg-webstreams-transfer.js b/test/parallel/test-whatwg-webstreams-transfer.js index 7be01c339652c0..505126125ffe00 100644 --- a/test/parallel/test-whatwg-webstreams-transfer.js +++ b/test/parallel/test-whatwg-webstreams-transfer.js @@ -459,10 +459,7 @@ const theData = 'hello'; const assert = require('assert'); - const tracker = new assert.CallTracker(); - process.on('exit', () => { - tracker.verify(); - }); + const { mock } = require('node:test'); // We create an interval to keep the event loop alive while // we wait for the stream read to complete. The reason this is needed is because there's @@ -474,16 +471,25 @@ const theData = 'hello'; // from terminating at all unless the stream was consumed/closed. const i = setInterval(() => {}, 1000); - parentPort.onmessage = tracker.calls(({ data }) => { + const innercb = mock.fn((result) => { + assert(!result.done); + assert(result.value instanceof Uint8Array); + clearInterval(i); + }); + + const cb = mock.fn(({ data }) => { assert(isReadableStream(data)); const reader = data.getReader(); - reader.read().then(tracker.calls((result) => { - assert(!result.done); - assert(result.value instanceof Uint8Array); - clearInterval(i); - })); + reader.read().then(innercb); parentPort.close(); }); + + process.on('exit', () => { + assert.strictEqual(innercb.mock.callCount(), 1); + assert.strictEqual(cb.mock.callCount(), 1); + }); + + parentPort.onmessage = cb; parentPort.onmessageerror = () => assert.fail('should not be called'); `, { eval: true });