test_runner: add mock-timers support for AbortSignal.timeout

PR-URL: https://github.com/nodejs/node/pull/60751
Fixes: https://github.com/nodejs/node/issues/60509
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Jacob Smith <jacob@frende.me>
This commit is contained in:
DeveloperViraj authored and Renegade334 committed 2026-04-22 22:18:48 +02:00
1 parent 045febcecc
commit d1ac02f841
2 files changed
+70 -5

No files matched your search

+49 -5
View File
@@ -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,14 @@ class MockTimers {
);
}
#storeOriginalAbortSignalTimeout() {
this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout');
}
#restoreOriginalAbortSignalTimeout() {
ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout);
}
#createTimer(isInterval, callback, delay, ...args) {
if (delay > TIMEOUT_MAX) {
delay = 1;
@@ -604,6 +623,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 +662,9 @@ class MockTimers {
'Date': () => {
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
},
'AbortSignal.timeout': () => {
this.#restoreOriginalAbortSignalTimeout();
},
},
};
@@ -664,10 +707,11 @@ 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
* @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
*/
/**
* Enables the MockTimers replacing the native timers with the fake ones.
* @param {EnableOptions} [options]
@@ -686,8 +730,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(
@@ -0,0 +1,21 @@
'use strict';
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);