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