1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178

base / time / time_apple_unittest.mm [blame]

// 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<uint64_t>::max()));
}

// Tests that there's no overflow in MachTimeToMicroseconds even with
// std::numeric_limits<uint64_t>::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<uint64_t>::max());
}

// Tests that there's no overflow in MachTimeToMicroseconds even with
// std::numeric_limits<uint64_t>::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<uint64_t>::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