-
Notifications
You must be signed in to change notification settings - Fork 109
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
Fake timers override mocks on performance object #374
Comments
Yes, that sounds like a reasonable ask. PR welcome or if another contributor wants to take it :) |
alright, I am pretty much overloaded currently with work but will try to brew something if noone does this in the near future |
@itayperry want to take a stab at this :)? |
I'll take a look into it - I might ask for some advice :) |
Seems like easy pickings for anyone interested party to pick up. |
fake-timers/src/fake-timers-src.js Lines 1526 to 1547 in 604f090
Hi everyone, it took me a while to get to this :) Something like that:
fake-timers/test/fake-timers-test.js Lines 3751 to 3775 in 604f090
|
I do understand that you need to overwrite performance.now() due to timers controll in sync manner, but can you explain why |
I'm not sure I know why it was done that way, I'm taking a look at the source code and at old PRs and issues to see how it originated :) |
Lets see what owners think. |
Found it! |
I can live with installing clock destroying my performance getEntries() mocks as long as I can remock them again after clock install. |
Usually we are not afforded the luxury of knowing why the authors did as they did originally for every line, but it is not very hard to make an educated guess that makes sense when looking at the code and considering the context
As you see, this was originally just a simple loop over every property on the Performance object that replaced everything with no-ops. That has to be the smallest possible implementation. It was just slightly modified when someone found that this changed the API for getEntries. The reason why it should still probably be stubbed is probably just that these entries come from some actual implementation and that it would not be right to return something that is based on timers that are not present. You could, of course, supply a PR that tells fake-timers not to install a stub for this specific method, but it would probably be a lot more cost efficient to just store the original values (your mocks) and then overwrite them again after installing lolex. That would be two lines of code. Given that very, very few people are likely to need any such option, I would just do this, if I were you. |
Hi @fatso83, thank you for the elaborated and enlightening reply! So, basically.. if we just take and change Performance.now() and take all the other values as they are (instead of replacing them with no-ops) that would be inefficient/unnecessary? |
@fatso83 makes sense, I will remock my performance implementation in the jsdom after clock install as it is not something big of a deal. You can close this ticket here without any changes if you find it suitable. |
@Smrtnyk 👍
@itayperry I think you are suggesting to just leave them be, right, if they exist? Not sure why that would be inefficient. Seems like less work to me. TBH, if the functions are there and we do not replace them with something meaningful, I am not sure why we do it at all. Your guess is as good as mine and some quick |
Then let's do it maybe? Leave them be and replace all the I could do that:
It would only break one test (
|
/* For instance, Safari 9 has performance.now(), but no performance.mark() */ | |
if (performanceMarkPresent) { | |
it("should let performance.mark still be callable after FakeTimers.install() (#136)", function () { | |
this.clock = FakeTimers.install(); | |
refute.exception(function () { | |
global.performance.mark("a name"); | |
}); | |
}); | |
} |
Do you think that maybe specifically the mark()
should stay a noop
? I did a console.log
and it seems like the mark()
exists but I don't understand why it says "Illegal invocation".
We no longer support Safari 9, so any code targetting that could be removed. I am not sure about the illegal invocation thing, but it might be a thing native functions have some expectations of where they are being called. So when no longer present on the native If calling native implementations on another object is illegal is the case, then we can't do the naive copy-thing. Anyway, I am thinking we go about this the wrong way. Right now, all the suggestions is basically just about not stubbing the performance timers, right? If that's the case, why not just add a possibility to not stub them? As in making it possible to add |
this blows my mind lol
Ohhh I see, that sounds very neat! So basically, I'm gonna have to move all the As in moving this fake-timers/src/fake-timers-src.js Lines 1526 to 1547 in ce85fea
in here? : fake-timers/src/fake-timers-src.js Lines 1616 to 1635 in ce85fea
|
Yes, that sounds about right.
Not so strange, though, if comparing to normal JS. You can't just store a method reference and expect it to work when called outside of its original context, unless you explicitly bind it, a la |
Thank you! About adding fake-timers/src/fake-timers-src.js Lines 1526 to 1547 in ce85fea
but I don't know how to deal with the fact that hrTime() function would be undefined.Do you have any idea how I could solve it? Generally speaking, when a user chooses not to fake performance then clock.performance.now shouldn't be created?
fake-timers/test/fake-timers-test.js Line 3703 in ce85fea
One more thing I found out - there are a few tests that actually add fake-timers/integration-test/fake-clock-integration-test.js Lines 39 to 58 in ce85fea
The thing is - if I we let users use |
I'll start with the last question, since that was the strangest one. If what you really mean is the
I have no idea what you mean here either :-| The only
Seems right.
I cannot see how this is correct. I have searched all of Maybe what you think about is this line:
When you inspected that in the debugger you might have seen something about "performance" in that array. No one has explicitly put that in there. If you look a few lines further up you can see on line 31-32 what is going:
Here, they install fake timers on the jsDomGlobal (instead of the normal |
Thank you so much for taking the time to reply so extensively - there are clearly a lot more things that I should learn about the project, I do not wish to burden members and to be too much in need of hand-holding (although it's exactly what I'm doing, I'm afraid). TBH - understanding most of the code (or what's happening in general) is way harder than I thought - but I still wanna try and resolve this (and many other issues) 🙈
That's exactly what happened.
Thanks!
About the |
You have to start somewhere 🙂 I haven't written more than a tiny subset, mostly bug fixes, and I need to reacquaint myself every time. Luckily, it's relatively little code and pretty straightforward. You just need a lot of jumping back and forth 😉 I don't understand the need to move hrtime anywhere. It's used to setup the clock and the toFake array is separate from this. It's all about this tiny for loop: fake-timers/src/fake-timers-src.js Line 1616 in ce85fea
|
That's part of the code I wanted to move into the loop: fake-timers/src/fake-timers-src.js Lines 1542 to 1546 in ce85fea
But from what I understand this (specifically) shouldn't be moved? btw - const |
I do not understand why it needs to be moved. Especially into the loop. We have to distinct phases
So your only changes would need be in the loop (or after it), AFAIK. Just check if |
Thank you :) That's the change in if (performancePresent) {
clock.performance = Object.create(null);
clock.performance.now = function FakeTimersNow() {
var hrt = hrtime();
var millis = hrt[0] * 1000 + hrt[1] / 1e6;
return millis;
};
} that's the addition to the loop in the if (
clock.methods[i] === "performance" &&
hasPerformancePrototype
) {
var proto = _global.Performance.prototype;
Object.getOwnPropertyNames(proto).forEach(function (name) {
if (name !== "now") {
clock.performance[name] =
name.indexOf("getEntries") === 0
? NOOP_ARRAY
: NOOP;
}
});
}
hijackMethod(_global, clock.methods[i], clock); All tests pass of course because everything is the same. EDIT: code is now colored 🎉 |
I think this seems to make sense. One could argue whether it makes sense to have a field called To make the logic a bit clearer, I think I would move all of that outside the loop and just do something like: if (clock.methods.includes("performance")) {
var proto = _global.Performance.prototype;
Object.getOwnPropertyNames(proto).forEach(function (name) {
if (name !== "now") {
clock.performance[name] =
name.indexOf("getEntries") === 0 ? NOOP_ARRAY : NOOP;
}
});
} Remember to add a test for your changes. P.S. What is the |
I didn't know I could color my code (like in Stack Overflow), thank you for showing me that this feature exists!!
I thought about it too! Perhaps I should change the name someday?
If you're asking that it probably means I still didn't do it right.. and that I don't understand exactly what's happening. clock.performance.now = function FakeTimersNow() {
var hrt = hrtime();
var millis = hrt[0] * 1000 + hrt[1] / 1e6;
return millis;
}; and breaks many tests. BTW - Thank you a million times for your undying patience and extremely elaborated replies - I don't take that for granted :) |
ohhh |
I don't have the answers in my head, so I needed to examine the code to answer you 😄. That's why it took some time.
As you see in the code, when fake-timers/src/fake-timers-src.js Line 1598 in ce85fea
Among which are performance :fake-timers/src/fake-timers-src.js Line 936 in ce85fea
So yes, we should mock performance by default - like all other timers.
I don't know 😄 The code does not mock performance if you leave it out from Just generally, if you wonder about something, ask the code. That's what I do when you ask ;) Just write a little driver to verify current behavior: const lolex = require("@sinonjs/fake-timers");
const origPerfNow = performance.now;
function isSame(){
console.log(
"performance.now the same after `install()`?",
origPerfNow === performance.now
);
}
console.log(performance.now()); // 43.575292110443115
const clock = lolex.install();
console.log(performance.now()); // 0
isSame(); // false
clock.uninstall();
lolex.install({toFake: ["setTimeout"]})
isSame(); // true |
Thank you @fatso83 :)
A good time to ask you - how do you use your local copy of I checked it now as well, and I can see that BTW, did you see my previous comment? I put the clock.performance.now = function FakeTimersNow() {
var hrt = hrtime();
var millis = hrt[0] * 1000 + hrt[1] / 1e6;
return millis;
}; and then the I thought about it and the changes I did in the code didn't really change much.. all tests pass and everything is almost exactly the same.
Perhaps it's already possible? I just tried it in a I understand that this should stay because if we don't hijack then there's no point for this to run: if (clock.methods.includes("performance")) {
var proto = _global.Performance.prototype;
Object.getOwnPropertyNames(proto).forEach(function (name) {
if (name !== "now") {
clock.performance[name] =
name.indexOf("getEntries") === 0 ? NOOP_ARRAY : NOOP;
}
});
} What are your thoughts about this? |
Yes, it is |
I am actually redirected here from jestjs/jest#11330
I am using jest with jsdom
What did you expect to happen?
I would expect that properties that I added to be preserved after fake timers install
What actually happens
performance object is overriden and properties I added before install are gone
How to reproduce
I will explain how I did it in jest which they say they just only install FakeTimers and don't do any logic
Please check this jestjs/jest#11330
I have global mocks file where before testing I do this
My app relies on these values.
After I install FakeTimers the performance.timing is gone
With jest legacy timers (which are not sinon/FakeTimers) this is not happening
From what I get is that FakeTimers mock performance.now() etc... but is it possible to preserve values that were added to performance object before clock install?
The text was updated successfully, but these errors were encountered: