Skip to content

Commit

Permalink
test_runner: add support for scheduler.wait on mock timers
Browse files Browse the repository at this point in the history
This adds support for nodetimers.promises.scheduler.wait on Mocktimers

Refs: nodejs#55244
PR-URL: nodejs#55244
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Rafael Gonzaga <[email protected]>
  • Loading branch information
ErickWendel authored and louwers committed Nov 2, 2024
1 parent f0125a4 commit 3bcfac2
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 12 deletions.
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 @@ -64,9 +64,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 @@ -108,6 +108,7 @@ class MockTimers {

#realPromisifiedSetTimeout;
#realPromisifiedSetInterval;
#realTimersPromisifiedSchedulerWait;

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

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

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

#storeOriginalSchedulerWait() {

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

#storeOriginalSetTimeout() {
this.#realSetTimeout = ObjectGetOwnPropertyDescriptor(
globalThis,
Expand Down Expand Up @@ -562,8 +578,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 @@ -577,7 +599,7 @@ class MockTimers {
this,
);
},
setInterval: () => {
'setInterval': () => {
this.#storeOriginalSetInterval();

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

// setImmediate functions needs to bind MockTimers
Expand All @@ -615,23 +637,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 @@ -791,6 +791,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

0 comments on commit 3bcfac2

Please sign in to comment.