From ef06137b65d157abcaaba966a3b13a241f6e7a05 Mon Sep 17 00:00:00 2001 From: DeveloperViraj Date: Sun, 16 Nov 2025 23:16:02 +0530 Subject: [PATCH] test_runner: add mock-timers support for AbortSignal.timeout --- lib/internal/test_runner/mock/mock_timers.js | 61 +++++++++++++++++-- .../test-mock-timers-abortsignal-timeout.js | 26 ++++++++ 2 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 test/parallel/test-mock-timers-abortsignal-timeout.js diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 490f8cd06fc549..b6aa5078f3c948 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -23,6 +23,7 @@ const { validateAbortSignal, validateNumber, validateStringArray, + validateUint32, } = require('internal/validators'); const { @@ -34,6 +35,7 @@ const { } = require('internal/errors'); const { addAbortListener } = require('internal/events/abort_listener'); +const { AbortController, AbortSignal } = require('internal/abort_controller'); const { TIMEOUT_MAX } = require('internal/timers'); @@ -60,9 +62,17 @@ function abortIt(signal) { } /** - * @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait')[]} Supported timers + * @typedef {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait'|'AbortSignal.timeout')[]} SupportedApis + * Supported timers that can be enabled via MockTimers.enable({ apis: [...] }) */ -const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait']; +const SUPPORTED_APIS = [ + 'setTimeout', + 'setInterval', + 'setImmediate', + 'Date', + 'scheduler.wait', + 'AbortSignal.timeout', +]; const TIMERS_DEFAULT_INTERVAL = { __proto__: null, setImmediate: -1, @@ -115,6 +125,7 @@ class MockTimers { #realPromisifiedSetImmediate; #nativeDateDescriptor; + #realAbortSignalTimeout; #timersInContext = []; #isEnabled = false; @@ -297,6 +308,18 @@ class MockTimers { ); } + #storeOriginalAbortSignalTimeout() { + this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout'); + } + + #restoreOriginalAbortSignalTimeout() { + if (this.#realAbortSignalTimeout) { + ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout); + } else { + delete AbortSignal.timeout; + } + } + #createTimer(isInterval, callback, delay, ...args) { if (delay > TIMEOUT_MAX) { delay = 1; @@ -604,6 +627,27 @@ class MockTimers { this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date'); globalThis.Date = this.#createDate(); }, + 'AbortSignal.timeout': () => { + this.#storeOriginalAbortSignalTimeout(); + const mock = this; + ObjectDefineProperty(AbortSignal, 'timeout', { + __proto__: null, + configurable: true, + writable: true, + value: function value(delay) { + validateUint32(delay, 'delay', false); + const controller = new AbortController(); + // Don't keep an unused binding to the timer; mock tick controls it + mock.#setTimeout( + () => { + controller.abort(); + }, + delay, + ); + return controller.signal; + }, + }); + }, }, toReal: { '__proto__': null, @@ -622,6 +666,9 @@ class MockTimers { 'Date': () => { ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor); }, + 'AbortSignal.timeout': () => { + this.#restoreOriginalAbortSignalTimeout(); + }, }, }; @@ -664,10 +711,12 @@ class MockTimers { } /** - * @typedef {{apis: SUPPORTED_APIS;now: number | Date;}} EnableOptions Options to enable the timers - * @property {SUPPORTED_APIS} apis List of timers to enable, defaults to all - * @property {number | Date} now The epoch to which the timers should be set to, defaults to 0 + * EnableOptions type: + * @typedef {object} EnableOptions + * @property {Array} apis List of timers to enable, defaults to all + * @property {number|Date} now The epoch to which the timers should be set, defaults to 0 */ + /** * Enables the MockTimers replacing the native timers with the fake ones. * @param {EnableOptions} [options] @@ -686,8 +735,8 @@ class MockTimers { internalOptions.apis ||= SUPPORTED_APIS; - validateStringArray(internalOptions.apis, 'options.apis'); // Check that the timers passed are supported + validateStringArray(internalOptions.apis, 'options.apis'); ArrayPrototypeForEach(internalOptions.apis, (timer) => { if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) { throw new ERR_INVALID_ARG_VALUE( diff --git a/test/parallel/test-mock-timers-abortsignal-timeout.js b/test/parallel/test-mock-timers-abortsignal-timeout.js new file mode 100644 index 00000000000000..598855070a39d5 --- /dev/null +++ b/test/parallel/test-mock-timers-abortsignal-timeout.js @@ -0,0 +1,26 @@ +'use strict'; + +// eslint-disable-next-line no-unused-vars +const common = require('../common'); +const assert = require('assert'); +const { MockTimers } = require('internal/test_runner/mock/mock_timers'); +const { AbortSignal } = require('internal/abort_controller'); + +{ + const mock = new MockTimers(); + mock.enable({ apis: ['AbortSignal.timeout'] }); + + try { + const signal = AbortSignal.timeout(50); + + assert.strictEqual(signal.aborted, false); + + mock.tick(49); + assert.strictEqual(signal.aborted, false); + + mock.tick(1); + assert.strictEqual(signal.aborted, true); + } finally { + mock.reset(); + } +}