blob: c03b83d54385b5652b4006bea9fc2bd56aa954ed [file] [log] [blame]
James Kuszmaul20dcc7c2023-01-20 11:06:31 -08001diff a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc (rejected hunks)
2@@ -1923,6 +1923,298 @@
3 EXPECT_GT(expected_times[expected_times.size() / 2], average_time - kEpsilon);
4 }
5
6+// Tests that a phased loop responds correctly to a changing offset; sweep
7+// across a variety of potential offset changes, to ensure that we are
8+// exercising a variety of potential cases.
9+TEST_P(AbstractEventLoopTest, PhasedLoopChangingOffsetSweep) {
10+ const chrono::milliseconds kInterval = chrono::milliseconds(1000);
11+ const int kCount = 5;
12+
13+ auto loop1 = MakePrimary();
14+
15+ std::vector<aos::monotonic_clock::duration> offset_options;
16+ for (int ii = 0; ii < kCount; ++ii) {
17+ offset_options.push_back(ii * kInterval / kCount);
18+ }
19+ std::vector<aos::monotonic_clock::duration> offset_sweep;
20+ // Run over all the pair-wise combinations of offsets.
21+ for (int ii = 0; ii < kCount; ++ii) {
22+ for (int jj = 0; jj < kCount; ++jj) {
23+ offset_sweep.push_back(offset_options.at(ii));
24+ offset_sweep.push_back(offset_options.at(jj));
25+ }
26+ }
27+
28+ std::vector<::aos::monotonic_clock::time_point> expected_times;
29+
30+ PhasedLoopHandler *phased_loop;
31+
32+ // Run kCount iterations.
33+ int counter = 0;
34+ phased_loop = loop1->AddPhasedLoop(
35+ [&phased_loop, &expected_times, &loop1, this, kInterval, &counter,
36+ offset_sweep](int count) {
37+ EXPECT_EQ(count, 1);
38+ expected_times.push_back(loop1->context().monotonic_event_time);
39+
40+ counter++;
41+
42+ if (counter == offset_sweep.size()) {
43+ LOG(INFO) << "Exiting";
44+ this->Exit();
45+ return;
46+ }
47+
48+ phased_loop->set_interval_and_offset(kInterval,
49+ offset_sweep.at(counter));
50+ },
51+ kInterval, offset_sweep.at(0));
52+
53+ Run();
54+ ASSERT_EQ(expected_times.size(), offset_sweep.size());
55+ for (size_t ii = 1; ii < expected_times.size(); ++ii) {
56+ EXPECT_LE(expected_times.at(ii) - expected_times.at(ii - 1), kInterval);
57+ }
58+}
59+
60+// Tests that a phased loop responds correctly to being rescheduled with now
61+// equal to a time in the past.
62+TEST_P(AbstractEventLoopTest, PhasedLoopRescheduleInPast) {
63+ const chrono::milliseconds kOffset = chrono::milliseconds(400);
64+ const chrono::milliseconds kInterval = chrono::milliseconds(1000);
65+
66+ auto loop1 = MakePrimary();
67+
68+ std::vector<::aos::monotonic_clock::time_point> expected_times;
69+
70+ PhasedLoopHandler *phased_loop;
71+
72+ int expected_count = 1;
73+
74+ // Set up a timer that will get run immediately after the phased loop and
75+ // which will attempt to reschedule the phased loop to just before now. This
76+ // should succeed, but will result in 0 cycles elapsing.
77+ TimerHandler *manager_timer =
78+ loop1->AddTimer([&phased_loop, &loop1, &expected_count, this]() {
79+ if (expected_count == 0) {
80+ LOG(INFO) << "Exiting";
81+ this->Exit();
82+ return;
83+ }
84+ phased_loop->Reschedule(loop1->context().monotonic_event_time -
85+ std::chrono::nanoseconds(1));
86+ expected_count = 0;
87+ });
88+
89+ phased_loop = loop1->AddPhasedLoop(
90+ [&expected_count, &expected_times, &loop1, manager_timer](int count) {
91+ EXPECT_EQ(count, expected_count);
92+ expected_times.push_back(loop1->context().monotonic_event_time);
93+
94+ manager_timer->Setup(loop1->context().monotonic_event_time);
95+ },
96+ kInterval, kOffset);
97+ phased_loop->set_name("Test loop");
98+ manager_timer->set_name("Manager timer");
99+
100+ Run();
101+
102+ ASSERT_EQ(2u, expected_times.size());
103+ ASSERT_EQ(expected_times[0], expected_times[1]);
104+}
105+
106+// Tests that a phased loop responds correctly to being rescheduled at the time
107+// when it should be triggering (it should kick the trigger to the next cycle).
108+TEST_P(AbstractEventLoopTest, PhasedLoopRescheduleNow) {
109+ const chrono::milliseconds kOffset = chrono::milliseconds(400);
110+ const chrono::milliseconds kInterval = chrono::milliseconds(1000);
111+
112+ auto loop1 = MakePrimary();
113+
114+ std::vector<::aos::monotonic_clock::time_point> expected_times;
115+
116+ PhasedLoopHandler *phased_loop;
117+
118+ bool should_exit = false;
119+ // Set up a timer that will get run immediately after the phased loop and
120+ // which will attempt to reschedule the phased loop to now. This should
121+ // succeed, but will result in no change to the expected behavior (since this
122+ // is the same thing that is actually done internally).
123+ TimerHandler *manager_timer =
124+ loop1->AddTimer([&phased_loop, &loop1, &should_exit, this]() {
125+ if (should_exit) {
126+ LOG(INFO) << "Exiting";
127+ this->Exit();
128+ return;
129+ }
130+ phased_loop->Reschedule(loop1->context().monotonic_event_time);
131+ should_exit = true;
132+ });
133+
134+ phased_loop = loop1->AddPhasedLoop(
135+ [&expected_times, &loop1, manager_timer](int count) {
136+ EXPECT_EQ(count, 1);
137+ expected_times.push_back(loop1->context().monotonic_event_time);
138+
139+ manager_timer->Setup(loop1->context().monotonic_event_time);
140+ },
141+ kInterval, kOffset);
142+ phased_loop->set_name("Test loop");
143+ manager_timer->set_name("Manager timer");
144+
145+ Run();
146+
147+ ASSERT_EQ(2u, expected_times.size());
148+ ASSERT_EQ(expected_times[0] + kInterval, expected_times[1]);
149+}
150+
151+// Tests that a phased loop responds correctly to being rescheduled at a time in
152+// the distant future.
153+TEST_P(AbstractEventLoopTest, PhasedLoopRescheduleFuture) {
154+ const chrono::milliseconds kOffset = chrono::milliseconds(400);
155+ const chrono::milliseconds kInterval = chrono::milliseconds(1000);
156+
157+ auto loop1 = MakePrimary();
158+
159+ std::vector<::aos::monotonic_clock::time_point> expected_times;
160+
161+ PhasedLoopHandler *phased_loop;
162+
163+ bool should_exit = false;
164+ int expected_count = 1;
165+ TimerHandler *manager_timer = loop1->AddTimer(
166+ [&expected_count, &phased_loop, &loop1, &should_exit, this, kInterval]() {
167+ if (should_exit) {
168+ LOG(INFO) << "Exiting";
169+ this->Exit();
170+ return;
171+ }
172+ expected_count = 10;
173+ // Knock off 1 ns, since the scheduler rounds up when it is
174+ // scheduled to exactly a loop time.
175+ phased_loop->Reschedule(loop1->context().monotonic_event_time +
176+ kInterval * expected_count -
177+ std::chrono::nanoseconds(1));
178+ should_exit = true;
179+ });
180+
181+ phased_loop = loop1->AddPhasedLoop(
182+ [&expected_times, &loop1, manager_timer, &expected_count](int count) {
183+ EXPECT_EQ(count, expected_count);
184+ expected_times.push_back(loop1->context().monotonic_event_time);
185+
186+ manager_timer->Setup(loop1->context().monotonic_event_time);
187+ },
188+ kInterval, kOffset);
189+ phased_loop->set_name("Test loop");
190+ manager_timer->set_name("Manager timer");
191+
192+ Run();
193+
194+ ASSERT_EQ(2u, expected_times.size());
195+ ASSERT_EQ(expected_times[0] + expected_count * kInterval, expected_times[1]);
196+}
197+
198+// Tests that a phased loop responds correctly to having its phase offset
199+// incremented and then being scheduled after a set time, exercising a pattern
200+// where a phased loop's offset is changed while trying to maintain the trigger
201+// at a consistent period.
202+TEST_P(AbstractEventLoopTest, PhasedLoopRescheduleWithLaterOffset) {
203+ const chrono::milliseconds kOffset = chrono::milliseconds(400);
204+ const chrono::milliseconds kInterval = chrono::milliseconds(1000);
205+
206+ auto loop1 = MakePrimary();
207+
208+ std::vector<::aos::monotonic_clock::time_point> expected_times;
209+
210+ PhasedLoopHandler *phased_loop;
211+
212+ bool should_exit = false;
213+ TimerHandler *manager_timer = loop1->AddTimer(
214+ [&phased_loop, &loop1, &should_exit, this, kInterval, kOffset]() {
215+ if (should_exit) {
216+ LOG(INFO) << "Exiting";
217+ this->Exit();
218+ return;
219+ }
220+ // Schedule the next callback to be strictly later than the current time
221+ // + interval / 2, to ensure a consistent frequency.
222+ monotonic_clock::time_point half_time =
223+ loop1->context().monotonic_event_time + kInterval / 2;
224+ phased_loop->set_interval_and_offset(
225+ kInterval, kOffset + std::chrono::nanoseconds(1), half_time);
226+ phased_loop->Reschedule(half_time);
227+ should_exit = true;
228+ });
229+
230+ phased_loop = loop1->AddPhasedLoop(
231+ [&expected_times, &loop1, manager_timer](int count) {
232+ EXPECT_EQ(1, count);
233+ expected_times.push_back(loop1->context().monotonic_event_time);
234+
235+ manager_timer->Setup(loop1->context().monotonic_event_time);
236+ },
237+ kInterval, kOffset);
238+ phased_loop->set_name("Test loop");
239+ manager_timer->set_name("Manager timer");
240+
241+ Run();
242+
243+ ASSERT_EQ(2u, expected_times.size());
244+ ASSERT_EQ(expected_times[0] + kInterval + std::chrono::nanoseconds(1),
245+ expected_times[1]);
246+}
247+
248+// Tests that a phased loop responds correctly to having its phase offset
249+// decremented and then being scheduled after a set time, exercising a pattern
250+// where a phased loop's offset is changed while trying to maintain the trigger
251+// at a consistent period.
252+TEST_P(AbstractEventLoopTest, PhasedLoopRescheduleWithEarlierOffset) {
253+ const chrono::milliseconds kOffset = chrono::milliseconds(400);
254+ const chrono::milliseconds kInterval = chrono::milliseconds(1000);
255+
256+ auto loop1 = MakePrimary();
257+
258+ std::vector<::aos::monotonic_clock::time_point> expected_times;
259+
260+ PhasedLoopHandler *phased_loop;
261+
262+ bool should_exit = false;
263+ TimerHandler *manager_timer = loop1->AddTimer(
264+ [&phased_loop, &loop1, &should_exit, this, kInterval, kOffset]() {
265+ if (should_exit) {
266+ LOG(INFO) << "Exiting";
267+ this->Exit();
268+ return;
269+ }
270+ // Schedule the next callback to be strictly later than the current time
271+ // + interval / 2, to ensure a consistent frequency.
272+ const aos::monotonic_clock::time_point half_time =
273+ loop1->context().monotonic_event_time + kInterval / 2;
274+ phased_loop->set_interval_and_offset(
275+ kInterval, kOffset - std::chrono::nanoseconds(1), half_time);
276+ phased_loop->Reschedule(half_time);
277+ should_exit = true;
278+ });
279+
280+ phased_loop = loop1->AddPhasedLoop(
281+ [&expected_times, &loop1, manager_timer](int count) {
282+ EXPECT_EQ(1, count);
283+ expected_times.push_back(loop1->context().monotonic_event_time);
284+
285+ manager_timer->Setup(loop1->context().monotonic_event_time);
286+ },
287+ kInterval, kOffset);
288+ phased_loop->set_name("Test loop");
289+ manager_timer->set_name("Manager timer");
290+
291+ Run();
292+
293+ ASSERT_EQ(2u, expected_times.size());
294+ ASSERT_EQ(expected_times[0] + kInterval - std::chrono::nanoseconds(1),
295+ expected_times[1]);
296+}
297+
298 // Tests that senders count correctly in the timing report.
299 TEST_P(AbstractEventLoopTest, SenderTimingReport) {
300 gflags::FlagSaver flag_saver;