1 #include "watchdog.hpp"
2 
3 #include <memory>
4 #include <thread>
5 #include <utility>
6 
7 namespace phosphor
8 {
9 namespace watchdog
10 {
11 
12 WdogTest::Quantum WdogTest::waitForWatchdog(Quantum timeLimit)
13 {
14     auto previousTimeRemaining = wdog->timeRemaining();
15     auto ret = Quantum(0);
16     while (ret < timeLimit && previousTimeRemaining >= wdog->timeRemaining() &&
17            wdog->timerEnabled())
18     {
19         previousTimeRemaining = wdog->timeRemaining();
20 
21         constexpr auto sleepTime = Quantum(1);
22         if (event.run(sleepTime) == 0)
23         {
24             ret += sleepTime;
25         }
26     }
27 
28     return ret;
29 }
30 
31 /** @brief Make sure that watchdog is started and not enabled */
32 TEST_F(WdogTest, createWdogAndDontEnable)
33 {
34     EXPECT_FALSE(wdog->enabled());
35     EXPECT_EQ(0, wdog->timeRemaining());
36     EXPECT_FALSE(wdog->timerExpired());
37     EXPECT_FALSE(wdog->timerEnabled());
38 
39     // We should be able to configure persistent properties
40     // while disabled
41     auto newAction = Watchdog::Action::PowerOff;
42     EXPECT_EQ(newAction, wdog->expireAction(newAction));
43     auto newIntervalMs = milliseconds(defaultInterval * 2).count();
44     EXPECT_EQ(newIntervalMs, wdog->interval(newIntervalMs));
45 
46     EXPECT_EQ(newAction, wdog->expireAction());
47     EXPECT_EQ(newIntervalMs, wdog->interval());
48 
49     // We won't be able to configure timeRemaining
50     EXPECT_EQ(0, wdog->timeRemaining(1000));
51     EXPECT_EQ(0, wdog->timeRemaining());
52 
53     // Timer should not have become enabled
54     EXPECT_FALSE(wdog->enabled());
55     EXPECT_EQ(0, wdog->timeRemaining());
56     EXPECT_FALSE(wdog->timerExpired());
57     EXPECT_FALSE(wdog->timerEnabled());
58 }
59 
60 /** @brief Make sure that watchdog is started and enabled */
61 TEST_F(WdogTest, createWdogAndEnable)
62 {
63     // Enable and then verify
64     EXPECT_TRUE(wdog->enabled(true));
65     EXPECT_FALSE(wdog->timerExpired());
66     EXPECT_TRUE(wdog->timerEnabled());
67 
68     // Get the configured interval
69     auto remaining = milliseconds(wdog->timeRemaining());
70 
71     // Its possible that we are off by few msecs depending on
72     // how we get scheduled. So checking a range here.
73     EXPECT_TRUE((remaining >= defaultInterval - Quantum(1)) &&
74                 (remaining <= defaultInterval));
75 
76     EXPECT_FALSE(wdog->timerExpired());
77     EXPECT_TRUE(wdog->timerEnabled());
78 }
79 
80 /** @brief Make sure that watchdog is started and enabled.
81  *         Later, disable watchdog
82  */
83 TEST_F(WdogTest, createWdogAndEnableThenDisable)
84 {
85     // Enable and then verify
86     EXPECT_TRUE(wdog->enabled(true));
87 
88     // Disable and then verify
89     EXPECT_FALSE(wdog->enabled(false));
90     EXPECT_FALSE(wdog->enabled());
91     EXPECT_EQ(0, wdog->timeRemaining());
92     EXPECT_FALSE(wdog->timerExpired());
93     EXPECT_FALSE(wdog->timerEnabled());
94 }
95 
96 /** @brief Make sure that watchdog is started and enabled.
97  *         Wait for 5 quantums and make sure that the remaining
98  *         time shows 5 fewer quantums.
99  */
100 TEST_F(WdogTest, enableWdogAndWait5Quantums)
101 {
102     // Enable and then verify
103     EXPECT_TRUE(wdog->enabled(true));
104 
105     // Sleep for 5 quantums
106     auto sleepTime = Quantum(2);
107     ASSERT_LT(sleepTime, defaultInterval);
108     std::this_thread::sleep_for(sleepTime);
109 
110     // Get the remaining time again and expectation is that we get fewer
111     auto remaining = milliseconds(wdog->timeRemaining());
112     auto expected = defaultInterval - sleepTime;
113 
114     // Its possible that we are off by few msecs depending on
115     // how we get scheduled. So checking a range here.
116     EXPECT_TRUE((remaining >= expected - Quantum(1)) &&
117                 (remaining <= expected));
118     EXPECT_FALSE(wdog->timerExpired());
119     EXPECT_TRUE(wdog->timerEnabled());
120 }
121 
122 /** @brief Make sure that watchdog is started and enabled.
123  *         Wait 1 quantum and then reset the timer to 5 quantums
124  *         and then expect the watchdog to expire in 5 quantums
125  */
126 TEST_F(WdogTest, enableWdogAndResetTo5Quantums)
127 {
128     // Enable and then verify
129     EXPECT_TRUE(wdog->enabled(true));
130 
131     // Sleep for 1 second
132     std::this_thread::sleep_for(Quantum(1));
133 
134     // Timer should still be running unexpired
135     EXPECT_FALSE(wdog->timerExpired());
136     EXPECT_TRUE(wdog->timerEnabled());
137 
138     // Next timer will expire in 5 quantums from now.
139     auto expireTime = Quantum(5);
140     auto expireTimeMs = milliseconds(expireTime).count();
141     EXPECT_EQ(expireTimeMs, wdog->timeRemaining(expireTimeMs));
142 
143     // Waiting for expiration
144     EXPECT_EQ(expireTime - Quantum(1), waitForWatchdog(expireTime));
145     EXPECT_TRUE(wdog->timerExpired());
146     EXPECT_FALSE(wdog->timerEnabled());
147 }
148 
149 /** @brief Make sure the Interval can be updated directly.
150  */
151 TEST_F(WdogTest, verifyIntervalUpdateReceived)
152 {
153     auto expireTime = Quantum(5);
154     auto expireTimeMs = milliseconds(expireTime).count();
155     EXPECT_EQ(expireTimeMs, wdog->interval(expireTimeMs));
156 
157     // Expect an update in the Interval
158     EXPECT_EQ(expireTimeMs, wdog->interval());
159 }
160 
161 /** @brief Make sure the Interval can be updated while the timer is running.
162  */
163 TEST_F(WdogTest, verifyIntervalUpdateRunning)
164 {
165     const auto oldInterval = milliseconds(wdog->interval());
166     const auto newInterval = 5s;
167 
168     EXPECT_TRUE(wdog->enabled(true));
169     auto remaining = milliseconds(wdog->timeRemaining());
170     EXPECT_GE(oldInterval, remaining);
171     EXPECT_LE(oldInterval - Quantum(1), remaining);
172     EXPECT_EQ(newInterval,
173               milliseconds(wdog->interval(milliseconds(newInterval).count())));
174 
175     // Expect only the interval to update
176     remaining = milliseconds(wdog->timeRemaining());
177     EXPECT_GE(oldInterval, remaining);
178     EXPECT_LE(oldInterval - Quantum(1), remaining);
179     EXPECT_EQ(newInterval, milliseconds(wdog->interval()));
180 
181     // Expect reset to use the new interval
182     wdog->resetTimeRemaining(false);
183     remaining = milliseconds(wdog->timeRemaining());
184     EXPECT_GE(newInterval, remaining);
185     EXPECT_LE(newInterval - Quantum(1), remaining);
186 }
187 
188 /** @brief Make sure that watchdog is started and enabled.
189  *         Wait default interval quantums and make sure that wdog has died
190  */
191 TEST_F(WdogTest, enableWdogAndWaitTillEnd)
192 {
193     // Enable and then verify
194     EXPECT_TRUE(wdog->enabled(true));
195 
196     // Waiting default expiration
197     EXPECT_EQ(defaultInterval - Quantum(1), waitForWatchdog(defaultInterval));
198 
199     EXPECT_FALSE(wdog->enabled());
200     EXPECT_EQ(0, wdog->timeRemaining());
201     EXPECT_TRUE(wdog->timerExpired());
202     EXPECT_FALSE(wdog->timerEnabled());
203 }
204 
205 /** @brief Make sure the watchdog is started and enabled with a fallback
206  *         Wait through the initial trip and ensure the fallback is observed
207  *         Make sure that fallback runs to completion and ensure the watchdog
208  *         is disabled
209  */
210 TEST_F(WdogTest, enableWdogWithFallbackTillEnd)
211 {
212     auto primaryInterval = Quantum(5);
213     auto primaryIntervalMs = milliseconds(primaryInterval).count();
214     auto fallbackInterval = primaryInterval * 2;
215     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
216 
217     // We need to make a wdog with the right fallback options
218     // The interval is set to be noticeably different from the default
219     // so we can always tell the difference
220     Watchdog::Fallback fallback;
221     fallback.action = Watchdog::Action::PowerOff;
222     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
223     fallback.always = false;
224     wdog.reset();
225     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
226                                       Watchdog::ActionTargetMap(),
227                                       std::move(fallback));
228     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
229     EXPECT_FALSE(wdog->enabled());
230     EXPECT_EQ(0, wdog->timeRemaining());
231 
232     // Enable and then verify
233     EXPECT_TRUE(wdog->enabled(true));
234 
235     // Waiting default expiration
236     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
237 
238     // We should now have entered the fallback once the primary expires
239     EXPECT_FALSE(wdog->enabled());
240     auto remaining = milliseconds(wdog->timeRemaining());
241     EXPECT_GE(fallbackInterval, remaining);
242     EXPECT_LT(primaryInterval, remaining);
243     EXPECT_FALSE(wdog->timerExpired());
244     EXPECT_TRUE(wdog->timerEnabled());
245 
246     // We should still be ticking in fallback when setting action or interval
247     auto newInterval = primaryInterval - Quantum(1);
248     auto newIntervalMs = milliseconds(newInterval).count();
249     EXPECT_EQ(newInterval, milliseconds(wdog->interval(newIntervalMs)));
250     EXPECT_EQ(Watchdog::Action::None,
251               wdog->expireAction(Watchdog::Action::None));
252 
253     EXPECT_FALSE(wdog->enabled());
254     EXPECT_GE(remaining, milliseconds(wdog->timeRemaining()));
255     EXPECT_LT(primaryInterval, milliseconds(wdog->timeRemaining()));
256     EXPECT_FALSE(wdog->timerExpired());
257     EXPECT_TRUE(wdog->timerEnabled());
258 
259     // Test that setting the timeRemaining always resets the timer to the
260     // fallback interval
261     EXPECT_EQ(fallback.interval, wdog->timeRemaining(primaryInterval.count()));
262     EXPECT_FALSE(wdog->enabled());
263 
264     remaining = milliseconds(wdog->timeRemaining());
265     EXPECT_GE(fallbackInterval, remaining);
266     EXPECT_LE(fallbackInterval - Quantum(1), remaining);
267     EXPECT_FALSE(wdog->timerExpired());
268     EXPECT_TRUE(wdog->timerEnabled());
269 
270     // Waiting fallback expiration
271     EXPECT_EQ(fallbackInterval - Quantum(1), waitForWatchdog(fallbackInterval));
272 
273     // We should now have disabled the watchdog after the fallback expires
274     EXPECT_FALSE(wdog->enabled());
275     EXPECT_EQ(0, wdog->timeRemaining());
276     EXPECT_TRUE(wdog->timerExpired());
277     EXPECT_FALSE(wdog->timerEnabled());
278 
279     // Make sure enabling the watchdog again works
280     EXPECT_TRUE(wdog->enabled(true));
281 
282     // We should have re-entered the primary
283     EXPECT_TRUE(wdog->enabled());
284     EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
285     EXPECT_FALSE(wdog->timerExpired());
286     EXPECT_TRUE(wdog->timerEnabled());
287 }
288 
289 /** @brief Make sure the watchdog is started and enabled with a fallback
290  *         Wait through the initial trip and ensure the fallback is observed
291  *         Make sure that we can re-enable the watchdog during fallback
292  */
293 TEST_F(WdogTest, enableWdogWithFallbackReEnable)
294 {
295     auto primaryInterval = Quantum(5);
296     auto primaryIntervalMs = milliseconds(primaryInterval).count();
297     auto fallbackInterval = primaryInterval * 2;
298     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
299 
300     // We need to make a wdog with the right fallback options
301     // The interval is set to be noticeably different from the default
302     // so we can always tell the difference
303     Watchdog::Fallback fallback;
304     fallback.action = Watchdog::Action::PowerOff;
305     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
306     fallback.always = false;
307     wdog.reset();
308     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
309                                       Watchdog::ActionTargetMap(),
310                                       std::move(fallback));
311     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
312     EXPECT_FALSE(wdog->enabled());
313     EXPECT_EQ(0, wdog->timeRemaining());
314     EXPECT_FALSE(wdog->timerExpired());
315     EXPECT_FALSE(wdog->timerEnabled());
316 
317     // Enable and then verify
318     EXPECT_TRUE(wdog->enabled(true));
319 
320     // Waiting default expiration
321     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
322 
323     // We should now have entered the fallback once the primary expires
324     EXPECT_FALSE(wdog->enabled());
325     auto remaining = milliseconds(wdog->timeRemaining());
326     EXPECT_GE(fallbackInterval, remaining);
327     EXPECT_LT(primaryInterval, remaining);
328     EXPECT_FALSE(wdog->timerExpired());
329     EXPECT_TRUE(wdog->timerEnabled());
330 
331     EXPECT_TRUE(wdog->enabled(true));
332 
333     // We should have re-entered the primary
334     EXPECT_TRUE(wdog->enabled());
335     EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
336     EXPECT_FALSE(wdog->timerExpired());
337     EXPECT_TRUE(wdog->timerEnabled());
338 }
339 
340 /** @brief Make sure the watchdog is started and enabled with a fallback
341  *         Wait through the initial trip and ensure the fallback is observed
342  *         Make sure that changing the primary interval and calling reset timer
343  *         will enable the primary watchdog with primary interval.
344  */
345 TEST_F(WdogTest, enableWdogWithFallbackResetTimerEnable)
346 {
347     auto primaryInterval = Quantum(5);
348     auto primaryIntervalMs = milliseconds(primaryInterval).count();
349     auto fallbackInterval = primaryInterval * 2;
350     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
351     auto newInterval = fallbackInterval * 2;
352     auto newIntervalMs = milliseconds(newInterval).count();
353 
354     // We need to make a wdog with the right fallback options
355     // The interval is set to be noticeably different from the default
356     // so we can always tell the difference
357     Watchdog::Fallback fallback;
358     fallback.action = Watchdog::Action::PowerOff;
359     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
360     fallback.always = false;
361     wdog.reset();
362     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
363                                       Watchdog::ActionTargetMap(),
364                                       std::move(fallback));
365     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
366     EXPECT_FALSE(wdog->enabled());
367     EXPECT_EQ(0, wdog->timeRemaining());
368     EXPECT_FALSE(wdog->timerExpired());
369     EXPECT_FALSE(wdog->timerEnabled());
370 
371     // Enable and then verify
372     EXPECT_TRUE(wdog->enabled(true));
373 
374     // Waiting default expiration
375     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
376 
377     // We should now have entered the fallback once the primary expires
378     EXPECT_FALSE(wdog->enabled());
379     auto remaining = milliseconds(wdog->timeRemaining());
380     EXPECT_GE(fallbackInterval, remaining);
381     EXPECT_LT(primaryInterval, remaining);
382     EXPECT_FALSE(wdog->timerExpired());
383     EXPECT_TRUE(wdog->timerEnabled());
384 
385     // Setting the interval should take effect once resetTimer re-enables wdog
386     EXPECT_EQ(newIntervalMs, wdog->interval(newIntervalMs));
387     wdog->resetTimeRemaining(true);
388 
389     // We should have re-entered the primary
390     EXPECT_TRUE(wdog->enabled());
391     remaining = milliseconds(wdog->timeRemaining());
392     EXPECT_GE(newInterval, remaining);
393     EXPECT_LE(newInterval - Quantum(1), remaining);
394     EXPECT_FALSE(wdog->timerExpired());
395     EXPECT_TRUE(wdog->timerEnabled());
396 }
397 
398 /** @brief Make sure the watchdog is started and with a fallback without
399  *         sending an enable
400  *         Then enable the watchdog
401  *         Wait through the initial trip and ensure the fallback is observed
402  *         Make sure that fallback runs to completion and ensure the watchdog
403  *         is in the fallback state again
404  */
405 TEST_F(WdogTest, enableWdogWithFallbackAlways)
406 {
407     auto primaryInterval = Quantum(5);
408     auto primaryIntervalMs = milliseconds(primaryInterval).count();
409     auto fallbackInterval = primaryInterval * 2;
410     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
411 
412     // We need to make a wdog with the right fallback options
413     // The interval is set to be noticeably different from the default
414     // so we can always tell the difference
415     Watchdog::Fallback fallback;
416     fallback.action = Watchdog::Action::PowerOff;
417     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
418     fallback.always = true;
419     wdog.reset();
420     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
421                                       Watchdog::ActionTargetMap(), fallback,
422                                       milliseconds(TEST_MIN_INTERVAL).count());
423 
424     // Make sure defualt interval is biggger than min interval
425     EXPECT_LT(milliseconds((TEST_MIN_INTERVAL).count()),
426               milliseconds(wdog->interval()));
427 
428     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
429     EXPECT_FALSE(wdog->enabled());
430     auto remaining = milliseconds(wdog->timeRemaining());
431     EXPECT_GE(fallbackInterval, remaining);
432     EXPECT_LT(primaryInterval, remaining);
433     EXPECT_FALSE(wdog->timerExpired());
434     EXPECT_TRUE(wdog->timerEnabled());
435 
436     // Enable and then verify
437     EXPECT_TRUE(wdog->enabled(true));
438     EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
439 
440     // Waiting default expiration
441     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
442 
443     // We should now have entered the fallback once the primary expires
444     EXPECT_FALSE(wdog->enabled());
445     remaining = milliseconds(wdog->timeRemaining());
446     EXPECT_GE(fallbackInterval, remaining);
447     EXPECT_LT(primaryInterval, remaining);
448     EXPECT_FALSE(wdog->timerExpired());
449     EXPECT_TRUE(wdog->timerEnabled());
450 
451     // Waiting fallback expiration
452     EXPECT_EQ(fallbackInterval - Quantum(1), waitForWatchdog(fallbackInterval));
453 
454     // We should now enter the fallback again
455     EXPECT_FALSE(wdog->enabled());
456     remaining = milliseconds(wdog->timeRemaining());
457     EXPECT_GE(fallbackInterval, remaining);
458     EXPECT_LT(primaryInterval, remaining);
459     EXPECT_FALSE(wdog->timerExpired());
460     EXPECT_TRUE(wdog->timerEnabled());
461 }
462 
463 /** @brief Test minimal interval
464  *  The minimal interval was set 2 seconds
465  *  Test that when setting interval to 1s , it is still returning 2s
466  */
467 TEST_F(WdogTest, verifyMinIntervalSetting)
468 {
469     auto newInterval = Quantum(1);
470     auto newIntervalMs = milliseconds(newInterval).count();
471     auto minIntervalMs = milliseconds(TEST_MIN_INTERVAL).count();
472 
473     // Check first that the current interval is greater than minInterval
474     EXPECT_LT(minIntervalMs, wdog->interval());
475     // Check that the interval was not set to smaller value than minInterval
476     EXPECT_EQ(minIntervalMs, wdog->interval(newIntervalMs));
477     // Check that the interval was not set to smaller value than minInterval
478     EXPECT_EQ(minIntervalMs, wdog->interval());
479 }
480 
481 /** @brief Test minimal interval
482  *  Initiate default Watchdog in order to get the default
483  *  interval.
484  *  Initiate watchdog with minInterval greater than default
485  *  interval, and make sure the default interval was set to the
486  *  minInterval.
487  */
488 TEST_F(WdogTest, verifyConstructorMinIntervalSetting)
489 {
490     // Initiate default Watchdog and get the default interval value.
491     wdog.reset();
492     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event);
493     auto defaultIntervalMs = wdog->interval();
494     auto defaultInterval = milliseconds(defaultIntervalMs);
495     auto minInterval = defaultInterval + Quantum(30);
496     auto minIntervalMs = milliseconds(minInterval).count();
497 
498     // We initiate a new Watchdog with min interval greater than the default
499     // intrval
500     wdog.reset();
501     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
502                                       Watchdog::ActionTargetMap(), std::nullopt,
503                                       minIntervalMs);
504     // Check that the interval was set to the minInterval
505     EXPECT_EQ(minIntervalMs, wdog->interval());
506 
507     // Enable and then verify
508     EXPECT_TRUE(wdog->enabled(true));
509     EXPECT_FALSE(wdog->timerExpired());
510     EXPECT_TRUE(wdog->timerEnabled());
511 
512     // Set remaining time shorter than minInterval will actually set it to
513     // minInterval
514     auto remaining = milliseconds(wdog->timeRemaining(defaultIntervalMs));
515 
516     // Its possible that we are off by few msecs depending on
517     // how we get scheduled. So checking a range here.
518     EXPECT_TRUE((remaining >= minInterval - Quantum(1)) &&
519                 (remaining <= minInterval));
520 
521     EXPECT_FALSE(wdog->timerExpired());
522     EXPECT_TRUE(wdog->timerEnabled());
523 }
524 
525 } // namespace watchdog
526 } // namespace phosphor
527