blob: cfe0107943ab11fe314900cac65c378895bdae90 [file] [log] [blame]
John Park33858a32018-09-28 23:05:48 -07001#include "aos/util/phased_loop.h"
Brian Silvermandcaa3f72015-11-29 05:32:08 +00002
James Kuszmaul20dcc7c2023-01-20 11:06:31 -08003#include "glog/logging.h"
Austin Schuh60e77942022-05-16 17:48:24 -07004#include "gtest/gtest.h"
Brian Silvermandcaa3f72015-11-29 05:32:08 +00005
Philipp Schrader790cb542023-07-05 21:06:52 -07006#include "aos/time/time.h"
7
Brian Silvermandcaa3f72015-11-29 05:32:08 +00008namespace aos {
9namespace time {
10namespace testing {
11
Austin Schuh8aec1ed2016-05-01 13:29:20 -070012using ::std::chrono::milliseconds;
Brian Silverman8babd8f2020-06-23 16:38:50 -070013using ::std::chrono::nanoseconds;
Austin Schuh8aec1ed2016-05-01 13:29:20 -070014
Alex Perrycb7da4b2019-08-28 19:35:56 -070015typedef ::testing::Test PhasedLoopTest;
Brian Silvermandcaa3f72015-11-29 05:32:08 +000016typedef PhasedLoopTest PhasedLoopDeathTest;
17
Austin Schuh8aec1ed2016-05-01 13:29:20 -070018monotonic_clock::time_point InMs(int ms) {
19 return monotonic_clock::time_point(::std::chrono::milliseconds(ms));
20}
21
Brian Silvermandcaa3f72015-11-29 05:32:08 +000022TEST_F(PhasedLoopTest, Reset) {
23 {
Austin Schuhd32b3622019-06-23 18:49:06 -070024 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
25 milliseconds(0));
Brian Silvermandcaa3f72015-11-29 05:32:08 +000026
Austin Schuh8aec1ed2016-05-01 13:29:20 -070027 loop.Reset(monotonic_clock::epoch());
28 EXPECT_EQ(InMs(0), loop.sleep_time());
29 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
30 EXPECT_EQ(InMs(100), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000031
Austin Schuh8aec1ed2016-05-01 13:29:20 -070032 loop.Reset(InMs(99));
33 EXPECT_EQ(InMs(0), loop.sleep_time());
34 EXPECT_EQ(1, loop.Iterate(InMs(99)));
35 EXPECT_EQ(InMs(100), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000036
Austin Schuh8aec1ed2016-05-01 13:29:20 -070037 loop.Reset(InMs(100));
38 EXPECT_EQ(InMs(100), loop.sleep_time());
39 EXPECT_EQ(1, loop.Iterate(InMs(199)));
40 EXPECT_EQ(InMs(200), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000041
Austin Schuh8aec1ed2016-05-01 13:29:20 -070042 loop.Reset(InMs(101));
43 EXPECT_EQ(InMs(100), loop.sleep_time());
44 EXPECT_EQ(1, loop.Iterate(InMs(101)));
45 EXPECT_EQ(InMs(200), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000046 }
47 {
Austin Schuhd32b3622019-06-23 18:49:06 -070048 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
49 milliseconds(1));
Austin Schuh8aec1ed2016-05-01 13:29:20 -070050 loop.Reset(monotonic_clock::epoch());
51 EXPECT_EQ(InMs(-99), loop.sleep_time());
52 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
53 EXPECT_EQ(InMs(1), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000054 }
55 {
Austin Schuhd32b3622019-06-23 18:49:06 -070056 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
57 milliseconds(99));
Brian Silvermandcaa3f72015-11-29 05:32:08 +000058
Austin Schuh8aec1ed2016-05-01 13:29:20 -070059 loop.Reset(monotonic_clock::epoch());
60 EXPECT_EQ(InMs(-1), loop.sleep_time());
61 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
62 EXPECT_EQ(InMs(99), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000063
Austin Schuh8aec1ed2016-05-01 13:29:20 -070064 loop.Reset(InMs(98));
65 EXPECT_EQ(InMs(-1), loop.sleep_time());
66 EXPECT_EQ(1, loop.Iterate(InMs(98)));
67 EXPECT_EQ(InMs(99), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000068
Austin Schuh8aec1ed2016-05-01 13:29:20 -070069 loop.Reset(InMs(99));
70 EXPECT_EQ(InMs(99), loop.sleep_time());
71 EXPECT_EQ(1, loop.Iterate(InMs(99)));
72 EXPECT_EQ(InMs(199), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000073
Austin Schuh8aec1ed2016-05-01 13:29:20 -070074 loop.Reset(InMs(100));
75 EXPECT_EQ(InMs(99), loop.sleep_time());
76 EXPECT_EQ(1, loop.Iterate(InMs(100)));
77 EXPECT_EQ(InMs(199), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000078 }
79}
80
81TEST_F(PhasedLoopTest, Iterate) {
82 {
Austin Schuhd32b3622019-06-23 18:49:06 -070083 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
84 milliseconds(99));
Austin Schuh8aec1ed2016-05-01 13:29:20 -070085 loop.Reset(monotonic_clock::epoch());
86 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
87 EXPECT_EQ(InMs(99), loop.sleep_time());
88 EXPECT_EQ(1, loop.Iterate(InMs(100)));
89 EXPECT_EQ(InMs(199), loop.sleep_time());
90 EXPECT_EQ(0, loop.Iterate(InMs(100)));
91 EXPECT_EQ(InMs(199), loop.sleep_time());
92 EXPECT_EQ(0, loop.Iterate(InMs(101)));
93 EXPECT_EQ(InMs(199), loop.sleep_time());
94 EXPECT_EQ(0, loop.Iterate(InMs(198)));
95 EXPECT_EQ(InMs(199), loop.sleep_time());
96 EXPECT_EQ(1, loop.Iterate(InMs(199)));
97 EXPECT_EQ(InMs(299), loop.sleep_time());
98 EXPECT_EQ(1, loop.Iterate(InMs(300)));
99 EXPECT_EQ(InMs(399), loop.sleep_time());
100 EXPECT_EQ(3, loop.Iterate(InMs(600)));
101 EXPECT_EQ(InMs(699), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000102 }
103 {
Austin Schuhd32b3622019-06-23 18:49:06 -0700104 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
105 milliseconds(1));
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700106 loop.Reset(monotonic_clock::epoch());
107 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
108 EXPECT_EQ(InMs(1), loop.sleep_time());
109 EXPECT_EQ(1, loop.Iterate(InMs(100)));
110 EXPECT_EQ(InMs(101), loop.sleep_time());
111 EXPECT_EQ(0, loop.Iterate(InMs(100)));
112 EXPECT_EQ(InMs(101), loop.sleep_time());
113 EXPECT_EQ(1, loop.Iterate(InMs(103)));
114 EXPECT_EQ(InMs(201), loop.sleep_time());
115 EXPECT_EQ(0, loop.Iterate(InMs(198)));
116 EXPECT_EQ(InMs(201), loop.sleep_time());
117 EXPECT_EQ(0, loop.Iterate(InMs(200)));
118 EXPECT_EQ(InMs(201), loop.sleep_time());
119 EXPECT_EQ(1, loop.Iterate(InMs(201)));
120 EXPECT_EQ(InMs(301), loop.sleep_time());
121 EXPECT_EQ(3, loop.Iterate(InMs(600)));
122 EXPECT_EQ(InMs(601), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000123 }
124}
125
126// Makes sure that everything works correctly when crossing zero.
127// This seems like a rare case at first, but starting from zero needs to
128// work, which means negatives should too.
129TEST_F(PhasedLoopTest, CrossingZero) {
Austin Schuhd32b3622019-06-23 18:49:06 -0700130 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(), milliseconds(1));
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700131 loop.Reset(InMs(-1000));
132 EXPECT_EQ(InMs(-1099), loop.sleep_time());
133 EXPECT_EQ(9, loop.Iterate(InMs(-250)));
134 EXPECT_EQ(InMs(-199), loop.sleep_time());
135 EXPECT_EQ(1, loop.Iterate(InMs(-199)));
136 EXPECT_EQ(InMs(-99), loop.sleep_time());
137 EXPECT_EQ(1, loop.Iterate(InMs(-90)));
138 EXPECT_EQ(InMs(1), loop.sleep_time());
139 EXPECT_EQ(0, loop.Iterate(InMs(0)));
140 EXPECT_EQ(InMs(1), loop.sleep_time());
141 EXPECT_EQ(1, loop.Iterate(InMs(1)));
142 EXPECT_EQ(InMs(101), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000143
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700144 EXPECT_EQ(0, loop.Iterate(InMs(2)));
145 EXPECT_EQ(InMs(101), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000146
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700147 EXPECT_EQ(-2, loop.Iterate(InMs(-101)));
148 EXPECT_EQ(InMs(-99), loop.sleep_time());
149 EXPECT_EQ(1, loop.Iterate(InMs(-99)));
150 EXPECT_EQ(InMs(1), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000151
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700152 EXPECT_EQ(0, loop.Iterate(InMs(-99)));
153 EXPECT_EQ(InMs(1), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000154}
155
Austin Schuh5d4b0982017-04-08 14:36:08 -0700156// Tests OffsetFromIntervalAndTime for various edge conditions.
157TEST_F(PhasedLoopTest, OffsetFromIntervalAndTimeTest) {
Austin Schuhd32b3622019-06-23 18:49:06 -0700158 PhasedLoop loop(milliseconds(1000), monotonic_clock::epoch(),
159 milliseconds(300));
Austin Schuh5d4b0982017-04-08 14:36:08 -0700160
161 EXPECT_EQ(milliseconds(1),
162 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(1001)));
163
164 EXPECT_EQ(milliseconds(0),
165 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(1000)));
166
167 EXPECT_EQ(milliseconds(0),
168 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(0)));
169
170 EXPECT_EQ(milliseconds(999),
171 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(-1)));
172
173 EXPECT_EQ(milliseconds(7),
174 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(19115007)));
175
176 EXPECT_EQ(milliseconds(7), loop.OffsetFromIntervalAndTime(milliseconds(1000),
177 InMs(-19115993)));
178}
179
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000180// Tests that passing invalid values to the constructor dies correctly.
181TEST_F(PhasedLoopDeathTest, InvalidValues) {
Austin Schuhd32b3622019-06-23 18:49:06 -0700182 EXPECT_DEATH(
183 PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(2)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700184 ".*offset < interval.*");
Austin Schuhd32b3622019-06-23 18:49:06 -0700185 EXPECT_DEATH(
186 PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(1)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700187 ".*offset < interval.*");
Austin Schuhd32b3622019-06-23 18:49:06 -0700188 EXPECT_DEATH(
189 PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(-1)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700190 ".*offset >= monotonic_clock::duration\\(0\\).*");
Austin Schuhd32b3622019-06-23 18:49:06 -0700191 EXPECT_DEATH(
192 PhasedLoop(milliseconds(0), monotonic_clock::epoch(), milliseconds(0)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700193 ".*interval > monotonic_clock::duration\\(0\\).*");
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000194}
195
Brian Silverman8babd8f2020-06-23 16:38:50 -0700196// Tests that every single value within two intervals of 0 works.
197// This is good at finding edge cases in the rounding.
198TEST_F(PhasedLoopTest, SweepingZero) {
199 for (int i = -30; i < -20; ++i) {
200 PhasedLoop loop(nanoseconds(20),
201 monotonic_clock::epoch() - nanoseconds(30));
202 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
203 }
204 for (int i = -20; i < 0; ++i) {
205 PhasedLoop loop(nanoseconds(20),
206 monotonic_clock::epoch() - nanoseconds(30));
207 EXPECT_EQ(2, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
208 }
209 for (int i = 0; i < 20; ++i) {
210 PhasedLoop loop(nanoseconds(20),
211 monotonic_clock::epoch() - nanoseconds(30));
212 EXPECT_EQ(3, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
213 }
214 for (int i = 20; i < 30; ++i) {
215 PhasedLoop loop(nanoseconds(20),
216 monotonic_clock::epoch() - nanoseconds(30));
217 EXPECT_EQ(4, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
218 }
219}
220
Milind Upadhyay42589bb2021-05-19 20:05:16 -0700221// Tests that the phased loop is correctly adjusting when the offset is
222// decremented multiple times.
223TEST_F(PhasedLoopTest, DecrementingOffset) {
224 constexpr int kCount = 5;
225 constexpr int kIterations = 10;
226 const auto kOffset = milliseconds(400);
227 const auto kInterval = milliseconds(1000);
228 const auto kAllIterationsInterval = kInterval * kIterations;
229
230 PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
231 auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
232 ASSERT_EQ(5, loop.Iterate(last_time));
233 for (int i = 1; i < kCount; i++) {
234 const auto offset = kOffset - milliseconds(i);
James Kuszmaul20dcc7c2023-01-20 11:06:31 -0800235 // First, set the interval/offset without specifying a "now". If we then
236 // attempt to Iterate() to the same time as the last iteration, this should
237 // always result in zero cycles elapsed.
238 {
239 const monotonic_clock::time_point original_time = loop.sleep_time();
240 loop.set_interval_and_offset(kInterval, offset);
241 EXPECT_EQ(original_time - milliseconds(1), loop.sleep_time());
242 EXPECT_EQ(0, loop.Iterate(last_time));
243 }
244
245 // Now, explicitly update/clear things to last_time. This should have the
246 // same behavior as not specifying a monotonic_now.
247 {
248 loop.set_interval_and_offset(kInterval, offset, last_time);
249 EXPECT_EQ(0, loop.Iterate(last_time));
250 }
251
Milind Upadhyay42589bb2021-05-19 20:05:16 -0700252 const auto next_time = last_time - milliseconds(1) + kAllIterationsInterval;
253 EXPECT_EQ(kIterations, loop.Iterate(next_time));
254 last_time = next_time;
255 }
256}
257
258// Tests that the phased loop is correctly adjusting when the offset is
James Kuszmaul20dcc7c2023-01-20 11:06:31 -0800259// incremented multiple times.
260TEST_F(PhasedLoopTest, IncrementingOffset) {
261 constexpr int kCount = 5;
262 constexpr int kIterations = 10;
263 const auto kOffset = milliseconds(0);
264 const auto kInterval = milliseconds(1000);
265 const auto kAllIterationsInterval = kInterval * kIterations;
266
267 PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
268 auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
269 ASSERT_EQ(4, loop.Iterate(last_time));
270 for (int i = 1; i < kCount; i++) {
271 const auto offset = kOffset + milliseconds(i);
272 {
273 const monotonic_clock::time_point original_time = loop.sleep_time();
274 loop.set_interval_and_offset(kInterval, offset);
275 EXPECT_EQ(original_time - kInterval + milliseconds(1), loop.sleep_time());
276 EXPECT_EQ(0, loop.Iterate(last_time));
277 }
278 // Now, explicitly update/clear things to a set time. We add a milliseconds
279 // so that when we call Iterate() next we actually get the expected number
280 // of iterations (otherwise, there is an iteration that would happen at
281 // last_time + 1 that gets counted, which is correct behavior, and so just
282 // needs to be accounted for somehow).
283 {
284 loop.set_interval_and_offset(kInterval, offset,
285 last_time + milliseconds(1));
286 EXPECT_EQ(0, loop.Iterate(last_time + milliseconds(1)));
287 }
288
289 const auto next_time = last_time + milliseconds(1) + kAllIterationsInterval;
290 EXPECT_EQ(kIterations, loop.Iterate(next_time));
291 last_time = next_time;
292 }
293}
294
295// Tests that the phased loop is correctly adjusting when the offset is
Milind Upadhyay42589bb2021-05-19 20:05:16 -0700296// changed to 0.
297TEST_F(PhasedLoopTest, ChangingOffset) {
298 const auto kOffset = milliseconds(900);
299 const auto kInterval = milliseconds(1000);
300 PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
301 const auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
302 ASSERT_EQ(5, loop.Iterate(last_time));
303 loop.set_interval_and_offset(kInterval, milliseconds(0));
304 EXPECT_EQ(4, loop.Iterate((last_time - kOffset) + (kInterval * 4)));
305}
306
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000307} // namespace testing
308} // namespace time
309} // namespace aos