Skip to content

Commit d1ac02f

Browse files
DeveloperVirajRenegade334
authored andcommitted
test_runner: add mock-timers support for AbortSignal.timeout
PR-URL: #60751 Fixes: #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>
1 parent 045febc commit d1ac02f

2 files changed

Lines changed: 70 additions & 5 deletions

File tree

lib/internal/test_runner/mock/mock_timers.js

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
validateAbortSignal,
2424
validateNumber,
2525
validateStringArray,
26+
validateUint32,
2627
} = require('internal/validators');
2728

2829
const {
@@ -34,6 +35,7 @@ const {
3435
} = require('internal/errors');
3536

3637
const { addAbortListener } = require('internal/events/abort_listener');
38+
const { AbortController, AbortSignal } = require('internal/abort_controller');
3739

3840
const { TIMEOUT_MAX } = require('internal/timers');
3941

@@ -60,9 +62,17 @@ function abortIt(signal) {
6062
}
6163

6264
/**
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+
];
6676
const TIMERS_DEFAULT_INTERVAL = {
6777
__proto__: null,
6878
setImmediate: -1,
@@ -115,6 +125,7 @@ class MockTimers {
115125
#realPromisifiedSetImmediate;
116126

117127
#nativeDateDescriptor;
128+
#realAbortSignalTimeout;
118129

119130
#timersInContext = [];
120131
#isEnabled = false;
@@ -297,6 +308,14 @@ class MockTimers {
297308
);
298309
}
299310

311+
#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) {
301320
if (delay > TIMEOUT_MAX) {
302321
delay = 1;
@@ -604,6 +623,27 @@ class MockTimers {
604623
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
605624
globalThis.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
},
608648
toReal: {
609649
'__proto__': null,
@@ -622,6 +662,9 @@ class MockTimers {
622662
'Date': () => {
623663
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
624664
},
665+
'AbortSignal.timeout': () => {
666+
this.#restoreOriginalAbortSignalTimeout();
667+
},
625668
},
626669
};
627670

@@ -664,10 +707,11 @@ class MockTimers {
664707
}
665708

666709
/**
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 {
686730

687731
internalOptions.apis ||= SUPPORTED_APIS;
688732

689-
validateStringArray(internalOptions.apis, 'options.apis');
690733
// Check that the timers passed are supported
734+
validateStringArray(internalOptions.apis, 'options.apis');
691735
ArrayPrototypeForEach(internalOptions.apis, (timer) => {
692736
if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) {
693737
throw new ERR_INVALID_ARG_VALUE(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const { mock } = require('node:test');
6+
7+
const originalAbortSignalTimeout = AbortSignal.timeout;
8+
9+
mock.timers.enable({ apis: ['AbortSignal.timeout'] });
10+
11+
const signal = AbortSignal.timeout(50);
12+
assert.strictEqual(signal.aborted, false);
13+
14+
mock.timers.tick(49);
15+
assert.strictEqual(signal.aborted, false);
16+
17+
mock.timers.tick(1);
18+
assert.strictEqual(signal.aborted, true);
19+
20+
mock.timers.reset();
21+
assert.strictEqual(AbortSignal.timeout, originalAbortSignalTimeout);

0 commit comments

Comments
 (0)