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: introduces a new MockTimers API #47775

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
568ad96
test_runner: add initial draft for fakeTimers
ErickWendel Apr 28, 2023
ac0fab3
test_runner: move fakerTimers to mock module
ErickWendel May 1, 2023
e54a162
test_runner: change fakeTimers prop name
ErickWendel May 1, 2023
71ffd10
test_runner: implement PriorityQueue for timers
ErickWendel May 1, 2023
a0f8afd
test_runner: add @ljharb suggestion
ErickWendel May 1, 2023
0f55f65
test_runner: add @benjamingr suggestion
ErickWendel May 1, 2023
e34a426
test_runner: add fake setTimeout initial impl
ErickWendel May 1, 2023
df35174
test_runner: add @molow suggestion
ErickWendel May 1, 2023
e5af52f
test_runner: fix a test
ErickWendel May 1, 2023
a22321f
test_runner: rename from fakeTimers to mockTimers
ErickWendel May 3, 2023
21be0bc
test_runner: add experimental warning
ErickWendel May 3, 2023
102a830
test_runner: fix tests
ErickWendel May 3, 2023
1d30e47
test_runner: add @ljharb suggestion
ErickWendel May 3, 2023
0cb4e7c
test_runner: fix possible truthy evaluation on tick by @ljharb
ErickWendel May 3, 2023
a53aedc
test_runner: add tests for clearTimeout
ErickWendel May 3, 2023
1a2cf34
test_runner: add tests for setInterval modules
ErickWendel May 3, 2023
db2a79f
test_runner: add tests for clearInterval modules
ErickWendel May 3, 2023
f200467
test_runner: add impl for timers.promises.setInterval function
ErickWendel May 4, 2023
cc652fe
test_runner: fix setInterval algorithm
ErickWendel May 4, 2023
942f504
test_runner: add abortController support for promises.setTimeout and …
ErickWendel May 14, 2023
bb75287
test_runner: add test cases for abortController and remove listeners …
ErickWendel May 14, 2023
b0baf00
test_runner: change import order
ErickWendel May 15, 2023
92bf115
test_runner: add ArrayPrototypeAt instead of [].at
ErickWendel May 15, 2023
1fe731e
test_runner: return safe asyncIterator
ErickWendel May 15, 2023
08f27ba
test_runner: make promises.setInterval update time during iterations
ErickWendel May 15, 2023
3e39782
test_runner: add __proto__ null while returning the object
ErickWendel May 19, 2023
a563ce9
test_runner: make promises.setInterval work with abortControllers
ErickWendel May 30, 2023
17381f1
test_runner: rm unsafe array iteration and comments
ErickWendel May 30, 2023
3677482
test_runner: avoid avoid code repetition
ErickWendel May 30, 2023
1825426
test_runner: (rollback) avoid avoid code repetition
ErickWendel May 30, 2023
abc7c96
test_runner: rm unecessarly PromiseRejection
ErickWendel May 30, 2023
87b0d36
test_runner: add timers.reset on test postRun
ErickWendel Jun 5, 2023
a001e65
test_runner: add config for choosing timers to use, error handling an…
ErickWendel Jun 5, 2023
d168614
test_runner: implement releaseAllTimers function
ErickWendel Jun 5, 2023
1e09a22
test_runner: reach 100% code coverage
ErickWendel Jun 5, 2023
89b489b
test_runner: rename function to runAll
ErickWendel Jun 6, 2023
2b64a4a
fix flaky test
ErickWendel Jun 8, 2023
a9ed28d
add abort once flag
ErickWendel Jun 8, 2023
b383a1c
check that timeout will only be called when reaches the specified tim…
ErickWendel Jun 8, 2023
ba725f4
fix lint problems
ErickWendel Jun 8, 2023
1bcbe65
test_runner: add initial doc
ErickWendel Jun 12, 2023
7c65e64
test_runner: fix heading
ErickWendel Jun 12, 2023
aaf7070
test_runner: fix text
ErickWendel Jun 12, 2023
ac9f5b6
test_runner: fix links
ErickWendel Jun 12, 2023
13fab2b
test_runner: add docs for timers.tick
ErickWendel Jun 12, 2023
296f5b0
test_runner: add docs for timers.runAll
ErickWendel Jun 12, 2023
a75ddb4
doc rm empty spaces
ErickWendel Jun 12, 2023
1bee321
Update test/parallel/test-runner-mock-timers.js
ErickWendel Jun 13, 2023
bc8fea7
Update lib/internal/test_runner/mock/mock_timers.js
ErickWendel Jun 13, 2023
1bfd7b8
format test.md
ErickWendel Jun 13, 2023
d2c9a1a
add 1ms to tick to fix flakyness
ErickWendel Jun 13, 2023
93950f5
test_runner: add 50ms to .tick so setInterval will not be flaky
ErickWendel Jun 13, 2023
49e8eba
test_runner: revert last commit
ErickWendel Jun 13, 2023
4d3a45c
founde the bug 🐛
MoLow Jun 13, 2023
12772d1
Apply suggestions from code review
ErickWendel Jun 14, 2023
5c9e425
apply suggestions from review
ErickWendel Jun 14, 2023
0424bf5
add info about support for destructuring from modules
ErickWendel Jun 14, 2023
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
110 changes: 110 additions & 0 deletions lib/internal/test_runner/fake_timers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use strict';

const {
DateNow,
SafeMap,
Symbol,
globalThis,
} = primordials;

class Timers {
constructor() {
this.timers = new SafeMap();
benjamingr marked this conversation as resolved.
Show resolved Hide resolved

this.setTimeout = this.#createTimer.bind(this, false);
this.clearTimeout = this.#clearTimer.bind(this);
this.setInterval = this.#createTimer.bind(this, true);
this.clearInterval = this.#clearTimer.bind(this);
}

#createTimer(isInterval, callback, delay, ...args) {
const timerId = Symbol('kTimerId');
const timer = {
id: timerId,
callback,
time: DateNow() + delay,
ErickWendel marked this conversation as resolved.
Show resolved Hide resolved
interval: isInterval,
args,
};
this.timers.set(timerId, timer);
return timerId;
}

#clearTimer(timerId) {
this.timers.delete(timerId);
}

}

let realSetTimeout;
let realClearTimeout;
let realSetInterval;
let realClearInterval;

class FakeTimers {
constructor() {
this.fakeTimers = {};
this.isEnabled = false;
this.now = DateNow();
}

tick(time = 0) {

// if (!this.isEnabled) {
// throw new Error('you should enable fakeTimers first by calling the .enable function');
// }

this.now += time;
const timers = this.fakeTimers.timers;

for (const timer of timers.values()) {
ErickWendel marked this conversation as resolved.
Show resolved Hide resolved

if (!(this.now >= timer.time)) continue;

timer.callback(...timer.args);
if (timer.interval) {
timer.time = this.now + (timer.time - this.now) % timer.args[0];
continue;
}

timers.delete(timer.id);
}
}

enable() {
// if (this.isEnabled) {
// throw new Error('fakeTimers is already enabled!');
// }
this.now = DateNow();
this.isEnabled = true;
this.fakeTimers = new Timers();

realSetTimeout = globalThis.setTimeout;
realClearTimeout = globalThis.clearTimeout;
realSetInterval = globalThis.setInterval;
realClearInterval = globalThis.clearInterval;

globalThis.setTimeout = this.fakeTimers.setTimeout;
ErickWendel marked this conversation as resolved.
Show resolved Hide resolved
globalThis.clearTimeout = this.fakeTimers.clearTimeout;
globalThis.setInterval = this.fakeTimers.setInterval;
globalThis.clearInterval = this.fakeTimers.clearInterval;

}

reset() {
this.isEnabled = false;
this.fakeTimers = {};

// Restore the real timer functions
globalThis.setTimeout = realSetTimeout;
globalThis.clearTimeout = realClearTimeout;
globalThis.setInterval = realSetInterval;
globalThis.clearInterval = realClearInterval;
}

releaseAllTimers() {

}
}

module.exports = { FakeTimers };
6 changes: 6 additions & 0 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const {
AbortError,
} = require('internal/errors');
const { MockTracker } = require('internal/test_runner/mock');
const { FakeTimers } = require('internal/test_runner/fake_timers');
const { TestsStream } = require('internal/test_runner/tests_stream');
const {
createDeferredCallback,
Expand Down Expand Up @@ -108,6 +109,11 @@ class TestContext {
return this.#test.mock;
}

get fakeTimers() {
ErickWendel marked this conversation as resolved.
Show resolved Hide resolved
this.#test.fakeTimers ??= new FakeTimers();
return this.#test.fakeTimers;
}

runOnly(value) {
this.#test.runOnlySubtests = !!value;
}
Expand Down
17 changes: 17 additions & 0 deletions lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,20 @@ ObjectDefineProperty(module.exports, 'mock', {
return lazyMock;
},
});

let lazyFakeTimers;

ObjectDefineProperty(module.exports, 'fakeTimers', {
__proto__: null,
configurable: true,
enumerable: true,
get() {
if (lazyFakeTimers === undefined) {
const { FakeTimers } = require('internal/test_runner/fake_timers');

lazyFakeTimers = new FakeTimers();
}

return lazyFakeTimers;
},
});
46 changes: 46 additions & 0 deletions test/parallel/test-runner-fake-timers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';
const common = require('../common');
process.env.NODE_TEST_KNOWN_GLOBALS = 0;

const assert = require('node:assert');
const { fakeTimers, it, mock, afterEach, describe } = require('node:test');
describe('Faketimers Test Suite', () => {

describe('setTimeout Suite', () => {
afterEach(() => fakeTimers.reset());

it('should advance in time and trigger timers when calling the .tick function', (t) => {
fakeTimers.enable();

const fn = mock.fn(() => {});

global.setTimeout(fn, 4000);

fakeTimers.tick(4000);
assert.ok(fn.mock.callCount());
});

it('should advance in time and trigger timers when calling the .tick function multiple times', (t) => {
fakeTimers.enable();
const fn = mock.fn();

global.setTimeout(fn, 2000);

fakeTimers.tick(1000);
fakeTimers.tick(1000);

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

it('should keep setTimeout working if fakeTimers are disabled', (t, done) => {
const now = Date.now();
const timeout = 2;
const expected = () => now - timeout;
global.setTimeout(common.mustCall(() => {
assert.strictEqual(now - timeout, expected());
done();
}), timeout);
});

});
});