Skip to content

Commit 42e1379

Browse files
test_runner: fix AbortSignal.timeout to be mock-timers compatible; add regression test
1 parent 3330e5c commit 42e1379

File tree

2 files changed

+90
-8
lines changed

2 files changed

+90
-8
lines changed

lib/internal/test_runner/mock/mock_timers.js

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
} = require('internal/errors');
3535

3636
const { addAbortListener } = require('internal/events/abort_listener');
37+
const { AbortController, AbortSignal } = require('internal/abort_controller');
3738

3839
const { TIMEOUT_MAX } = require('internal/timers');
3940

@@ -59,10 +60,14 @@ function abortIt(signal) {
5960
return new AbortError(undefined, { __proto__: null, cause: signal.reason });
6061
}
6162

62-
/**
63-
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait')[]} Supported timers
64-
*/
65-
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait'];
63+
const SUPPORTED_APIS = [
64+
'setTimeout',
65+
'setInterval',
66+
'setImmediate',
67+
'Date',
68+
'scheduler.wait',
69+
'AbortSignal.timeout',
70+
];
6671
const TIMERS_DEFAULT_INTERVAL = {
6772
__proto__: null,
6873
setImmediate: -1,
@@ -115,6 +120,7 @@ class MockTimers {
115120
#realPromisifiedSetImmediate;
116121

117122
#nativeDateDescriptor;
123+
#realAbortSignalTimeout;
118124

119125
#timersInContext = [];
120126
#isEnabled = false;
@@ -297,6 +303,18 @@ class MockTimers {
297303
);
298304
}
299305

306+
#storeOriginalAbortSignalTimeout() {
307+
this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout');
308+
}
309+
310+
#restoreOriginalAbortSignalTimeout() {
311+
if (this.#realAbortSignalTimeout) {
312+
ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout);
313+
} else {
314+
delete AbortSignal.timeout;
315+
}
316+
}
317+
300318
#createTimer(isInterval, callback, delay, ...args) {
301319
if (delay > TIMEOUT_MAX) {
302320
delay = 1;
@@ -604,6 +622,28 @@ class MockTimers {
604622
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
605623
globalThis.Date = this.#createDate();
606624
},
625+
'AbortSignal.timeout': () => {
626+
this.#storeOriginalAbortSignalTimeout();
627+
const mock = this;
628+
ObjectDefineProperty(AbortSignal, 'timeout', {
629+
__proto__: null,
630+
configurable: true,
631+
writable: true,
632+
value: function mockableAbortSignalTimeout(delay) {
633+
if (NumberIsNaN(delay)) {
634+
throw new ERR_INVALID_ARG_VALUE('delay', delay, 'delay must be a number');
635+
}
636+
const controller = new AbortController();
637+
const timer = 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,13 @@ 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
669-
* @property {number | Date} now The epoch to which the timers should be set to, defaults to 0
710+
* EnableOptions type:
711+
*
712+
* @typedef {Object} EnableOptions
713+
* @property {Array<string>} apis List of timers to enable, defaults to all
714+
* @property {number|Date} now The epoch to which the timers should be set, defaults to 0
670715
*/
716+
671717
/**
672718
* Enables the MockTimers replacing the native timers with the fake ones.
673719
* @param {EnableOptions} [options]
@@ -686,8 +732,8 @@ class MockTimers {
686732

687733
internalOptions.apis ||= SUPPORTED_APIS;
688734

689-
validateStringArray(internalOptions.apis, 'options.apis');
690735
// Check that the timers passed are supported
736+
validateStringArray(internalOptions.apis, 'options.apis');
691737
ArrayPrototypeForEach(internalOptions.apis, (timer) => {
692738
if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) {
693739
throw new ERR_INVALID_ARG_VALUE(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const { MockTimers } = require('internal/test_runner/mock/mock_timers');
5+
const { AbortSignal } = require('internal/abort_controller');
6+
7+
{
8+
const mock = new MockTimers();
9+
mock.enable({ apis: ['AbortSignal.timeout'] });
10+
11+
try {
12+
const signal = AbortSignal.timeout(50);
13+
14+
assert.strictEqual(
15+
signal.aborted,
16+
false,
17+
'signal should not be aborted initially'
18+
);
19+
20+
mock.tick(49);
21+
assert.strictEqual(
22+
signal.aborted,
23+
false,
24+
'signal should not be aborted after 49ms'
25+
);
26+
27+
mock.tick(1);
28+
assert.strictEqual(
29+
signal.aborted,
30+
true,
31+
'signal should be aborted after total 50ms'
32+
);
33+
} finally {
34+
mock.reset();
35+
}
36+
}

0 commit comments

Comments
 (0)