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

Prohibit faking of faked timers #426

Merged
merged 6 commits into from Apr 13, 2022
Merged

Conversation

cjbarth
Copy link
Contributor

@cjbarth cjbarth commented Apr 9, 2022

Purpose

This change will prohibit the faking of already faked timers.

Background (Problem in detail)

This addresses sinonjs/sinon#2449.

The problem this PR is addressing can occur when an exception is thrown before a timer is uninstalled or if a nested test tries to fake timers again instead of adjusting the currently faked timer or clearing the previous fake first.

If a faked timer is faked again, it will be impossible to recover the original Date and Date-related functions again in call cases. So, instead of trying to detect incepted faked timers, this is prohibited in all cases.

Solution

If an attempt is make to fake a timer that is already faked, an exception will be thrown.

@benjamingr
Copy link
Member

I'm not sure we should do this - I can see someone writing a test using fake timers inside a suite using fake timers and expecting double faking + restoring to work

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 10, 2022

Well, it doesn't now, it fails silently. So, this change will let them know that what they are doing doesn't work. It is actually my guess that if people are trying to get this to work, and their tests are passing (like those in the project I'm working on), then they would probably like to know that their tests are faulty (like I would have wanted to know). It was easy enough to change my tests to be compliant once I knew the problem.

Additionally, no documentation says you can fake faked timers, so if they are doing that, it is undocumented and broken.

Copy link
Contributor

@fatso83 fatso83 left a comment

Choose a reason for hiding this comment

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

I like the idea you propose here, as it seems to support the most common scenario one would want to avoid. Theoretically, someone could be doing the suggested double-faking, but as you pointed out, that does not actually work, so if that is the case, I have no objections towards doing this. Just the implementation 🤓

@@ -53,6 +53,69 @@ const utilPromisifyAvailable = promisePresent && utilPromisify;
const timeoutResult = global.setTimeout(NOOP, 0);
const addTimerReturnsObject = typeof timeoutResult === "object";

describe("issue #2449: perminent loss of native functions", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
describe("issue #2449: perminent loss of native functions", () => {
describe("issue #2449: permanent loss of native functions", function () {

typo-fix. Also Mocha relies heavily on this, so it's a malpractice to use arrow functions. Not sure why we do not have ESLint rules to handle this 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Also, I forgot to run npm run lint, so I didn't see the problem with the arrow functions. However, since I'm not using this in my tests, it wouldn't have mattered.

Comment on lines 1640 to 1643
if (
typeof stockDate === "function" &&
Math.abs(new Date().getTime() - new stockDate().getTime()) > 100
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not fancy the implicitness of this. It also won't cover a bunch of scenarios where we do not fake Date. fake-timers support faking a single timer or many the APIs. See the toFake config option: https://github.com/sinonjs/fake-timers#var-clock--faketimersinstallconfig

I would much rather be specific in testing for a property on the various timers, along the lines of what Luxon DateTime.isDatetime(obj) is doing. This would only mean adding a boolean isFakeTimer to Date and the timers. And the test would be explicit!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noted that the faked timers can't be used unless install is first called, so I tried to put the property on the global only when install is called. I tried to leave the global object clean after uninstall.

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 11, 2022

This global leak detection thing is really causing a problem. It seems that adding a property, even if I remove it when done, prohibits Mocha leak detection from working correctly with beforeEach and afterEach. Thoughts?

@mantoni
Copy link
Member

mantoni commented Apr 11, 2022

Instead of leaking a global variable, you could take a reference of setTimeout and check if it isn't the original instance anymore, no?

@mroderick
Copy link
Member

This is a very nice pull request, as it will help people where we are failing them 👍

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 11, 2022

Ok, I've made another adjustment that is actually even more correct than my first try at this. This tracks very well if the object is a fake or not.

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

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

Changes lgtm, good tests :)

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 11, 2022

@benjamingr , can you please run the workflow too?

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 11, 2022

Does anyone have any idea why all the tests would run fine on my machine, and apparently here, but not on chromium? I don't quite follow what the test that failed is trying to do, and since it runs on my machine, I don't have a good way to trace down what is going on. Honestly, it seems like this might actually be a problem with the test trying to install twice. Or uninstalling improperly in chromium.

@benjamingr
Copy link
Member

It looks like most tests pass and there is a single related failure at:

 1) loop limit stack trace
       "before each" hook for "provides a stack trace for running all sync":
     TypeError: Can't install fake timers twice on the same global object.
      at Object.install (src/fake-timers-src.js:1642)
      at Context.<anonymous> (test/fake-timers-test.js:5008)

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 12, 2022

So, after further research, I found that this failing test is actually leaking a faked date instance. So, even the tests for the project were in need of this exception to catch bad use of these timers! I'll push up a fix in a bit.

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 12, 2022

Actually, I'm not sure how I might fix this. The problem is that you are skipping a test, in which case, the afterEach of the parent describe doesn't run because technically the test never ran. That leaks the fake timer. If I move the installation of the fake timer to each lower-level describe block, it breaks some of the recursive stuff you're trying to do. Thoughts?

@cjbarth
Copy link
Contributor Author

cjbarth commented Apr 12, 2022

Ok, I fixed the test. If you have any questions, please let me know. Otherwise, I look forward to this landing. I don't think it would require a semver-major release, in fact, it is probably just a semver-patch release because it "fixes" a bug.

@codecov
Copy link

codecov bot commented Apr 13, 2022

Codecov Report

Merging #426 (c0e2a0a) into main (5ea3a4d) will increase coverage by 1.33%.
The diff coverage is 100.00%.

❗ Current head c0e2a0a differs from pull request most recent head 3b9250a. Consider uploading reports for the commit 3b9250a to get more accurate results

@@            Coverage Diff             @@
##             main     #426      +/-   ##
==========================================
+ Coverage   94.17%   95.51%   +1.33%     
==========================================
  Files           1        1              
  Lines         618      624       +6     
==========================================
+ Hits          582      596      +14     
+ Misses         36       28       -8     
Flag Coverage Δ
unit 95.51% <100.00%> (+1.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/fake-timers-src.js 95.51% <100.00%> (+1.33%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f8a09c9...3b9250a. Read the comment docs.

@fatso83
Copy link
Contributor

fatso83 commented Apr 13, 2022

Thank you for sticking with us all the way through. Great work and great tests, and that you discovered a bug in the test suite just shows all the more so how this prevents common cases 🙌

test/fake-timers-test.js Outdated Show resolved Hide resolved
@mroderick
Copy link
Member

The changes in this PR are published as @sinonjs/fake-timers@9.1.2 and are included in sinon@13.0.2

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

Successfully merging this pull request may close these issues.

None yet

5 participants