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

fix: do not use a real timeout in waitfor when using fake timers #1177

Merged
merged 8 commits into from
Oct 31, 2022

Conversation

pierrezimmermannbam
Copy link
Collaborator

@pierrezimmermannbam pierrezimmermannbam commented Oct 16, 2022

Summary

Fixes #1176

Use a timeout in waitFor only when using realTimers and timeout with fake timers only after fakeTimeRemaining is over

Test plan

I added a test case that initially failed. I compute fibonaci suite so that it takes some time to perform the checks (it must take longer than interval for this bug to be visible)

This shouldn't be a breaking change, all tests that previously passed should still do because we're not performing any check after fakeTimeRemaining is over anyway

Comment on lines 207 to 213
const fibonaci = (n: number): number => {
if (n === 0 || n === 1) {
return 1;
}

return fibonaci(n - 1) + fibonaci(n - 2);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we replace this with something both more controllable and explicit:

Suggested change
const fibonaci = (n: number): number => {
if (n === 0 || n === 1) {
return 1;
}
return fibonaci(n - 1) + fibonaci(n - 2);
};
function blockThread(milliseconds)
{
var end = Date.now() + milliseconds;
while (Date.now() < end) {
// Intentionally do nothing
};
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good idea, it's way more explicit that way! I just had to switch to real timers when calling blockThread so that it works

Comment on lines 215 to 218
test.each([false, true])(
'it should not depend on real time when using fake timers (legacyFakeTimers = %s)',
async () => {
jest.useFakeTimers({ legacyFakeTimers: false });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test does not seem to use each parameter. Pls remove each param or apply it to legacyFakeTimers

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my bad, fixed it

Copy link
Member

@mdjastrzebski mdjastrzebski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pierrezimmermannbam I've reviewed the code the best I could, but I do not feel an expert in fake timers.

The changes in the waitFor.ts file seem reasonable. Though I felt safer just sticking to the DTL code which we copied, but that might be just my poor understanding of fake timers ;-)

I've left some comments & suggestions about test code.

@thymikee pls take a look at this PR, you seem to know much more about fake timers than I.

@pierrezimmermannbam
Copy link
Collaborator Author

@mdjastrzebski thanks for the review ! I believe I implemented all the changes you requested


const mockErrorFn = jest.fn(() => {
// Wait 5 seconds so that check time is longer than interval
blockThread(5, legacyFakeTimers);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would wait a little bit longer to make the test more robust using the exact value might cause some race conditions

Suggested change
blockThread(5, legacyFakeTimers);
blockThread(8, legacyFakeTimers);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm not using the exact value, the aim is that we block the thread for a time longer than the interval, but I'll make it a bit more explicit

Comment on lines 232 to 233
timeout: 6,
interval: 2,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to be using very tight intervals, which could be fragile. Let use some interval in ~20-100 range (maybe 50 ms)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how using tight intervals could be fragile since we're using fake timers but maybe I'm thinking too much based in the current implementation so I increased it to 20

Comment on lines 237 to 239
// Verify that even though time to perform check is longer than interval
// test won't timeout until number of checks * interval >= timeout
// ie fake timers have been advanced by timeout when waitfor rejects
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Verify that even though time to perform check is longer than interval
// test won't timeout until number of checks * interval >= timeout
// ie fake timers have been advanced by timeout when waitfor rejects
// Verify that even though time to perform check is longer than interval
// test won't timeout until number of checks * interval >= timeout
// i.e. fake timers have been advanced (at least?) by timeout when waitFor rejects

Copy link
Member

@mdjastrzebski mdjastrzebski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I start to understand this a bit more. I've left some comments for the testing code.

@pierrezimmermannbam
Copy link
Collaborator Author

@mdjastrzebski thanks again for your feedbacks, I treated all your comments

Copy link
Member

@mdjastrzebski mdjastrzebski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I think I finally grasped what is happening both in the code and in the test.

Good job @pierrezimmermannbam for catching it. I've suggested a rewrite to the expect comment to clarify the intent. Pls use/modify as you find it suitable.

@mdjastrzebski
Copy link
Member

@thymikee @AugustinLF pls take a look at this PR, as I do not feel expert on fake timers.

Co-authored-by: Maciej Jastrzebski <mdjastrzebski@gmail.com>
@AugustinLF
Copy link
Collaborator

@mdjastrzebski it looks good to me, but same, I'm not familiar with the timers part of the codebase. I'd suggest releasing this commit alone in a dedicated rc release to be able to do some testing on it, it's the type of changes that could bring some weird errors in some codebases.

@mdjastrzebski
Copy link
Member

I've asked @thymikee for review, as our "fake timer specialist". That might delay merging for couple of days, but I think it's worth it.

Copy link
Member

@thymikee thymikee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks!

@thymikee thymikee merged commit 380ff32 into callstack:main Oct 31, 2022
@mdjastrzebski
Copy link
Member

@pierrezimmermannbam you might want to submit a similar bug/PR for DOM Testing Library, as they should have the same issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When using fake timers, waitFor still uses a setTimeout with real timers
4 participants