James Kuszmaul | 20dcc7c | 2023-01-20 11:06:31 -0800 | [diff] [blame] | 1 | diff 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; |