1 #include <chrono> 2 #include <gmock/gmock.h> 3 #include <gtest/gtest.h> 4 #include <memory> 5 #include <optional> 6 #include <sdeventplus/clock.hpp> 7 #include <sdeventplus/event.hpp> 8 #include <sdeventplus/test/sdevent.hpp> 9 #include <sdeventplus/utility/timer.hpp> 10 #include <stdexcept> 11 #include <systemd/sd-event.h> 12 13 namespace sdeventplus 14 { 15 namespace utility 16 { 17 namespace 18 { 19 20 constexpr ClockId testClock = ClockId::Monotonic; 21 22 using std::chrono::microseconds; 23 using std::chrono::milliseconds; 24 using testing::DoAll; 25 using testing::Return; 26 using testing::SaveArg; 27 using testing::SetArgPointee; 28 using TestTimer = Timer<testClock>; 29 30 ssize_t event_ref_times = 0; 31 32 ACTION(EventRef) 33 { 34 event_ref_times++; 35 } 36 37 ACTION(EventUnref) 38 { 39 ASSERT_LT(0, event_ref_times); 40 event_ref_times--; 41 } 42 43 class TimerTest : public testing::Test 44 { 45 protected: 46 testing::StrictMock<test::SdEventMock> mock; 47 sd_event* const expected_event = reinterpret_cast<sd_event*>(1234); 48 sd_event_source* const expected_source = 49 reinterpret_cast<sd_event_source*>(2345); 50 const milliseconds interval{134}; 51 const milliseconds starting_time{10}; 52 sd_event_time_handler_t handler = nullptr; 53 void* handler_userdata; 54 std::unique_ptr<Event> event; 55 std::unique_ptr<TestTimer> timer; 56 std::function<void()> callback; 57 58 void expectNow(microseconds ret) 59 { 60 EXPECT_CALL(mock, 61 sd_event_now(expected_event, 62 static_cast<clockid_t>(testClock), testing::_)) 63 .WillOnce(DoAll(SetArgPointee<2>(ret.count()), Return(0))); 64 } 65 66 void expectSetTime(microseconds time) 67 { 68 EXPECT_CALL(mock, 69 sd_event_source_set_time(expected_source, time.count())) 70 .WillOnce(Return(0)); 71 } 72 73 void expectSetEnabled(source::Enabled enabled) 74 { 75 EXPECT_CALL(mock, sd_event_source_set_enabled( 76 expected_source, static_cast<int>(enabled))) 77 .WillOnce(Return(0)); 78 } 79 80 void expectGetEnabled(source::Enabled enabled) 81 { 82 EXPECT_CALL(mock, 83 sd_event_source_get_enabled(expected_source, testing::_)) 84 .WillOnce( 85 DoAll(SetArgPointee<1>(static_cast<int>(enabled)), Return(0))); 86 } 87 88 void resetTimer() 89 { 90 if (timer) 91 { 92 expectSetEnabled(source::Enabled::Off); 93 timer.reset(); 94 } 95 } 96 97 void expireTimer() 98 { 99 const milliseconds new_time(90); 100 expectNow(new_time); 101 expectSetTime(new_time + interval); 102 EXPECT_EQ(0, handler(nullptr, 0, handler_userdata)); 103 EXPECT_TRUE(timer->hasExpired()); 104 EXPECT_EQ(interval, timer->getInterval()); 105 } 106 107 void SetUp() 108 { 109 EXPECT_CALL(mock, sd_event_ref(expected_event)) 110 .WillRepeatedly(DoAll(EventRef(), Return(expected_event))); 111 EXPECT_CALL(mock, sd_event_unref(expected_event)) 112 .WillRepeatedly(DoAll(EventUnref(), Return(nullptr))); 113 event = std::make_unique<Event>(expected_event, &mock); 114 EXPECT_CALL(mock, sd_event_source_unref(expected_source)) 115 .WillRepeatedly(Return(nullptr)); 116 EXPECT_CALL(mock, 117 sd_event_source_set_userdata(expected_source, testing::_)) 118 .WillRepeatedly( 119 DoAll(SaveArg<1>(&handler_userdata), Return(nullptr))); 120 121 // Having a callback proxy allows us to update the test callback 122 // dynamically, without changing it inside the timer 123 auto runCallback = [&](TestTimer&) { 124 if (callback) 125 { 126 callback(); 127 } 128 }; 129 expectNow(starting_time); 130 EXPECT_CALL(mock, sd_event_add_time( 131 expected_event, testing::_, 132 static_cast<clockid_t>(testClock), 133 microseconds(starting_time + interval).count(), 134 1000, testing::_, nullptr)) 135 .WillOnce(DoAll(SetArgPointee<1>(expected_source), 136 SaveArg<5>(&handler), Return(0))); 137 expectSetEnabled(source::Enabled::On); 138 timer = std::make_unique<TestTimer>(*event, runCallback, interval); 139 } 140 141 void TearDown() 142 { 143 resetTimer(); 144 event.reset(); 145 EXPECT_EQ(0, event_ref_times); 146 } 147 }; 148 149 TEST_F(TimerTest, NoCallback) 150 { 151 resetTimer(); 152 expectNow(starting_time); 153 EXPECT_CALL( 154 mock, sd_event_add_time(expected_event, testing::_, 155 static_cast<clockid_t>(testClock), 156 microseconds(starting_time + interval).count(), 157 1000, testing::_, nullptr)) 158 .WillOnce(DoAll(SetArgPointee<1>(expected_source), SaveArg<5>(&handler), 159 Return(0))); 160 expectSetEnabled(source::Enabled::On); 161 timer = std::make_unique<TestTimer>(*event, nullptr, interval); 162 163 expectNow(starting_time); 164 expectSetTime(starting_time + interval); 165 EXPECT_EQ(0, handler(nullptr, 0, handler_userdata)); 166 } 167 168 TEST_F(TimerTest, NoInterval) 169 { 170 resetTimer(); 171 expectNow(starting_time); 172 EXPECT_CALL(mock, sd_event_add_time(expected_event, testing::_, 173 static_cast<clockid_t>(testClock), 174 microseconds(starting_time).count(), 175 1000, testing::_, nullptr)) 176 .WillOnce(DoAll(SetArgPointee<1>(expected_source), SaveArg<5>(&handler), 177 Return(0))); 178 expectSetEnabled(source::Enabled::Off); 179 timer = std::make_unique<TestTimer>(*event, nullptr); 180 181 EXPECT_EQ(std::nullopt, timer->getInterval()); 182 EXPECT_THROW(timer->setEnabled(true), std::runtime_error); 183 } 184 185 TEST_F(TimerTest, NewTimer) 186 { 187 EXPECT_FALSE(timer->hasExpired()); 188 EXPECT_EQ(interval, timer->getInterval()); 189 } 190 191 TEST_F(TimerTest, IsEnabled) 192 { 193 expectGetEnabled(source::Enabled::On); 194 EXPECT_TRUE(timer->isEnabled()); 195 expectGetEnabled(source::Enabled::Off); 196 EXPECT_FALSE(timer->isEnabled()); 197 } 198 199 TEST_F(TimerTest, GetRemainingDisabled) 200 { 201 expectGetEnabled(source::Enabled::Off); 202 EXPECT_THROW(timer->getRemaining(), std::runtime_error); 203 } 204 205 TEST_F(TimerTest, GetRemainingNegative) 206 { 207 milliseconds now(675), end(453); 208 expectGetEnabled(source::Enabled::On); 209 EXPECT_CALL(mock, sd_event_source_get_time(expected_source, testing::_)) 210 .WillOnce( 211 DoAll(SetArgPointee<1>(microseconds(end).count()), Return(0))); 212 expectNow(now); 213 EXPECT_EQ(milliseconds(0), timer->getRemaining()); 214 } 215 216 TEST_F(TimerTest, GetRemainingPositive) 217 { 218 milliseconds now(453), end(675); 219 expectGetEnabled(source::Enabled::On); 220 EXPECT_CALL(mock, sd_event_source_get_time(expected_source, testing::_)) 221 .WillOnce( 222 DoAll(SetArgPointee<1>(microseconds(end).count()), Return(0))); 223 expectNow(now); 224 EXPECT_EQ(end - now, timer->getRemaining()); 225 } 226 227 TEST_F(TimerTest, SetEnabled) 228 { 229 expectSetEnabled(source::Enabled::On); 230 timer->setEnabled(true); 231 EXPECT_FALSE(timer->hasExpired()); 232 // Value should always be passed through regardless of current state 233 expectSetEnabled(source::Enabled::On); 234 timer->setEnabled(true); 235 EXPECT_FALSE(timer->hasExpired()); 236 237 expectSetEnabled(source::Enabled::Off); 238 timer->setEnabled(false); 239 EXPECT_FALSE(timer->hasExpired()); 240 // Value should always be passed through regardless of current state 241 expectSetEnabled(source::Enabled::Off); 242 timer->setEnabled(false); 243 EXPECT_FALSE(timer->hasExpired()); 244 } 245 246 TEST_F(TimerTest, SetEnabledUnsetTimer) 247 { 248 // Force the timer to become unset 249 expectSetEnabled(source::Enabled::Off); 250 timer->restart(std::nullopt); 251 252 // Setting an interval should not update the timer directly 253 timer->setInterval(milliseconds(90)); 254 255 expectSetEnabled(source::Enabled::Off); 256 timer->setEnabled(false); 257 EXPECT_THROW(timer->setEnabled(true), std::runtime_error); 258 } 259 260 TEST_F(TimerTest, SetEnabledOneshot) 261 { 262 // Timer effectively becomes oneshot if it gets initialized but has 263 // the interval removed 264 timer->setInterval(std::nullopt); 265 266 expectSetEnabled(source::Enabled::Off); 267 timer->setEnabled(false); 268 expectSetEnabled(source::Enabled::On); 269 timer->setEnabled(true); 270 } 271 272 TEST_F(TimerTest, SetRemaining) 273 { 274 const milliseconds now(90), remaining(30); 275 expectNow(now); 276 expectSetTime(now + remaining); 277 timer->setRemaining(remaining); 278 EXPECT_EQ(interval, timer->getInterval()); 279 EXPECT_FALSE(timer->hasExpired()); 280 } 281 282 TEST_F(TimerTest, ResetRemaining) 283 { 284 const milliseconds now(90); 285 expectNow(now); 286 expectSetTime(now + interval); 287 timer->resetRemaining(); 288 EXPECT_EQ(interval, timer->getInterval()); 289 EXPECT_FALSE(timer->hasExpired()); 290 } 291 292 TEST_F(TimerTest, SetInterval) 293 { 294 const milliseconds new_interval(40); 295 timer->setInterval(new_interval); 296 EXPECT_EQ(new_interval, timer->getInterval()); 297 EXPECT_FALSE(timer->hasExpired()); 298 } 299 300 TEST_F(TimerTest, SetIntervalEmpty) 301 { 302 timer->setInterval(std::nullopt); 303 EXPECT_EQ(std::nullopt, timer->getInterval()); 304 EXPECT_FALSE(timer->hasExpired()); 305 } 306 307 TEST_F(TimerTest, CallbackHappensLast) 308 { 309 const milliseconds new_time(90); 310 expectNow(new_time); 311 expectSetTime(new_time + interval); 312 callback = [&]() { 313 EXPECT_TRUE(timer->hasExpired()); 314 expectSetEnabled(source::Enabled::On); 315 timer->setEnabled(true); 316 timer->clearExpired(); 317 timer->setInterval(std::nullopt); 318 }; 319 EXPECT_EQ(0, handler(nullptr, 0, handler_userdata)); 320 EXPECT_FALSE(timer->hasExpired()); 321 EXPECT_EQ(std::nullopt, timer->getInterval()); 322 expectSetEnabled(source::Enabled::On); 323 timer->setEnabled(true); 324 } 325 326 TEST_F(TimerTest, CallbackOneshot) 327 { 328 // Make sure we try a one shot so we can test the callback 329 // correctly 330 timer->setInterval(std::nullopt); 331 332 expectSetEnabled(source::Enabled::Off); 333 callback = [&]() { 334 EXPECT_TRUE(timer->hasExpired()); 335 EXPECT_THROW(timer->setEnabled(true), std::runtime_error); 336 timer->setInterval(interval); 337 }; 338 EXPECT_EQ(0, handler(nullptr, 0, handler_userdata)); 339 EXPECT_THROW(timer->setEnabled(true), std::runtime_error); 340 } 341 342 TEST_F(TimerTest, SetValuesExpiredTimer) 343 { 344 const milliseconds new_time(90); 345 expectNow(new_time); 346 expectSetTime(new_time + interval); 347 EXPECT_EQ(0, handler(nullptr, 0, handler_userdata)); 348 EXPECT_TRUE(timer->hasExpired()); 349 EXPECT_EQ(interval, timer->getInterval()); 350 351 // Timer should remain expired unless clearExpired() or reset() 352 expectSetEnabled(source::Enabled::On); 353 timer->setEnabled(true); 354 EXPECT_TRUE(timer->hasExpired()); 355 expectNow(milliseconds(20)); 356 expectSetTime(milliseconds(50)); 357 timer->setRemaining(milliseconds(30)); 358 EXPECT_TRUE(timer->hasExpired()); 359 timer->setInterval(milliseconds(10)); 360 EXPECT_TRUE(timer->hasExpired()); 361 expectNow(milliseconds(20)); 362 expectSetTime(milliseconds(30)); 363 timer->resetRemaining(); 364 EXPECT_TRUE(timer->hasExpired()); 365 366 timer->clearExpired(); 367 EXPECT_FALSE(timer->hasExpired()); 368 } 369 370 TEST_F(TimerTest, Restart) 371 { 372 expireTimer(); 373 374 const milliseconds new_interval(471); 375 expectNow(starting_time); 376 expectSetTime(starting_time + new_interval); 377 expectSetEnabled(source::Enabled::On); 378 timer->restart(new_interval); 379 EXPECT_FALSE(timer->hasExpired()); 380 EXPECT_EQ(new_interval, timer->getInterval()); 381 expectSetEnabled(source::Enabled::On); 382 timer->setEnabled(true); 383 } 384 385 TEST_F(TimerTest, RestartEmpty) 386 { 387 expireTimer(); 388 389 expectSetEnabled(source::Enabled::Off); 390 timer->restart(std::nullopt); 391 EXPECT_FALSE(timer->hasExpired()); 392 EXPECT_EQ(std::nullopt, timer->getInterval()); 393 EXPECT_THROW(timer->setEnabled(true), std::runtime_error); 394 } 395 396 TEST_F(TimerTest, RestartOnce) 397 { 398 expireTimer(); 399 400 const milliseconds remaining(471); 401 expectNow(starting_time); 402 expectSetTime(starting_time + remaining); 403 expectSetEnabled(source::Enabled::On); 404 timer->restartOnce(remaining); 405 EXPECT_FALSE(timer->hasExpired()); 406 EXPECT_EQ(std::nullopt, timer->getInterval()); 407 expectSetEnabled(source::Enabled::On); 408 timer->setEnabled(true); 409 } 410 411 } // namespace 412 } // namespace utility 413 } // namespace sdeventplus 414