// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/time/time.h" #include "base/test/gtest_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace { class ScopedTimebase { public: explicit ScopedTimebase(mach_timebase_info_data_t timebase) { orig_timebase_ = base::TimeTicks::SetMachTimebaseInfoForTesting(timebase); } ScopedTimebase(const ScopedTimebase&) = delete; ScopedTimebase& operator=(const ScopedTimebase&) = delete; ~ScopedTimebase() { base::TimeTicks::SetMachTimebaseInfoForTesting(orig_timebase_); } private: mach_timebase_info_data_t orig_timebase_; }; mach_timebase_info_data_t kIntelTimebase = {1, 1}; // A sample (not definitive) timebase for M1. mach_timebase_info_data_t kM1Timebase = {125, 3}; } // namespace namespace base { namespace { base::Time NoonOnDate(int year, int month, int day) { const base::Time::Exploded exploded = { .year = year, .month = month, .day_of_month = day, .hour = 12}; base::Time imploded; CHECK(base::Time::FromUTCExploded(exploded, &imploded)); return imploded; } void CheckRoundTrip(int y, int m, int d) { base::Time original = NoonOnDate(y, m, d); base::Time roundtrip = Time::FromNSDate(original.ToNSDate()); EXPECT_EQ(original, roundtrip); } TEST(TimeMacTest, RoundTripNSDate) { CheckRoundTrip(1911, 12, 14); CheckRoundTrip(1924, 9, 28); CheckRoundTrip(1926, 5, 12); CheckRoundTrip(1969, 7, 24); } TEST(TimeMacTest, MachTimeToMicrosecondsIntelTimebase) { ScopedTimebase timebase(kIntelTimebase); // Perform the conversion. uint64_t kArbitraryTicks = 59090101000; TimeDelta result = TimeDelta::FromMachTime(kArbitraryTicks); // With Intel the output should be the input. EXPECT_EQ(Nanoseconds(kArbitraryTicks), result); } TEST(TimeMacTest, MachTimeToMicrosecondsM1Timebase) { ScopedTimebase timebase(kM1Timebase); // Use a tick count that's divisible by 3. const uint64_t kArbitraryTicks = 92738127000; TimeDelta result = TimeDelta::FromMachTime(kArbitraryTicks); const uint64_t kExpectedResult = kArbitraryTicks * kM1Timebase.numer / kM1Timebase.denom; EXPECT_EQ(Nanoseconds(kExpectedResult), result); } // Tests MachTimeToMicroseconds when // mach_timebase_info_data_t.numer and mach_timebase_info_data_t.denom // are equal. TEST(TimeMacTest, MachTimeToMicrosecondsEqualTimebaseMembers) { // These members would produce overflow but don't because // MachTimeToMicroseconds should skip the timebase conversion // when they're equal. ScopedTimebase timebase({UINT_MAX, UINT_MAX}); uint64_t kArbitraryTicks = 175920053729; TimeDelta result = TimeDelta::FromMachTime(kArbitraryTicks); // With a unity timebase the output should be the input. EXPECT_EQ(Nanoseconds(kArbitraryTicks), result); } TEST(TimeMacTest, MachTimeToMicrosecondsOverflowDetection) { const uint32_t kArbitraryNumer = 1234567; ScopedTimebase timebase({kArbitraryNumer, 1}); // Expect an overflow. EXPECT_CHECK_DEATH( TimeDelta::FromMachTime(std::numeric_limits::max())); } // Tests that there's no overflow in MachTimeToMicroseconds even with // std::numeric_limits::max() ticks on Intel. TEST(TimeMacTest, MachTimeToMicrosecondsNoOverflowIntel) { ScopedTimebase timebase(kIntelTimebase); // The incoming Mach time ticks are on the order of nanoseconds while the // return result is microseconds. Even though we're passing in the largest // tick count the result should be orders of magnitude smaller. On Intel the // mapping from ticks to nanoseconds is 1:1 so we wouldn't ever expect an // overflow when applying the timebase conversion. TimeDelta::FromMachTime(std::numeric_limits::max()); } // Tests that there's no overflow in MachTimeToMicroseconds even with // std::numeric_limits::max() ticks on M1. TEST(TimeMacTest, MachTimeToMicrosecondsNoOverflowM1) { ScopedTimebase timebase(kM1Timebase); // The incoming Mach time ticks are on the order of nanoseconds while the // return result is microseconds. Even though we're passing in the largest // tick count the result should be orders of magnitude smaller. Expect that // FromMachTime(), when applying the timebase conversion, is smart enough to // not multiply first and generate an overflow. TimeDelta::FromMachTime(std::numeric_limits::max()); } // Tests that there's no underflow in MachTimeToMicroseconds on Intel. TEST(TimeMacTest, MachTimeToMicrosecondsNoUnderflowIntel) { ScopedTimebase timebase(kIntelTimebase); // On Intel the timebase conversion is 1:1, so min ticks is one microsecond // worth of nanoseconds. const uint64_t kMinimumTicks = base::Time::kNanosecondsPerMicrosecond; const uint64_t kOneMicrosecond = 1; EXPECT_EQ(kOneMicrosecond, TimeDelta::FromMachTime(kMinimumTicks).InMicroseconds() * 1UL); // If we have even one fewer tick (i.e. not enough ticks to constitute a full // microsecond) the integer rounding should result in 0 microseconds. const uint64_t kZeroMicroseconds = 0; EXPECT_EQ(kZeroMicroseconds, TimeDelta::FromMachTime(kMinimumTicks - 1).InMicroseconds() * 1UL); } // Tests that there's no underflow in MachTimeToMicroseconds for M1. TEST(TimeMacTest, MachTimeToMicrosecondsNoUnderflowM1) { ScopedTimebase timebase(kM1Timebase); // Microseconds is mach_time multiplied by kM1Timebase.numer / // (kM1Timebase.denom * base::Time::kNanosecondsPerMicrosecond). Inverting // that should be the minimum number of ticks to get a single microsecond in // return. If we get zero it means an underflow in the conversion. For example // if FromMachTime() first divides mach_time by kM1Timebase.denom * // base::Time::kNanosecondsPerMicrosecond we'll get zero back. const uint64_t kMinimumTicks = (kM1Timebase.denom * base::Time::kNanosecondsPerMicrosecond) / kM1Timebase.numer; const uint64_t kOneMicrosecond = 1; EXPECT_EQ(kOneMicrosecond, TimeDelta::FromMachTime(kMinimumTicks).InMicroseconds() * 1UL); // If we have even one fewer tick (i.e. not enough ticks to constitute a full // microsecond) the integer rounding should result in 0 microseconds. const uint64_t kZeroMicroseconds = 0; EXPECT_EQ(kZeroMicroseconds, TimeDelta::FromMachTime(kMinimumTicks - 1).InMicroseconds() * 1UL); } } // namespace } // namespace base