blob: af33567044fc97e877d2fc23a1ddb05b20978cdb [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
Stephan Pleinesf63bde82024-01-13 15:59:33 -08008namespace aos::time::testing {
Brian Silvermandcaa3f72015-11-29 05:32:08 +00009
Austin Schuh8aec1ed2016-05-01 13:29:20 -070010using ::std::chrono::milliseconds;
Brian Silverman8babd8f2020-06-23 16:38:50 -070011using ::std::chrono::nanoseconds;
Austin Schuh8aec1ed2016-05-01 13:29:20 -070012
Alex Perrycb7da4b2019-08-28 19:35:56 -070013typedef ::testing::Test PhasedLoopTest;
Brian Silvermandcaa3f72015-11-29 05:32:08 +000014typedef PhasedLoopTest PhasedLoopDeathTest;
15
Austin Schuh8aec1ed2016-05-01 13:29:20 -070016monotonic_clock::time_point InMs(int ms) {
17 return monotonic_clock::time_point(::std::chrono::milliseconds(ms));
18}
19
Brian Silvermandcaa3f72015-11-29 05:32:08 +000020TEST_F(PhasedLoopTest, Reset) {
21 {
Austin Schuhd32b3622019-06-23 18:49:06 -070022 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
23 milliseconds(0));
Brian Silvermandcaa3f72015-11-29 05:32:08 +000024
Austin Schuh8aec1ed2016-05-01 13:29:20 -070025 loop.Reset(monotonic_clock::epoch());
26 EXPECT_EQ(InMs(0), loop.sleep_time());
27 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
28 EXPECT_EQ(InMs(100), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000029
Austin Schuh8aec1ed2016-05-01 13:29:20 -070030 loop.Reset(InMs(99));
31 EXPECT_EQ(InMs(0), loop.sleep_time());
32 EXPECT_EQ(1, loop.Iterate(InMs(99)));
33 EXPECT_EQ(InMs(100), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000034
Austin Schuh8aec1ed2016-05-01 13:29:20 -070035 loop.Reset(InMs(100));
36 EXPECT_EQ(InMs(100), loop.sleep_time());
37 EXPECT_EQ(1, loop.Iterate(InMs(199)));
38 EXPECT_EQ(InMs(200), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000039
Austin Schuh8aec1ed2016-05-01 13:29:20 -070040 loop.Reset(InMs(101));
41 EXPECT_EQ(InMs(100), loop.sleep_time());
42 EXPECT_EQ(1, loop.Iterate(InMs(101)));
43 EXPECT_EQ(InMs(200), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000044 }
45 {
Austin Schuhd32b3622019-06-23 18:49:06 -070046 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
47 milliseconds(1));
Austin Schuh8aec1ed2016-05-01 13:29:20 -070048 loop.Reset(monotonic_clock::epoch());
49 EXPECT_EQ(InMs(-99), loop.sleep_time());
50 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
51 EXPECT_EQ(InMs(1), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000052 }
53 {
Austin Schuhd32b3622019-06-23 18:49:06 -070054 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
55 milliseconds(99));
Brian Silvermandcaa3f72015-11-29 05:32:08 +000056
Austin Schuh8aec1ed2016-05-01 13:29:20 -070057 loop.Reset(monotonic_clock::epoch());
58 EXPECT_EQ(InMs(-1), loop.sleep_time());
59 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
60 EXPECT_EQ(InMs(99), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000061
Austin Schuh8aec1ed2016-05-01 13:29:20 -070062 loop.Reset(InMs(98));
63 EXPECT_EQ(InMs(-1), loop.sleep_time());
64 EXPECT_EQ(1, loop.Iterate(InMs(98)));
65 EXPECT_EQ(InMs(99), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000066
Austin Schuh8aec1ed2016-05-01 13:29:20 -070067 loop.Reset(InMs(99));
68 EXPECT_EQ(InMs(99), loop.sleep_time());
69 EXPECT_EQ(1, loop.Iterate(InMs(99)));
70 EXPECT_EQ(InMs(199), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000071
Austin Schuh8aec1ed2016-05-01 13:29:20 -070072 loop.Reset(InMs(100));
73 EXPECT_EQ(InMs(99), loop.sleep_time());
74 EXPECT_EQ(1, loop.Iterate(InMs(100)));
75 EXPECT_EQ(InMs(199), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000076 }
77}
78
79TEST_F(PhasedLoopTest, Iterate) {
80 {
Austin Schuhd32b3622019-06-23 18:49:06 -070081 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
82 milliseconds(99));
Austin Schuh8aec1ed2016-05-01 13:29:20 -070083 loop.Reset(monotonic_clock::epoch());
84 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
85 EXPECT_EQ(InMs(99), loop.sleep_time());
86 EXPECT_EQ(1, loop.Iterate(InMs(100)));
87 EXPECT_EQ(InMs(199), loop.sleep_time());
88 EXPECT_EQ(0, loop.Iterate(InMs(100)));
89 EXPECT_EQ(InMs(199), loop.sleep_time());
90 EXPECT_EQ(0, loop.Iterate(InMs(101)));
91 EXPECT_EQ(InMs(199), loop.sleep_time());
92 EXPECT_EQ(0, loop.Iterate(InMs(198)));
93 EXPECT_EQ(InMs(199), loop.sleep_time());
94 EXPECT_EQ(1, loop.Iterate(InMs(199)));
95 EXPECT_EQ(InMs(299), loop.sleep_time());
96 EXPECT_EQ(1, loop.Iterate(InMs(300)));
97 EXPECT_EQ(InMs(399), loop.sleep_time());
98 EXPECT_EQ(3, loop.Iterate(InMs(600)));
99 EXPECT_EQ(InMs(699), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000100 }
101 {
Austin Schuhd32b3622019-06-23 18:49:06 -0700102 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(),
103 milliseconds(1));
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700104 loop.Reset(monotonic_clock::epoch());
105 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch()));
106 EXPECT_EQ(InMs(1), loop.sleep_time());
107 EXPECT_EQ(1, loop.Iterate(InMs(100)));
108 EXPECT_EQ(InMs(101), loop.sleep_time());
109 EXPECT_EQ(0, loop.Iterate(InMs(100)));
110 EXPECT_EQ(InMs(101), loop.sleep_time());
111 EXPECT_EQ(1, loop.Iterate(InMs(103)));
112 EXPECT_EQ(InMs(201), loop.sleep_time());
113 EXPECT_EQ(0, loop.Iterate(InMs(198)));
114 EXPECT_EQ(InMs(201), loop.sleep_time());
115 EXPECT_EQ(0, loop.Iterate(InMs(200)));
116 EXPECT_EQ(InMs(201), loop.sleep_time());
117 EXPECT_EQ(1, loop.Iterate(InMs(201)));
118 EXPECT_EQ(InMs(301), loop.sleep_time());
119 EXPECT_EQ(3, loop.Iterate(InMs(600)));
120 EXPECT_EQ(InMs(601), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000121 }
122}
123
124// Makes sure that everything works correctly when crossing zero.
125// This seems like a rare case at first, but starting from zero needs to
126// work, which means negatives should too.
127TEST_F(PhasedLoopTest, CrossingZero) {
Austin Schuhd32b3622019-06-23 18:49:06 -0700128 PhasedLoop loop(milliseconds(100), monotonic_clock::epoch(), milliseconds(1));
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700129 loop.Reset(InMs(-1000));
130 EXPECT_EQ(InMs(-1099), loop.sleep_time());
131 EXPECT_EQ(9, loop.Iterate(InMs(-250)));
132 EXPECT_EQ(InMs(-199), loop.sleep_time());
133 EXPECT_EQ(1, loop.Iterate(InMs(-199)));
134 EXPECT_EQ(InMs(-99), loop.sleep_time());
135 EXPECT_EQ(1, loop.Iterate(InMs(-90)));
136 EXPECT_EQ(InMs(1), loop.sleep_time());
137 EXPECT_EQ(0, loop.Iterate(InMs(0)));
138 EXPECT_EQ(InMs(1), loop.sleep_time());
139 EXPECT_EQ(1, loop.Iterate(InMs(1)));
140 EXPECT_EQ(InMs(101), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000141
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700142 EXPECT_EQ(0, loop.Iterate(InMs(2)));
143 EXPECT_EQ(InMs(101), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000144
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700145 EXPECT_EQ(-2, loop.Iterate(InMs(-101)));
146 EXPECT_EQ(InMs(-99), loop.sleep_time());
147 EXPECT_EQ(1, loop.Iterate(InMs(-99)));
148 EXPECT_EQ(InMs(1), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000149
Austin Schuh8aec1ed2016-05-01 13:29:20 -0700150 EXPECT_EQ(0, loop.Iterate(InMs(-99)));
151 EXPECT_EQ(InMs(1), loop.sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000152}
153
Austin Schuh5d4b0982017-04-08 14:36:08 -0700154// Tests OffsetFromIntervalAndTime for various edge conditions.
155TEST_F(PhasedLoopTest, OffsetFromIntervalAndTimeTest) {
Austin Schuhd32b3622019-06-23 18:49:06 -0700156 PhasedLoop loop(milliseconds(1000), monotonic_clock::epoch(),
157 milliseconds(300));
Austin Schuh5d4b0982017-04-08 14:36:08 -0700158
159 EXPECT_EQ(milliseconds(1),
160 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(1001)));
161
162 EXPECT_EQ(milliseconds(0),
163 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(1000)));
164
165 EXPECT_EQ(milliseconds(0),
166 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(0)));
167
168 EXPECT_EQ(milliseconds(999),
169 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(-1)));
170
171 EXPECT_EQ(milliseconds(7),
172 loop.OffsetFromIntervalAndTime(milliseconds(1000), InMs(19115007)));
173
174 EXPECT_EQ(milliseconds(7), loop.OffsetFromIntervalAndTime(milliseconds(1000),
175 InMs(-19115993)));
176}
177
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000178// Tests that passing invalid values to the constructor dies correctly.
179TEST_F(PhasedLoopDeathTest, InvalidValues) {
Austin Schuhd32b3622019-06-23 18:49:06 -0700180 EXPECT_DEATH(
181 PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(2)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700182 ".*offset < interval.*");
Austin Schuhd32b3622019-06-23 18:49:06 -0700183 EXPECT_DEATH(
184 PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(1)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700185 ".*offset < interval.*");
Austin Schuhd32b3622019-06-23 18:49:06 -0700186 EXPECT_DEATH(
187 PhasedLoop(milliseconds(1), monotonic_clock::epoch(), milliseconds(-1)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700188 ".*offset >= monotonic_clock::duration\\(0\\).*");
Austin Schuhd32b3622019-06-23 18:49:06 -0700189 EXPECT_DEATH(
190 PhasedLoop(milliseconds(0), monotonic_clock::epoch(), milliseconds(0)),
Austin Schuhf257f3c2019-10-27 21:00:43 -0700191 ".*interval > monotonic_clock::duration\\(0\\).*");
Brian Silvermandcaa3f72015-11-29 05:32:08 +0000192}
193
Brian Silverman8babd8f2020-06-23 16:38:50 -0700194// Tests that every single value within two intervals of 0 works.
195// This is good at finding edge cases in the rounding.
196TEST_F(PhasedLoopTest, SweepingZero) {
197 for (int i = -30; i < -20; ++i) {
198 PhasedLoop loop(nanoseconds(20),
199 monotonic_clock::epoch() - nanoseconds(30));
200 EXPECT_EQ(1, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
201 }
202 for (int i = -20; i < 0; ++i) {
203 PhasedLoop loop(nanoseconds(20),
204 monotonic_clock::epoch() - nanoseconds(30));
205 EXPECT_EQ(2, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
206 }
207 for (int i = 0; i < 20; ++i) {
208 PhasedLoop loop(nanoseconds(20),
209 monotonic_clock::epoch() - nanoseconds(30));
210 EXPECT_EQ(3, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
211 }
212 for (int i = 20; i < 30; ++i) {
213 PhasedLoop loop(nanoseconds(20),
214 monotonic_clock::epoch() - nanoseconds(30));
215 EXPECT_EQ(4, loop.Iterate(monotonic_clock::epoch() + nanoseconds(i)));
216 }
217}
218
Milind Upadhyay42589bb2021-05-19 20:05:16 -0700219// Tests that the phased loop is correctly adjusting when the offset is
220// decremented multiple times.
221TEST_F(PhasedLoopTest, DecrementingOffset) {
222 constexpr int kCount = 5;
223 constexpr int kIterations = 10;
224 const auto kOffset = milliseconds(400);
225 const auto kInterval = milliseconds(1000);
226 const auto kAllIterationsInterval = kInterval * kIterations;
227
228 PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
229 auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
230 ASSERT_EQ(5, loop.Iterate(last_time));
231 for (int i = 1; i < kCount; i++) {
232 const auto offset = kOffset - milliseconds(i);
James Kuszmaul20dcc7c2023-01-20 11:06:31 -0800233 // First, set the interval/offset without specifying a "now". If we then
234 // attempt to Iterate() to the same time as the last iteration, this should
235 // always result in zero cycles elapsed.
236 {
237 const monotonic_clock::time_point original_time = loop.sleep_time();
238 loop.set_interval_and_offset(kInterval, offset);
239 EXPECT_EQ(original_time - milliseconds(1), loop.sleep_time());
240 EXPECT_EQ(0, loop.Iterate(last_time));
241 }
242
243 // Now, explicitly update/clear things to last_time. This should have the
244 // same behavior as not specifying a monotonic_now.
245 {
246 loop.set_interval_and_offset(kInterval, offset, last_time);
247 EXPECT_EQ(0, loop.Iterate(last_time));
248 }
249
Milind Upadhyay42589bb2021-05-19 20:05:16 -0700250 const auto next_time = last_time - milliseconds(1) + kAllIterationsInterval;
251 EXPECT_EQ(kIterations, loop.Iterate(next_time));
252 last_time = next_time;
253 }
254}
255
256// Tests that the phased loop is correctly adjusting when the offset is
James Kuszmaul20dcc7c2023-01-20 11:06:31 -0800257// incremented multiple times.
258TEST_F(PhasedLoopTest, IncrementingOffset) {
259 constexpr int kCount = 5;
260 constexpr int kIterations = 10;
261 const auto kOffset = milliseconds(0);
262 const auto kInterval = milliseconds(1000);
263 const auto kAllIterationsInterval = kInterval * kIterations;
264
265 PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
266 auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
267 ASSERT_EQ(4, loop.Iterate(last_time));
268 for (int i = 1; i < kCount; i++) {
269 const auto offset = kOffset + milliseconds(i);
270 {
271 const monotonic_clock::time_point original_time = loop.sleep_time();
272 loop.set_interval_and_offset(kInterval, offset);
273 EXPECT_EQ(original_time - kInterval + milliseconds(1), loop.sleep_time());
274 EXPECT_EQ(0, loop.Iterate(last_time));
275 }
276 // Now, explicitly update/clear things to a set time. We add a milliseconds
277 // so that when we call Iterate() next we actually get the expected number
278 // of iterations (otherwise, there is an iteration that would happen at
279 // last_time + 1 that gets counted, which is correct behavior, and so just
280 // needs to be accounted for somehow).
281 {
282 loop.set_interval_and_offset(kInterval, offset,
283 last_time + milliseconds(1));
284 EXPECT_EQ(0, loop.Iterate(last_time + milliseconds(1)));
285 }
286
287 const auto next_time = last_time + milliseconds(1) + kAllIterationsInterval;
288 EXPECT_EQ(kIterations, loop.Iterate(next_time));
289 last_time = next_time;
290 }
291}
292
293// Tests that the phased loop is correctly adjusting when the offset is
Milind Upadhyay42589bb2021-05-19 20:05:16 -0700294// changed to 0.
295TEST_F(PhasedLoopTest, ChangingOffset) {
296 const auto kOffset = milliseconds(900);
297 const auto kInterval = milliseconds(1000);
298 PhasedLoop loop(kInterval, monotonic_clock::epoch(), kOffset);
299 const auto last_time = monotonic_clock::epoch() + kOffset + (kInterval * 3);
300 ASSERT_EQ(5, loop.Iterate(last_time));
301 loop.set_interval_and_offset(kInterval, milliseconds(0));
302 EXPECT_EQ(4, loop.Iterate((last_time - kOffset) + (kInterval * 4)));
303}
304
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800305} // namespace aos::time::testing