mirror of
https://github.com/nodejs/node.git
synced 2025-04-28 13:40:37 +00:00

PR-URL: https://github.com/nodejs/node/pull/55044 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name>
770 lines
21 KiB
JavaScript
770 lines
21 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayPrototypeAt,
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeIncludes,
|
|
DatePrototypeGetTime,
|
|
DatePrototypeToString,
|
|
FunctionPrototypeApply,
|
|
FunctionPrototypeBind,
|
|
FunctionPrototypeToString,
|
|
NumberIsNaN,
|
|
ObjectDefineProperties,
|
|
ObjectDefineProperty,
|
|
ObjectGetOwnPropertyDescriptor,
|
|
ObjectGetOwnPropertyDescriptors,
|
|
Promise,
|
|
Symbol,
|
|
SymbolAsyncIterator,
|
|
SymbolDispose,
|
|
globalThis,
|
|
} = primordials;
|
|
|
|
const {
|
|
validateAbortSignal,
|
|
validateNumber,
|
|
validateStringArray,
|
|
} = require('internal/validators');
|
|
|
|
const {
|
|
emitExperimentalWarning,
|
|
} = require('internal/util');
|
|
const {
|
|
AbortError,
|
|
codes: {
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_STATE,
|
|
},
|
|
} = require('internal/errors');
|
|
|
|
const PriorityQueue = require('internal/priority_queue');
|
|
const nodeTimers = require('timers');
|
|
const nodeTimersPromises = require('timers/promises');
|
|
const EventEmitter = require('events');
|
|
|
|
let kResistStopPropagation;
|
|
// Internal reference to the MockTimers class inside MockDate
|
|
let kMock;
|
|
// Initial epoch to which #now should be set to
|
|
const kInitialEpoch = 0;
|
|
|
|
function compareTimersLists(a, b) {
|
|
return (a.runAt - b.runAt) || (a.id - b.id);
|
|
}
|
|
|
|
function setPosition(node, pos) {
|
|
node.priorityQueuePosition = pos;
|
|
}
|
|
|
|
function abortIt(signal) {
|
|
return new AbortError(undefined, { __proto__: null, cause: signal.reason });
|
|
}
|
|
|
|
/**
|
|
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date')[]} Supported timers
|
|
*/
|
|
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date'];
|
|
const TIMERS_DEFAULT_INTERVAL = {
|
|
__proto__: null,
|
|
setImmediate: -1,
|
|
};
|
|
|
|
class Timeout {
|
|
constructor(opts) {
|
|
this.id = opts.id;
|
|
this.callback = opts.callback;
|
|
this.runAt = opts.runAt;
|
|
this.interval = opts.interval;
|
|
this.args = opts.args;
|
|
}
|
|
|
|
hasRef() {
|
|
return true;
|
|
}
|
|
|
|
ref() {
|
|
return this;
|
|
}
|
|
|
|
unref() {
|
|
return this;
|
|
}
|
|
|
|
refresh() {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
class MockTimers {
|
|
#realSetTimeout;
|
|
#realClearTimeout;
|
|
#realSetInterval;
|
|
#realClearInterval;
|
|
#realSetImmediate;
|
|
#realClearImmediate;
|
|
|
|
#realPromisifiedSetTimeout;
|
|
#realPromisifiedSetInterval;
|
|
|
|
#realTimersSetTimeout;
|
|
#realTimersClearTimeout;
|
|
#realTimersSetInterval;
|
|
#realTimersClearInterval;
|
|
#realTimersSetImmediate;
|
|
#realTimersClearImmediate;
|
|
#realPromisifiedSetImmediate;
|
|
|
|
#nativeDateDescriptor;
|
|
|
|
#timersInContext = [];
|
|
#isEnabled = false;
|
|
#currentTimer = 1;
|
|
#now = kInitialEpoch;
|
|
|
|
#executionQueue = new PriorityQueue(compareTimersLists, setPosition);
|
|
|
|
#setTimeout = FunctionPrototypeBind(this.#createTimer, this, false);
|
|
#clearTimeout = FunctionPrototypeBind(this.#clearTimer, this);
|
|
#setInterval = FunctionPrototypeBind(this.#createTimer, this, true);
|
|
#clearInterval = FunctionPrototypeBind(this.#clearTimer, this);
|
|
#clearImmediate = FunctionPrototypeBind(this.#clearTimer, this);
|
|
|
|
constructor() {
|
|
emitExperimentalWarning('The MockTimers API');
|
|
}
|
|
|
|
#restoreSetImmediate() {
|
|
ObjectDefineProperty(
|
|
globalThis,
|
|
'setImmediate',
|
|
this.#realSetImmediate,
|
|
);
|
|
ObjectDefineProperty(
|
|
globalThis,
|
|
'clearImmediate',
|
|
this.#realClearImmediate,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimers,
|
|
'setImmediate',
|
|
this.#realTimersSetImmediate,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimers,
|
|
'clearImmediate',
|
|
this.#realTimersClearImmediate,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimersPromises,
|
|
'setImmediate',
|
|
this.#realPromisifiedSetImmediate,
|
|
);
|
|
}
|
|
|
|
#restoreOriginalSetInterval() {
|
|
ObjectDefineProperty(
|
|
globalThis,
|
|
'setInterval',
|
|
this.#realSetInterval,
|
|
);
|
|
ObjectDefineProperty(
|
|
globalThis,
|
|
'clearInterval',
|
|
this.#realClearInterval,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimers,
|
|
'setInterval',
|
|
this.#realTimersSetInterval,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimers,
|
|
'clearInterval',
|
|
this.#realTimersClearInterval,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimersPromises,
|
|
'setInterval',
|
|
this.#realPromisifiedSetInterval,
|
|
);
|
|
}
|
|
|
|
#restoreOriginalSetTimeout() {
|
|
ObjectDefineProperty(
|
|
globalThis,
|
|
'setTimeout',
|
|
this.#realSetTimeout,
|
|
);
|
|
ObjectDefineProperty(
|
|
globalThis,
|
|
'clearTimeout',
|
|
this.#realClearTimeout,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimers,
|
|
'setTimeout',
|
|
this.#realTimersSetTimeout,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimers,
|
|
'clearTimeout',
|
|
this.#realTimersClearTimeout,
|
|
);
|
|
ObjectDefineProperty(
|
|
nodeTimersPromises,
|
|
'setTimeout',
|
|
this.#realPromisifiedSetTimeout,
|
|
);
|
|
}
|
|
|
|
#storeOriginalSetImmediate() {
|
|
this.#realSetImmediate = ObjectGetOwnPropertyDescriptor(
|
|
globalThis,
|
|
'setImmediate',
|
|
);
|
|
this.#realClearImmediate = ObjectGetOwnPropertyDescriptor(
|
|
globalThis,
|
|
'clearImmediate',
|
|
);
|
|
this.#realTimersSetImmediate = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimers,
|
|
'setImmediate',
|
|
);
|
|
this.#realTimersClearImmediate = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimers,
|
|
'clearImmediate',
|
|
);
|
|
this.#realPromisifiedSetImmediate = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimersPromises,
|
|
'setImmediate',
|
|
);
|
|
}
|
|
|
|
#storeOriginalSetInterval() {
|
|
this.#realSetInterval = ObjectGetOwnPropertyDescriptor(
|
|
globalThis,
|
|
'setInterval',
|
|
);
|
|
this.#realClearInterval = ObjectGetOwnPropertyDescriptor(
|
|
globalThis,
|
|
'clearInterval',
|
|
);
|
|
this.#realTimersSetInterval = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimers,
|
|
'setInterval',
|
|
);
|
|
this.#realTimersClearInterval = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimers,
|
|
'clearInterval',
|
|
);
|
|
this.#realPromisifiedSetInterval = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimersPromises,
|
|
'setInterval',
|
|
);
|
|
}
|
|
|
|
#storeOriginalSetTimeout() {
|
|
this.#realSetTimeout = ObjectGetOwnPropertyDescriptor(
|
|
globalThis,
|
|
'setTimeout',
|
|
);
|
|
this.#realClearTimeout = ObjectGetOwnPropertyDescriptor(
|
|
globalThis,
|
|
'clearTimeout',
|
|
);
|
|
this.#realTimersSetTimeout = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimers,
|
|
'setTimeout',
|
|
);
|
|
this.#realTimersClearTimeout = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimers,
|
|
'clearTimeout',
|
|
);
|
|
this.#realPromisifiedSetTimeout = ObjectGetOwnPropertyDescriptor(
|
|
nodeTimersPromises,
|
|
'setTimeout',
|
|
);
|
|
}
|
|
|
|
#createTimer(isInterval, callback, delay, ...args) {
|
|
const timerId = this.#currentTimer++;
|
|
const opts = {
|
|
__proto__: null,
|
|
id: timerId,
|
|
callback,
|
|
runAt: this.#now + delay,
|
|
interval: isInterval ? delay : undefined,
|
|
args,
|
|
};
|
|
|
|
const timer = new Timeout(opts);
|
|
this.#executionQueue.insert(timer);
|
|
return timer;
|
|
}
|
|
|
|
#clearTimer(timer) {
|
|
if (timer?.priorityQueuePosition !== undefined) {
|
|
this.#executionQueue.removeAt(timer.priorityQueuePosition);
|
|
timer.priorityQueuePosition = undefined;
|
|
}
|
|
}
|
|
|
|
#createDate() {
|
|
kMock ??= Symbol('MockTimers');
|
|
const NativeDateConstructor = this.#nativeDateDescriptor.value;
|
|
/**
|
|
* Function to mock the Date constructor, treats cases as per ECMA-262
|
|
* and returns a Date object with a mocked implementation
|
|
* @typedef {Date} MockDate
|
|
* @returns {MockDate} a mocked Date object
|
|
*/
|
|
function MockDate(year, month, date, hours, minutes, seconds, ms) {
|
|
const mockTimersSource = MockDate[kMock];
|
|
const nativeDate = mockTimersSource.#nativeDateDescriptor.value;
|
|
|
|
// As of the fake-timers implementation for Sinon
|
|
// ref https://github.com/sinonjs/fake-timers/blob/a4c757f80840829e45e0852ea1b17d87a998388e/src/fake-timers-src.js#L456
|
|
// This covers the Date constructor called as a function ref.
|
|
// ECMA-262 Edition 5.1 section 15.9.2.
|
|
// and ECMA-262 Edition 14 Section 21.4.2.1
|
|
// replaces 'this instanceof MockDate' with a more reliable check
|
|
// from ECMA-262 Edition 14 Section 13.3.12.1 NewTarget
|
|
if (!new.target) {
|
|
return DatePrototypeToString(new nativeDate(mockTimersSource.#now));
|
|
}
|
|
|
|
// Cases where Date is called as a constructor
|
|
// This is intended as a defensive implementation to avoid
|
|
// having unexpected returns
|
|
switch (arguments.length) {
|
|
case 0:
|
|
return new nativeDate(MockDate[kMock].#now);
|
|
case 1:
|
|
return new nativeDate(year);
|
|
case 2:
|
|
return new nativeDate(year, month);
|
|
case 3:
|
|
return new nativeDate(year, month, date);
|
|
case 4:
|
|
return new nativeDate(year, month, date, hours);
|
|
case 5:
|
|
return new nativeDate(year, month, date, hours, minutes);
|
|
case 6:
|
|
return new nativeDate(year, month, date, hours, minutes, seconds);
|
|
default:
|
|
return new nativeDate(year, month, date, hours, minutes, seconds, ms);
|
|
}
|
|
}
|
|
|
|
// Prototype is read-only, and non assignable through Object.defineProperties
|
|
// eslint-disable-next-line no-unused-vars -- used to get the prototype out of the object
|
|
const { prototype, ...dateProps } = ObjectGetOwnPropertyDescriptors(NativeDateConstructor);
|
|
|
|
// Binds all the properties of Date to the MockDate function
|
|
ObjectDefineProperties(
|
|
MockDate,
|
|
dateProps,
|
|
);
|
|
|
|
MockDate.now = function now() {
|
|
return MockDate[kMock].#now;
|
|
};
|
|
|
|
// This is just to print the function { native code } in the console
|
|
// when the user prints the function and not the internal code
|
|
MockDate.toString = function toString() {
|
|
return FunctionPrototypeToString(MockDate[kMock].#nativeDateDescriptor.value);
|
|
};
|
|
|
|
// We need to pollute the prototype of this
|
|
ObjectDefineProperties(MockDate, {
|
|
__proto__: null,
|
|
[kMock]: {
|
|
__proto__: null,
|
|
enumerable: false,
|
|
configurable: false,
|
|
writable: false,
|
|
value: this,
|
|
},
|
|
|
|
isMock: {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false,
|
|
value: true,
|
|
},
|
|
});
|
|
|
|
MockDate.prototype = NativeDateConstructor.prototype;
|
|
MockDate.parse = NativeDateConstructor.parse;
|
|
MockDate.UTC = NativeDateConstructor.UTC;
|
|
MockDate.prototype.toUTCString = NativeDateConstructor.prototype.toUTCString;
|
|
return MockDate;
|
|
}
|
|
|
|
async * #setIntervalPromisified(interval, result, options) {
|
|
const context = this;
|
|
const emitter = new EventEmitter();
|
|
if (options?.signal) {
|
|
validateAbortSignal(options.signal, 'options.signal');
|
|
|
|
if (options.signal.aborted) {
|
|
throw abortIt(options.signal);
|
|
}
|
|
|
|
const onAbort = (reason) => {
|
|
emitter.emit('data', { __proto__: null, aborted: true, reason });
|
|
};
|
|
|
|
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
|
options.signal.addEventListener('abort', onAbort, {
|
|
__proto__: null,
|
|
once: true,
|
|
[kResistStopPropagation]: true,
|
|
});
|
|
}
|
|
|
|
const eventIt = EventEmitter.on(emitter, 'data');
|
|
const callback = () => {
|
|
emitter.emit('data', result);
|
|
};
|
|
|
|
const timer = this.#createTimer(true, callback, interval, options);
|
|
const clearListeners = () => {
|
|
emitter.removeAllListeners();
|
|
context.#clearTimer(timer);
|
|
};
|
|
const iterator = {
|
|
__proto__: null,
|
|
[SymbolAsyncIterator]() {
|
|
return this;
|
|
},
|
|
async next() {
|
|
const result = await eventIt.next();
|
|
const value = ArrayPrototypeAt(result.value, 0);
|
|
if (value?.aborted) {
|
|
iterator.return();
|
|
throw abortIt(options.signal);
|
|
}
|
|
|
|
return {
|
|
__proto__: null,
|
|
done: result.done,
|
|
value,
|
|
};
|
|
},
|
|
async return() {
|
|
clearListeners();
|
|
return eventIt.return();
|
|
},
|
|
};
|
|
yield* iterator;
|
|
}
|
|
|
|
#setImmediate(callback, ...args) {
|
|
return this.#createTimer(
|
|
false,
|
|
callback,
|
|
TIMERS_DEFAULT_INTERVAL.setImmediate,
|
|
...args,
|
|
);
|
|
}
|
|
|
|
#promisifyTimer({ timerFn, clearFn, ms, result, options }) {
|
|
return new Promise((resolve, reject) => {
|
|
if (options?.signal) {
|
|
try {
|
|
validateAbortSignal(options.signal, 'options.signal');
|
|
} catch (err) {
|
|
return reject(err);
|
|
}
|
|
|
|
if (options.signal.aborted) {
|
|
return reject(abortIt(options.signal));
|
|
}
|
|
}
|
|
|
|
const onabort = () => {
|
|
clearFn(timer);
|
|
return reject(abortIt(options.signal));
|
|
};
|
|
|
|
const timer = timerFn(() => {
|
|
return resolve(result);
|
|
}, ms);
|
|
|
|
if (options?.signal) {
|
|
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
|
options.signal.addEventListener('abort', onabort, {
|
|
__proto__: null,
|
|
once: true,
|
|
[kResistStopPropagation]: true,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
#setImmediatePromisified(result, options) {
|
|
return this.#promisifyTimer({
|
|
__proto__: null,
|
|
timerFn: FunctionPrototypeBind(this.#setImmediate, this),
|
|
clearFn: FunctionPrototypeBind(this.#clearImmediate, this),
|
|
ms: TIMERS_DEFAULT_INTERVAL.setImmediate,
|
|
result,
|
|
options,
|
|
});
|
|
}
|
|
|
|
#setTimeoutPromisified(ms, result, options) {
|
|
return this.#promisifyTimer({
|
|
__proto__: null,
|
|
timerFn: FunctionPrototypeBind(this.#setTimeout, this),
|
|
clearFn: FunctionPrototypeBind(this.#clearTimeout, this),
|
|
ms,
|
|
result,
|
|
options,
|
|
});
|
|
}
|
|
|
|
#assertTimersAreEnabled() {
|
|
if (!this.#isEnabled) {
|
|
throw new ERR_INVALID_STATE(
|
|
'You should enable MockTimers first by calling the .enable function',
|
|
);
|
|
}
|
|
}
|
|
|
|
#assertTimeArg(time) {
|
|
if (time < 0) {
|
|
throw new ERR_INVALID_ARG_VALUE('time', 'positive integer', time);
|
|
}
|
|
}
|
|
|
|
#isValidDateWithGetTime(maybeDate) {
|
|
// Validation inspired on https://github.com/inspect-js/is-date-object/blob/main/index.js#L3-L11
|
|
try {
|
|
DatePrototypeGetTime(maybeDate);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#toggleEnableTimers(activate) {
|
|
const options = {
|
|
__proto__: null,
|
|
toFake: {
|
|
__proto__: null,
|
|
setTimeout: () => {
|
|
this.#storeOriginalSetTimeout();
|
|
|
|
globalThis.setTimeout = this.#setTimeout;
|
|
globalThis.clearTimeout = this.#clearTimeout;
|
|
|
|
nodeTimers.setTimeout = this.#setTimeout;
|
|
nodeTimers.clearTimeout = this.#clearTimeout;
|
|
|
|
nodeTimersPromises.setTimeout = FunctionPrototypeBind(
|
|
this.#setTimeoutPromisified,
|
|
this,
|
|
);
|
|
},
|
|
setInterval: () => {
|
|
this.#storeOriginalSetInterval();
|
|
|
|
globalThis.setInterval = this.#setInterval;
|
|
globalThis.clearInterval = this.#clearInterval;
|
|
|
|
nodeTimers.setInterval = this.#setInterval;
|
|
nodeTimers.clearInterval = this.#clearInterval;
|
|
|
|
nodeTimersPromises.setInterval = FunctionPrototypeBind(
|
|
this.#setIntervalPromisified,
|
|
this,
|
|
);
|
|
},
|
|
setImmediate: () => {
|
|
this.#storeOriginalSetImmediate();
|
|
|
|
// setImmediate functions needs to bind MockTimers
|
|
// otherwise it will throw an error when called
|
|
// "Receiver must be an instance of MockTimers"
|
|
// because #setImmediate is the only function here
|
|
// that calls #createTimer and it's not bound to MockTimers
|
|
globalThis.setImmediate = FunctionPrototypeBind(
|
|
this.#setImmediate,
|
|
this,
|
|
);
|
|
globalThis.clearImmediate = this.#clearImmediate;
|
|
|
|
nodeTimers.setImmediate = FunctionPrototypeBind(
|
|
this.#setImmediate,
|
|
this,
|
|
);
|
|
nodeTimers.clearImmediate = this.#clearImmediate;
|
|
nodeTimersPromises.setImmediate = FunctionPrototypeBind(
|
|
this.#setImmediatePromisified,
|
|
this,
|
|
);
|
|
},
|
|
Date: () => {
|
|
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
|
|
globalThis.Date = this.#createDate();
|
|
},
|
|
},
|
|
toReal: {
|
|
__proto__: null,
|
|
setTimeout: () => {
|
|
this.#restoreOriginalSetTimeout();
|
|
},
|
|
setInterval: () => {
|
|
this.#restoreOriginalSetInterval();
|
|
},
|
|
setImmediate: () => {
|
|
this.#restoreSetImmediate();
|
|
},
|
|
Date: () => {
|
|
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
|
|
},
|
|
},
|
|
};
|
|
|
|
const target = activate ? options.toFake : options.toReal;
|
|
ArrayPrototypeForEach(this.#timersInContext, (timer) => target[timer]());
|
|
this.#isEnabled = activate;
|
|
}
|
|
|
|
/**
|
|
* Advances the virtual time of MockTimers by the specified duration (in milliseconds).
|
|
* This method simulates the passage of time and triggers any scheduled timers that are due.
|
|
* @param {number} [time=1] - The amount of time (in milliseconds) to advance the virtual time.
|
|
* @throws {ERR_INVALID_STATE} If MockTimers are not enabled.
|
|
* @throws {ERR_INVALID_ARG_VALUE} If a negative time value is provided.
|
|
*/
|
|
tick(time = 1) {
|
|
this.#assertTimersAreEnabled();
|
|
this.#assertTimeArg(time);
|
|
|
|
this.#now += time;
|
|
let timer = this.#executionQueue.peek();
|
|
while (timer) {
|
|
if (timer.runAt > this.#now) break;
|
|
FunctionPrototypeApply(timer.callback, undefined, timer.args);
|
|
|
|
// Check if the timeout was cleared by calling clearTimeout inside its own callback
|
|
const afterCallback = this.#executionQueue.peek();
|
|
if (afterCallback?.id === timer.id) {
|
|
this.#executionQueue.shift();
|
|
timer.priorityQueuePosition = undefined;
|
|
}
|
|
|
|
if (timer.interval !== undefined) {
|
|
timer.runAt += timer.interval;
|
|
this.#executionQueue.insert(timer);
|
|
}
|
|
|
|
timer = this.#executionQueue.peek();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
*/
|
|
/**
|
|
* Enables the MockTimers replacing the native timers with the fake ones.
|
|
* @param {EnableOptions} [options]
|
|
*/
|
|
enable(options = { __proto__: null, apis: SUPPORTED_APIS, now: 0 }) {
|
|
const internalOptions = { __proto__: null, ...options };
|
|
if (this.#isEnabled) {
|
|
throw new ERR_INVALID_STATE('MockTimers is already enabled!');
|
|
}
|
|
|
|
if (NumberIsNaN(internalOptions.now)) {
|
|
throw new ERR_INVALID_ARG_VALUE('now', internalOptions.now, `epoch must be a positive integer received ${internalOptions.now}`);
|
|
}
|
|
|
|
internalOptions.now ||= 0;
|
|
|
|
internalOptions.apis ||= SUPPORTED_APIS;
|
|
|
|
validateStringArray(internalOptions.apis, 'options.apis');
|
|
// Check that the timers passed are supported
|
|
ArrayPrototypeForEach(internalOptions.apis, (timer) => {
|
|
if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) {
|
|
throw new ERR_INVALID_ARG_VALUE(
|
|
'options.apis',
|
|
timer,
|
|
`option ${timer} is not supported`,
|
|
);
|
|
}
|
|
});
|
|
this.#timersInContext = internalOptions.apis;
|
|
|
|
// Checks if the second argument is the initial time
|
|
if (this.#isValidDateWithGetTime(internalOptions.now)) {
|
|
this.#now = DatePrototypeGetTime(internalOptions.now);
|
|
} else if (validateNumber(internalOptions.now, 'initialTime') === undefined) {
|
|
this.#assertTimeArg(internalOptions.now);
|
|
this.#now = internalOptions.now;
|
|
}
|
|
|
|
this.#toggleEnableTimers(true);
|
|
}
|
|
|
|
/**
|
|
* Sets the current time to the given epoch.
|
|
* @param {number} time The epoch to set the current time to.
|
|
*/
|
|
setTime(time = kInitialEpoch) {
|
|
validateNumber(time, 'time');
|
|
this.#assertTimeArg(time);
|
|
this.#assertTimersAreEnabled();
|
|
|
|
this.#now = time;
|
|
}
|
|
|
|
/**
|
|
* An alias for `this.reset()`, allowing the disposal of the `MockTimers` instance.
|
|
*/
|
|
[SymbolDispose]() {
|
|
this.reset();
|
|
}
|
|
|
|
/**
|
|
* Resets MockTimers, disabling any enabled timers and clearing the execution queue.
|
|
* Does nothing if MockTimers are not enabled.
|
|
*/
|
|
reset() {
|
|
// Ignore if not enabled
|
|
if (!this.#isEnabled) return;
|
|
|
|
this.#toggleEnableTimers(false);
|
|
this.#timersInContext = [];
|
|
this.#now = kInitialEpoch;
|
|
|
|
let timer = this.#executionQueue.peek();
|
|
while (timer) {
|
|
this.#executionQueue.shift();
|
|
timer = this.#executionQueue.peek();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs all scheduled timers until there are no more pending timers.
|
|
* @throws {ERR_INVALID_STATE} If MockTimers are not enabled.
|
|
*/
|
|
runAll() {
|
|
this.#assertTimersAreEnabled();
|
|
const longestTimer = this.#executionQueue.peekBottom();
|
|
if (!longestTimer) return;
|
|
this.tick(longestTimer.runAt - this.#now);
|
|
}
|
|
}
|
|
|
|
module.exports = { MockTimers };
|