Skip to content

Commit

Permalink
initial work
Browse files Browse the repository at this point in the history
Signed-off-by: Erick Wendel <erick.workspace@gmail.com>
  • Loading branch information
ErickWendel committed Oct 3, 2024
1 parent cebf21d commit 559ff1f
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 2 deletions.
46 changes: 44 additions & 2 deletions lib/internal/test_runner/mock/mock_timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ 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 @@ -115,6 +121,8 @@ class MockTimers {
#realTimersClearImmediate;
#realPromisifiedSetImmediate;

#realPromisifiedSchedulerWait;

#nativeDateDescriptor;

#timersInContext = [];
Expand All @@ -130,6 +138,7 @@ class MockTimers {
#clearInterval = FunctionPrototypeBind(this.#clearTimer, this);
#clearImmediate = FunctionPrototypeBind(this.#clearTimer, this);


constructor() {
emitExperimentalWarning('The MockTimers API');
}
Expand Down Expand Up @@ -218,6 +227,15 @@ class MockTimers {
);
}

#restoreOriginalSchedulerWait() {

ObjectDefineProperty(
nodeTimersPromises.scheduler,
'wait',
this.#realPromisifiedSchedulerWait,
);
}

#storeOriginalSetImmediate() {
this.#realSetImmediate = ObjectGetOwnPropertyDescriptor(
globalThis,
Expand Down Expand Up @@ -287,6 +305,13 @@ class MockTimers {
);
}

#storeOriginalSchedulerWait() {
this.#realPromisifiedSchedulerWait = ObjectGetOwnPropertyDescriptor(
nodeTimersPromises.scheduler,
'wait',
);
}

#createTimer(isInterval, callback, delay, ...args) {
const timerId = this.#currentTimer++;
const opts = {
Expand Down Expand Up @@ -472,6 +497,13 @@ class MockTimers {
);
}

#schedulerWait(delay, options) {
// Calling timersPromises.scheduler.wait(delay, options)
// is equivalent to calling
// timersPromises.setTimeout(delay, undefined, options).
return this.#setTimeoutPromisified(delay, undefined, options)
}

#promisifyTimer({ timerFn, clearFn, ms, result, options }) {
return new Promise((resolve, reject) => {
if (options?.signal) {
Expand Down Expand Up @@ -613,6 +645,13 @@ class MockTimers {
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
globalThis.Date = this.#createDate();
},
'scheduler.wait': () => {
this.#storeOriginalSchedulerWait();
nodeTimersPromises.scheduler.wait = FunctionPrototypeBind(
this.#schedulerWait,
this
)
},
},
toReal: {
__proto__: null,
Expand All @@ -628,6 +667,9 @@ class MockTimers {
Date: () => {
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
},
'scheduler.wait': () => {
this.#restoreOriginalSchedulerWait()
}
},
};

Expand Down
179 changes: 179 additions & 0 deletions test/parallel/test-runner-mock-timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,185 @@ describe('Mock Timers Test Suite', () => {
});
});

describe('scheduler.wait Suite', () => {
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 p = nodeTimersPromises.scheduler.wait(2000);

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

p.then(common.mustCall((result) => {
assert.strictEqual(result, undefined);
}));
});

// it('should work with the same params as the original timers/promises/scheduler/wait', async (t) => {
// t.mock.timers.enable({ apis: ['scheduler.wait'] });
// const expectedResult = 'result';
// const controller = new AbortController();
// const p = nodeTimersPromises.scheduler.wait(2000, expectedResult, {
// 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, expectedResult);
// });

// it('should abort operation if timers/promises/scheduler.wait received an aborted signal', async (t) => {
// t.mock.timers.enable({ apis: ['scheduler.wait'] });
// const expectedResult = 'result';
// 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: ['setTimeout'] });
// const expectedResult = 'result';
// const controller = new AbortController();
// controller.abort();
// const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
// 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',
// });
// });

// // Test for https://github.com/nodejs/node/issues/50365
// it('should not affect other timers when aborting', async (t) => {
// const f1 = t.mock.fn();
// const f2 = t.mock.fn();
// t.mock.timers.enable({ apis: ['scheduler.wait'] });
// const ac = new AbortController();

// // id 1 & pos 1 in priority queue
// nodeTimersPromises.scheduler.wait(100, { signal: ac.signal }).then(f1, f1);
// // id 2 & pos 1 in priority queue (id 1 is moved to pos 2)
// nodeTimersPromises.scheduler.wait(50).then(f2, f2);

// ac.abort(); // BUG: will remove timer at pos 1 not timer with id 1!

// t.mock.timers.runAll();
// await nodeTimersPromises.setImmediate(); // let promises settle

// // First scheduler.wait is aborted
// assert.strictEqual(f1.mock.callCount(), 1);
// assert.strictEqual(f1.mock.calls[0].arguments[0].code, 'ABORT_ERR');

// // Second scheduler.wait should resolve, but never settles, because it was eronously removed by ac.abort()
// assert.strictEqual(f2.mock.callCount(), 1);
// });

// // Test for https://github.com/nodejs/node/issues/50365
// it('should not affect other timers when aborted after triggering', async (t) => {
// const f1 = t.mock.fn();
// const f2 = t.mock.fn();
// t.mock.timers.enable({ apis: ['setTimeout'] });
// const ac = new AbortController();

// // id 1 & pos 1 in priority queue
// nodeTimersPromises.setTimeout(50, true, { signal: ac.signal }).then(f1, f1);
// // id 2 & pos 2 in priority queue
// nodeTimersPromises.setTimeout(100).then(f2, f2);

// // First setTimeout resolves
// t.mock.timers.tick(50);
// await nodeTimersPromises.setImmediate(); // let promises settle
// assert.strictEqual(f1.mock.callCount(), 1);
// assert.strictEqual(f1.mock.calls[0].arguments.length, 1);
// assert.strictEqual(f1.mock.calls[0].arguments[0], true);

// // Now timer with id 2 will be at pos 1 in priority queue
// ac.abort(); // BUG: will remove timer at pos 1 not timer with id 1!

// // Second setTimeout should resolve, but never settles, because it was eronously removed by ac.abort()
// t.mock.timers.runAll();
// await nodeTimersPromises.setImmediate(); // let promises settle
// assert.strictEqual(f2.mock.callCount(), 1);
// });

// it('should not affect other timers when clearing timeout inside own callback', (t) => {
// t.mock.timers.enable({ apis: ['setTimeout'] });
// const f = t.mock.fn();

// const timer = nodeTimers.setTimeout(() => {
// f();
// // Clearing the already-expired timeout should do nothing
// nodeTimers.clearTimeout(timer);
// }, 50);
// nodeTimers.setTimeout(f, 50);
// nodeTimers.setTimeout(f, 50);

// t.mock.timers.runAll();
// assert.strictEqual(f.mock.callCount(), 3);
// });

// it('should allow clearing timeout inside own callback', (t) => {
// t.mock.timers.enable({ apis: ['setTimeout'] });
// const f = t.mock.fn();

// const timer = nodeTimers.setTimeout(() => {
// f();
// nodeTimers.clearTimeout(timer);
// }, 50);

// t.mock.timers.runAll();
// assert.strictEqual(f.mock.callCount(), 1);
// });
});

describe('setInterval Suite', () => {
it('should tick three times using fake setInterval', async (t) => {
t.mock.timers.enable({ apis: ['setInterval'] });
Expand Down

0 comments on commit 559ff1f

Please sign in to comment.