Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_runner: add mock timers for nodeTimers.promises.scheduler.wait #55244

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions lib/internal/test_runner/mock/mock_timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ function abortIt(signal) {
}

/**
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date')[]} Supported timers
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date', 'scheduler.wait')[]} Supported timers
*/
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date'];
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait'];
const TIMERS_DEFAULT_INTERVAL = {
__proto__: null,
setImmediate: -1,
Expand Down Expand Up @@ -106,6 +106,7 @@ class MockTimers {

#realPromisifiedSetTimeout;
#realPromisifiedSetInterval;
#realTimersPromisifiedSchedulerWait;

#realTimersSetTimeout;
#realTimersClearTimeout;
Expand Down Expand Up @@ -190,6 +191,13 @@ class MockTimers {
);
}

#restoreOriginalSchedulerWait() {
nodeTimersPromises.scheduler.wait = FunctionPrototypeBind(
this.#realTimersPromisifiedSchedulerWait,
this,
);
}

#restoreOriginalSetTimeout() {
ObjectDefineProperty(
globalThis,
Expand Down Expand Up @@ -264,6 +272,14 @@ class MockTimers {
);
}

#storeOriginalSchedulerWait() {

this.#realTimersPromisifiedSchedulerWait = FunctionPrototypeBind(
nodeTimersPromises.scheduler.wait,
this,
);
}

#storeOriginalSetTimeout() {
this.#realSetTimeout = ObjectGetOwnPropertyDescriptor(
globalThis,
Expand Down Expand Up @@ -556,8 +572,14 @@ class MockTimers {
const options = {
__proto__: null,
toFake: {
__proto__: null,
setTimeout: () => {
'__proto__': null,
'scheduler.wait': () => {
this.#storeOriginalSchedulerWait();

nodeTimersPromises.scheduler.wait = (delay, options) =>
this.#setTimeoutPromisified(delay, undefined, options);
},
'setTimeout': () => {
this.#storeOriginalSetTimeout();

globalThis.setTimeout = this.#setTimeout;
Expand All @@ -571,7 +593,7 @@ class MockTimers {
this,
);
},
setInterval: () => {
'setInterval': () => {
this.#storeOriginalSetInterval();

globalThis.setInterval = this.#setInterval;
Expand All @@ -585,7 +607,7 @@ class MockTimers {
this,
);
},
setImmediate: () => {
'setImmediate': () => {
this.#storeOriginalSetImmediate();

// setImmediate functions needs to bind MockTimers
Expand All @@ -609,23 +631,26 @@ class MockTimers {
this,
);
},
Date: () => {
'Date': () => {
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
globalThis.Date = this.#createDate();
},
},
toReal: {
__proto__: null,
setTimeout: () => {
'__proto__': null,
'scheduler.wait': () => {
this.#restoreOriginalSchedulerWait();
},
'setTimeout': () => {
this.#restoreOriginalSetTimeout();
},
setInterval: () => {
'setInterval': () => {
this.#restoreOriginalSetInterval();
},
setImmediate: () => {
'setImmediate': () => {
this.#restoreSetImmediate();
},
Date: () => {
'Date': () => {
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
},
},
Expand Down
120 changes: 120 additions & 0 deletions test/parallel/test-runner-mock-timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,126 @@ describe('Mock Timers Test Suite', () => {
});
});

describe('scheduler Suite', () => {
describe('scheduler.wait', () => {
it('should advance in time and trigger timers when calling the .tick function', (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });

const now = Date.now();
const durationAtMost = 100;

const p = nodeTimersPromises.scheduler.wait(4000);
t.mock.timers.tick(4000);

return p.then(common.mustCall((result) => {
assert.strictEqual(result, undefined);
assert.ok(
Date.now() - now < durationAtMost,
`time should be advanced less than the ${durationAtMost}ms`
);
}));
});

it('should advance in time and trigger timers when calling the .tick function multiple times', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });

const fn = t.mock.fn();

nodeTimersPromises.scheduler.wait(9999).then(fn);

t.mock.timers.tick(8999);
assert.strictEqual(fn.mock.callCount(), 0);
t.mock.timers.tick(500);

await nodeTimersPromises.setImmediate();

assert.strictEqual(fn.mock.callCount(), 0);
t.mock.timers.tick(500);

await nodeTimersPromises.setImmediate();
assert.strictEqual(fn.mock.callCount(), 1);
});

it('should work with the same params as the original timers/promises/scheduler.wait', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

t.mock.timers.tick(1000);
t.mock.timers.tick(500);
t.mock.timers.tick(500);
t.mock.timers.tick(500);

const result = await p;
assert.strictEqual(result, undefined);
});

it('should abort operation if timers/promises/scheduler.wait received an aborted signal', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

t.mock.timers.tick(1000);
controller.abort();
t.mock.timers.tick(500);
t.mock.timers.tick(500);
t.mock.timers.tick(500);

await assert.rejects(() => p, {
name: 'AbortError',
});
});
it('should abort operation even if the .tick was not called', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

controller.abort();

await assert.rejects(() => p, {
name: 'AbortError',
});
});

it('should abort operation when .abort is called before calling setInterval', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
controller.abort();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

await assert.rejects(() => p, {
name: 'AbortError',
});
});

it('should reject given an an invalid signal instance', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: {},
});

await assert.rejects(() => p, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
});
});

});
});

describe('Date Suite', () => {
it('should return the initial UNIX epoch if not specified', (t) => {
t.mock.timers.enable({ apis: ['Date'] });
Expand Down
Loading