1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "gtest/gtest.h"
8 #include "mozilla/TimeStamp.h"
9 #include "SystemTimeConverter.h"
10
11 using mozilla::SystemTimeConverter;
12 using mozilla::TimeDuration;
13 using mozilla::TimeStamp;
14
15 namespace {
16
17 // This class provides a mock implementation of the CurrentTimeGetter template
18 // type used in SystemTimeConverter. It can be constructed with a particular
19 // Time and always returns that Time.
20 template <typename Time>
21 class MockCurrentTimeGetter {
22 public:
MockCurrentTimeGetter()23 MockCurrentTimeGetter() : mTime(0) {}
MockCurrentTimeGetter(Time aTime)24 explicit MockCurrentTimeGetter(Time aTime) : mTime(aTime) {}
25
26 // Methods needed for CurrentTimeGetter compatibility
GetCurrentTime() const27 Time GetCurrentTime() const { return mTime; }
GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp & aNow)28 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {}
29
30 private:
31 Time mTime;
32 };
33
34 // This is another mock implementation of the CurrentTimeGetter template
35 // type used in SystemTimeConverter, except this asserts that it will not be
36 // used. i.e. it should only be used in calls to SystemTimeConverter that we
37 // know will not invoke it.
38 template <typename Time>
39 class UnusedCurrentTimeGetter {
40 public:
GetCurrentTime() const41 Time GetCurrentTime() const {
42 EXPECT_TRUE(false);
43 return 0;
44 }
45
GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp & aNow)46 void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
47 EXPECT_TRUE(false);
48 }
49 };
50
51 // This class provides a mock implementation of the TimeStampNowProvider
52 // template type used in SystemTimeConverter. It also has other things in it
53 // that allow the test to better control time for testing purposes.
54 class MockTimeStamp {
55 public:
56 // This should generally be called at the start of every test function, as
57 // it will initialize this class's static fields to sane values. In particular
58 // it will initialize the baseline TimeStamp against which all other
59 // TimeStamps are compared.
Init()60 static void Init() {
61 sBaseline = TimeStamp::Now();
62 sTimeStamp = sBaseline;
63 }
64
65 // Advance the timestamp returned by `MockTimeStamp::Now()`
Advance(double ms)66 static void Advance(double ms) {
67 sTimeStamp += TimeDuration::FromMilliseconds(ms);
68 }
69
70 // Returns the baseline TimeStamp, that is used as a fixed reference point
71 // in time against which other TimeStamps can be compared. This is needed
72 // because mozilla::TimeStamp itself doesn't provide any conversion to
73 // human-readable strings, and we need to convert it to a TimeDuration in
74 // order to get that. This baseline TimeStamp can be used to turn an
75 // arbitrary TimeStamp into a TimeDuration.
Baseline()76 static TimeStamp Baseline() { return sBaseline; }
77
78 // This is the method needed for TimeStampNowProvider compatibility, and
79 // simulates `TimeStamp::Now()`
Now()80 static TimeStamp Now() { return sTimeStamp; }
81
82 private:
83 static TimeStamp sTimeStamp;
84 static TimeStamp sBaseline;
85 };
86
87 TimeStamp MockTimeStamp::sTimeStamp;
88 TimeStamp MockTimeStamp::sBaseline;
89
90 // Could have platform-specific implementations of this using DWORD, guint32,
91 // etc behind ifdefs. But this is sufficient for now.
92 using GTestTime = uint32_t;
93 using TimeConverter = SystemTimeConverter<GTestTime, MockTimeStamp>;
94
95 } // namespace
96
97 // Checks the expectation that the TimeStamp `ts` is exactly `ms` milliseconds
98 // after the baseline timestamp. This is a macro so gtest still gives us useful
99 // line numbers for failures.
100 #define EXPECT_TS(ts, ms) \
101 EXPECT_EQ((ts)-MockTimeStamp::Baseline(), TimeDuration::FromMilliseconds(ms))
102
103 #define EXPECT_TS_FUZZY(ts, ms) \
104 EXPECT_DOUBLE_EQ(((ts)-MockTimeStamp::Baseline()).ToMilliseconds(), ms)
105
TEST(TimeConverter,SanityCheck)106 TEST(TimeConverter, SanityCheck)
107 {
108 MockTimeStamp::Init();
109
110 MockCurrentTimeGetter timeGetter(10);
111 UnusedCurrentTimeGetter<GTestTime> unused;
112 TimeConverter converter;
113
114 // This call sets the reference time and timestamp
115 TimeStamp ts = converter.GetTimeStampFromSystemTime(10, timeGetter);
116 EXPECT_TS(ts, 0);
117
118 // Advance "TimeStamp::Now" by 10ms, use the same event time and OS time.
119 // Since the event time is the same as before, we expect to get back the
120 // same TimeStamp as before too, despite Now() changing.
121 MockTimeStamp::Advance(10);
122 ts = converter.GetTimeStampFromSystemTime(10, unused);
123 EXPECT_TS(ts, 0);
124
125 // Now let's use an event time 20ms after the old event. This will trigger
126 // forward skew detection and resync the TimeStamp for the new event to Now().
127 ts = converter.GetTimeStampFromSystemTime(30, unused);
128 EXPECT_TS(ts, 10);
129 }
130
TEST(TimeConverter,Overflow)131 TEST(TimeConverter, Overflow)
132 {
133 // This tests wrapping time around the max value supported in the GTestTime
134 // type and ensuring it is handled properly.
135
136 MockTimeStamp::Init();
137
138 const GTestTime max = std::numeric_limits<GTestTime>::max();
139 const GTestTime min = std::numeric_limits<GTestTime>::min();
140 double fullRange = (double)max - (double)min;
141 double wrapPeriod = fullRange + 1.0;
142
143 GTestTime almostOverflowed = max - 100;
144 GTestTime overflowed = max + 100;
145 MockCurrentTimeGetter timeGetter(almostOverflowed);
146 UnusedCurrentTimeGetter<GTestTime> unused;
147 TimeConverter converter;
148
149 // Set reference time to 100ms before the overflow point
150 TimeStamp ts =
151 converter.GetTimeStampFromSystemTime(almostOverflowed, timeGetter);
152 EXPECT_TS(ts, 0);
153
154 // Advance everything by 200ms and verify we get back a TimeStamp 200ms from
155 // the baseline despite wrapping an overflow.
156 MockTimeStamp::Advance(200);
157 ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
158 EXPECT_TS(ts, 200);
159
160 // Advance by another full wraparound of the time. This loses some precision
161 // so we have to do the FUZZY match
162 MockTimeStamp::Advance(wrapPeriod);
163 ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
164 EXPECT_TS_FUZZY(ts, 200.0 + wrapPeriod);
165 }
166
TEST(TimeConverter,InvertedOverflow)167 TEST(TimeConverter, InvertedOverflow)
168 {
169 // This tests time going from near the min value of GTestTime to the max
170 // value of GTestTime
171
172 MockTimeStamp::Init();
173
174 const GTestTime max = std::numeric_limits<GTestTime>::max();
175 const GTestTime min = std::numeric_limits<GTestTime>::min();
176 double fullRange = (double)max - (double)min;
177 double wrapPeriod = fullRange + 1.0;
178
179 GTestTime nearRangeMin = min + 100;
180 GTestTime nearRangeMax = max - 100;
181 double gap = (double)nearRangeMax - (double)nearRangeMin;
182
183 MockCurrentTimeGetter timeGetter(nearRangeMin);
184 UnusedCurrentTimeGetter<GTestTime> unused;
185 TimeConverter converter;
186
187 // Set reference time to value near min numeric limit
188 TimeStamp ts = converter.GetTimeStampFromSystemTime(nearRangeMin, timeGetter);
189 EXPECT_TS(ts, 0);
190
191 // Advance to value near max numeric limit
192 MockTimeStamp::Advance(gap);
193 ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
194 EXPECT_TS(ts, gap);
195
196 // Advance by another full wraparound of the time. This loses some precision
197 // so we have to do the FUZZY match
198 MockTimeStamp::Advance(wrapPeriod);
199 ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
200 EXPECT_TS_FUZZY(ts, gap + wrapPeriod);
201 }
202
TEST(TimeConverter,HalfRangeBoundary)203 TEST(TimeConverter, HalfRangeBoundary)
204 {
205 MockTimeStamp::Init();
206
207 GTestTime max = std::numeric_limits<GTestTime>::max();
208 GTestTime min = std::numeric_limits<GTestTime>::min();
209 double fullRange = (double)max - (double)min;
210 double wrapPeriod = fullRange + 1.0;
211 GTestTime halfRange = (GTestTime)(fullRange / 2.0);
212 GTestTime halfWrapPeriod = (GTestTime)(wrapPeriod / 2.0);
213
214 TimeConverter converter;
215
216 GTestTime firstEvent = 10;
217 MockCurrentTimeGetter timeGetter(firstEvent);
218 UnusedCurrentTimeGetter<GTestTime> unused;
219
220 // Set reference time
221 TimeStamp ts = converter.GetTimeStampFromSystemTime(firstEvent, timeGetter);
222 EXPECT_TS(ts, 0);
223
224 // Advance event time by just under the half-period, to trigger about as big
225 // a forwards skew as we possibly can.
226 GTestTime secondEvent = firstEvent + (halfWrapPeriod - 1);
227 ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
228 EXPECT_TS(ts, 0);
229
230 // The above forwards skew will have reset the reference timestamp. Now
231 // advance Now time by just under the half-range, to trigger about as big
232 // a backwards skew as we possibly can.
233 MockTimeStamp::Advance(halfRange - 1);
234 ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
235 EXPECT_TS(ts, 0);
236 }
237
TEST(TimeConverter,FractionalMillisBug1626734)238 TEST(TimeConverter, FractionalMillisBug1626734)
239 {
240 MockTimeStamp::Init();
241
242 TimeConverter converter;
243
244 GTestTime eventTime = 10;
245 MockCurrentTimeGetter timeGetter(eventTime);
246 UnusedCurrentTimeGetter<GTestTime> unused;
247
248 TimeStamp ts = converter.GetTimeStampFromSystemTime(eventTime, timeGetter);
249 EXPECT_TS(ts, 0);
250
251 MockTimeStamp::Advance(0.2);
252 ts = converter.GetTimeStampFromSystemTime(eventTime, unused);
253 EXPECT_TS(ts, 0);
254
255 MockTimeStamp::Advance(0.9);
256 TimeStamp ts2 = converter.GetTimeStampFromSystemTime(eventTime, unused);
257 EXPECT_TS(ts2, 0);
258
259 // Since ts2 came from a "future" call relative to ts, we expect ts2 to not
260 // be "before" ts. (i.e. time shouldn't go backwards, even by fractional
261 // milliseconds). This assertion is technically already implied by the
262 // EXPECT_TS checks above, but fixing this assertion is the end result that
263 // we wanted in bug 1626734 so it feels appropriate to recheck it explicitly.
264 EXPECT_TRUE(ts <= ts2);
265 }
266