travisjeffery / timecop

A gem providing "time travel", "time freezing", and "time acceleration" capabilities, making it simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.
MIT License
3.36k stars 223 forks source link

Time.now sometimes has greater-than-nanosecond precision when called under Timecop.travel #420

Open dgholz opened 3 months ago

dgholz commented 3 months ago

Hello, I have a test that looks like:

Timecop.travel(some_date)
company = create_company
[…]
Timecop.freeze
EmailPolicy::Actions::Block(company)
expect(Preferences::EmailRateLimit.for(company).blocked_until).to eq(6.hours.from_now)

Where EmailPolicy::Actions::Block calls Time.now, and serialised the date to interact with other systems, and Preferences::EmailRateLimit deserialised the date. Times are serialised down to the nanosecond.

It failed with:

       Failure/Error: expect(Preferences::EmailRateLimit.for(company).blocked_until).to eq(6.hours.from_now)

         expected: 2024-05-14 19:42:47.696001634 +0000
              got: 2024-05-14 19:42:47.696001634 +0000

Which baffled me a bit, since I didn't consider that the Time used to calculate 6.hours.from_now would have a fraction of a nanosecond difference to the deserialised Time from the Preferences system.

Previously, the test only had the Timecop.freeze line & worked great. It was only after adding the Timecop.travel that the two identical-looking Times started comparing as unequal. So that made me look at how the travel_offset was added to create the Time.now used for Timecop.freeze, and I saw it was a Float with a much higher precision than was possible with a computer clock.

I have some ideas on how to change my test to pass, but thought I'd raise this issue to highlight that the Times returned under Timecop.travel may sometimes have more precision than you might expect, and shouldn't be compared to Times created from other sources.

dgholz commented 3 months ago

Times returned under Timecop.travel may sometimes have more precision than you might expect, and shouldn't be compared to Times created from other sources.

I opened #421 as a possible approach to removing this caveat, though I do worry that the test doesn't cover the case when Timecop.travel is called with a Time created with fractions of a nanosecond.

jasonkingPNM commented 3 months ago

Nice, just stumbled over this today.

dgholz commented 2 months ago

the test doesn't cover the case when Timecop.travel is called with a Time created with fractions of a nanosecond

Resolved, I added a test explicitly for this.

joshuacronemeyer commented 3 weeks ago

Reopening this issue since i had to revert the PR