From ef06137b65d157abcaaba966a3b13a241f6e7a05 Mon Sep 17 00:00:00 2001 From: DeveloperViraj Date: Sun, 16 Nov 2025 23:16:02 +0530 Subject: [PATCH 1/4] 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(); + } +} From fb02de73deaa1b914a1acb7505b3593a31ec2d95 Mon Sep 17 00:00:00 2001 From: DeveloperViraj Date: Mon, 1 Dec 2025 15:55:34 +0530 Subject: [PATCH 2/4] test_runner: revert EnableOptions typedef change and update AbortSignal.timeout test --- lib/internal/test_runner/mock/mock_timers.js | 7 +++---- .../test-mock-timers-abortsignal-timeout.js | 15 ++++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index b6aa5078f3c948..6e24317e627cc8 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -711,10 +711,9 @@ class MockTimers { } /** - * 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 + * @typedef {{apis: SupportedApis;now: number | Date;}} EnableOptions Options to enable the timers + * @property {SupportedApis} 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 */ /** diff --git a/test/parallel/test-mock-timers-abortsignal-timeout.js b/test/parallel/test-mock-timers-abortsignal-timeout.js index 598855070a39d5..867b5820366bef 100644 --- a/test/parallel/test-mock-timers-abortsignal-timeout.js +++ b/test/parallel/test-mock-timers-abortsignal-timeout.js @@ -1,26 +1,23 @@ 'use strict'; -// eslint-disable-next-line no-unused-vars -const common = require('../common'); +require('../common'); const assert = require('assert'); -const { MockTimers } = require('internal/test_runner/mock/mock_timers'); -const { AbortSignal } = require('internal/abort_controller'); +const { mock } = require('node:test'); { - const mock = new MockTimers(); - mock.enable({ apis: ['AbortSignal.timeout'] }); + mock.timers.enable({ apis: ['AbortSignal.timeout'] }); try { const signal = AbortSignal.timeout(50); assert.strictEqual(signal.aborted, false); - mock.tick(49); + mock.timers.tick(49); assert.strictEqual(signal.aborted, false); - mock.tick(1); + mock.timers.tick(1); assert.strictEqual(signal.aborted, true); } finally { - mock.reset(); + mock.timers.reset(); } } From 9ffc42dd8217dcde071f3c757a5493dc17ddfd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Mon, 20 Apr 2026 16:40:51 +0100 Subject: [PATCH 3/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: René --- lib/internal/test_runner/mock/mock_timers.js | 6 +--- .../test-mock-timers-abortsignal-timeout.js | 32 +++++++++---------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 6e24317e627cc8..2923b89814e4d7 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -313,11 +313,7 @@ class MockTimers { } #restoreOriginalAbortSignalTimeout() { - if (this.#realAbortSignalTimeout) { - ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout); - } else { - delete AbortSignal.timeout; - } + ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout); } #createTimer(isInterval, callback, delay, ...args) { diff --git a/test/parallel/test-mock-timers-abortsignal-timeout.js b/test/parallel/test-mock-timers-abortsignal-timeout.js index 867b5820366bef..1971e7f5ecc5eb 100644 --- a/test/parallel/test-mock-timers-abortsignal-timeout.js +++ b/test/parallel/test-mock-timers-abortsignal-timeout.js @@ -4,20 +4,18 @@ require('../common'); const assert = require('assert'); const { mock } = require('node:test'); -{ - mock.timers.enable({ apis: ['AbortSignal.timeout'] }); - - try { - const signal = AbortSignal.timeout(50); - - assert.strictEqual(signal.aborted, false); - - mock.timers.tick(49); - assert.strictEqual(signal.aborted, false); - - mock.timers.tick(1); - assert.strictEqual(signal.aborted, true); - } finally { - mock.timers.reset(); - } -} +const originalAbortSignalTimeout = AbortSignal.timeout; + +mock.timers.enable({ apis: ['AbortSignal.timeout'] }); + +const signal = AbortSignal.timeout(50); +assert.strictEqual(signal.aborted, false); + +mock.timers.tick(49); +assert.strictEqual(signal.aborted, false); + +mock.timers.tick(1); +assert.strictEqual(signal.aborted, true); + +mock.timers.reset(); +assert.strictEqual(AbortSignal.timeout, originalAbortSignalTimeout); From 177ab113b758370293d6eed19d7824fcafb93e7f Mon Sep 17 00:00:00 2001 From: Renegade334 Date: Mon, 20 Apr 2026 20:11:06 +0200 Subject: [PATCH 4/4] remove CRLF introduced by GitHub code review --- .../test-mock-timers-abortsignal-timeout.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/parallel/test-mock-timers-abortsignal-timeout.js b/test/parallel/test-mock-timers-abortsignal-timeout.js index 1971e7f5ecc5eb..242cb3093756e1 100644 --- a/test/parallel/test-mock-timers-abortsignal-timeout.js +++ b/test/parallel/test-mock-timers-abortsignal-timeout.js @@ -4,18 +4,18 @@ require('../common'); const assert = require('assert'); const { mock } = require('node:test'); -const originalAbortSignalTimeout = AbortSignal.timeout; - -mock.timers.enable({ apis: ['AbortSignal.timeout'] }); - -const signal = AbortSignal.timeout(50); -assert.strictEqual(signal.aborted, false); - -mock.timers.tick(49); -assert.strictEqual(signal.aborted, false); - -mock.timers.tick(1); -assert.strictEqual(signal.aborted, true); - -mock.timers.reset(); -assert.strictEqual(AbortSignal.timeout, originalAbortSignalTimeout); +const originalAbortSignalTimeout = AbortSignal.timeout; + +mock.timers.enable({ apis: ['AbortSignal.timeout'] }); + +const signal = AbortSignal.timeout(50); +assert.strictEqual(signal.aborted, false); + +mock.timers.tick(49); +assert.strictEqual(signal.aborted, false); + +mock.timers.tick(1); +assert.strictEqual(signal.aborted, true); + +mock.timers.reset(); +assert.strictEqual(AbortSignal.timeout, originalAbortSignalTimeout);