test_runner: add mock-timers support for AbortSignal.timeout · nodejs/node@01a9552
@@ -23,6 +23,7 @@ const {
2323 validateAbortSignal,
2424 validateNumber,
2525 validateStringArray,
26+ validateUint32,
2627} = require('internal/validators');
27282829const {
@@ -34,6 +35,7 @@ const {
3435} = require('internal/errors');
35363637const { addAbortListener } = require('internal/events/abort_listener');
38+const { AbortController, AbortSignal } = require('internal/abort_controller');
37393840const { TIMEOUT_MAX } = require('internal/timers');
3941@@ -60,9 +62,17 @@ function abortIt(signal) {
6062}
61636264/**
63- * @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait')[]} Supported timers
65+ * @typedef {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait'|'AbortSignal.timeout')[]} SupportedApis
66+ * Supported timers that can be enabled via MockTimers.enable({ apis: [...] })
6467 */
65-const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait'];
68+const SUPPORTED_APIS = [
69+'setTimeout',
70+'setInterval',
71+'setImmediate',
72+'Date',
73+'scheduler.wait',
74+'AbortSignal.timeout',
75+];
6676const TIMERS_DEFAULT_INTERVAL = {
6777__proto__: null,
6878setImmediate: -1,
@@ -115,6 +125,7 @@ class MockTimers {
115125 #realPromisifiedSetImmediate;
116126117127 #nativeDateDescriptor;
128+ #realAbortSignalTimeout;
118129119130 #timersInContext = [];
120131 #isEnabled = false;
@@ -297,6 +308,14 @@ class MockTimers {
297308);
298309}
299310311+ #storeOriginalAbortSignalTimeout() {
312+this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout');
313+}
314+315+ #restoreOriginalAbortSignalTimeout() {
316+ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout);
317+}
318+300319 #createTimer(isInterval, callback, delay, ...args) {
301320if (delay > TIMEOUT_MAX) {
302321delay = 1;
@@ -604,6 +623,27 @@ class MockTimers {
604623this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
605624globalThis.Date = this.#createDate();
606625},
626+'AbortSignal.timeout': () => {
627+this.#storeOriginalAbortSignalTimeout();
628+const mock = this;
629+ObjectDefineProperty(AbortSignal, 'timeout', {
630+__proto__: null,
631+configurable: true,
632+writable: true,
633+value: function value(delay) {
634+validateUint32(delay, 'delay', false);
635+const controller = new AbortController();
636+// Don't keep an unused binding to the timer; mock tick controls it
637+mock.#setTimeout(
638+() => {
639+controller.abort();
640+},
641+delay,
642+);
643+return controller.signal;
644+},
645+});
646+},
607647},
608648toReal: {
609649'__proto__': null,
@@ -622,6 +662,9 @@ class MockTimers {
622662'Date': () => {
623663ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
624664},
665+'AbortSignal.timeout': () => {
666+this.#restoreOriginalAbortSignalTimeout();
667+},
625668},
626669};
627670@@ -664,10 +707,11 @@ class MockTimers {
664707}
665708666709/**
667- * @typedef {{apis: SUPPORTED_APIS;now: number | Date;}} EnableOptions Options to enable the timers
668- * @property {SUPPORTED_APIS} apis List of timers to enable, defaults to all
710+ * @typedef {{apis: SupportedApis;now: number | Date;}} EnableOptions Options to enable the timers
711+ * @property {SupportedApis} apis List of timers to enable, defaults to all
669712 * @property {number | Date} now The epoch to which the timers should be set to, defaults to 0
670713 */
714+671715/**
672716 * Enables the MockTimers replacing the native timers with the fake ones.
673717 * @param {EnableOptions} [options]
@@ -686,8 +730,8 @@ class MockTimers {
686730687731internalOptions.apis ||= SUPPORTED_APIS;
688732689-validateStringArray(internalOptions.apis, 'options.apis');
690733// Check that the timers passed are supported
734+validateStringArray(internalOptions.apis, 'options.apis');
691735ArrayPrototypeForEach(internalOptions.apis, (timer) => {
692736if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) {
693737throw new ERR_INVALID_ARG_VALUE(