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 = std::make_unique<Watchdog>(bus, TEST_PATH, event,
225                                       Watchdog::ActionTargetMap(),
226                                       std::move(fallback));
227     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
228     EXPECT_FALSE(wdog->enabled());
229     EXPECT_EQ(0, wdog->timeRemaining());
230 
231     // Enable and then verify
232     EXPECT_TRUE(wdog->enabled(true));
233 
234     // Waiting default expiration
235     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
236 
237     // We should now have entered the fallback once the primary expires
238     EXPECT_FALSE(wdog->enabled());
239     auto remaining = milliseconds(wdog->timeRemaining());
240     EXPECT_GE(fallbackInterval, remaining);
241     EXPECT_LT(primaryInterval, remaining);
242     EXPECT_FALSE(wdog->timerExpired());
243     EXPECT_TRUE(wdog->timerEnabled());
244 
245     // We should still be ticking in fallback when setting action or interval
246     auto newInterval = primaryInterval - Quantum(1);
247     auto newIntervalMs = milliseconds(newInterval).count();
248     EXPECT_EQ(newInterval, milliseconds(wdog->interval(newIntervalMs)));
249     EXPECT_EQ(Watchdog::Action::None,
250               wdog->expireAction(Watchdog::Action::None));
251 
252     EXPECT_FALSE(wdog->enabled());
253     EXPECT_GE(remaining, milliseconds(wdog->timeRemaining()));
254     EXPECT_LT(primaryInterval, milliseconds(wdog->timeRemaining()));
255     EXPECT_FALSE(wdog->timerExpired());
256     EXPECT_TRUE(wdog->timerEnabled());
257 
258     // Test that setting the timeRemaining always resets the timer to the
259     // fallback interval
260     EXPECT_EQ(fallback.interval, wdog->timeRemaining(primaryInterval.count()));
261     EXPECT_FALSE(wdog->enabled());
262 
263     remaining = milliseconds(wdog->timeRemaining());
264     EXPECT_GE(fallbackInterval, remaining);
265     EXPECT_LE(fallbackInterval - Quantum(1), remaining);
266     EXPECT_FALSE(wdog->timerExpired());
267     EXPECT_TRUE(wdog->timerEnabled());
268 
269     // Waiting fallback expiration
270     EXPECT_EQ(fallbackInterval - Quantum(1), waitForWatchdog(fallbackInterval));
271 
272     // We should now have disabled the watchdog after the fallback expires
273     EXPECT_FALSE(wdog->enabled());
274     EXPECT_EQ(0, wdog->timeRemaining());
275     EXPECT_TRUE(wdog->timerExpired());
276     EXPECT_FALSE(wdog->timerEnabled());
277 
278     // Make sure enabling the watchdog again works
279     EXPECT_TRUE(wdog->enabled(true));
280 
281     // We should have re-entered the primary
282     EXPECT_TRUE(wdog->enabled());
283     EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
284     EXPECT_FALSE(wdog->timerExpired());
285     EXPECT_TRUE(wdog->timerEnabled());
286 }
287 
288 /** @brief Make sure the watchdog is started and enabled with a fallback
289  *         Wait through the initial trip and ensure the fallback is observed
290  *         Make sure that we can re-enable the watchdog during fallback
291  */
292 TEST_F(WdogTest, enableWdogWithFallbackReEnable)
293 {
294     auto primaryInterval = Quantum(5);
295     auto primaryIntervalMs = milliseconds(primaryInterval).count();
296     auto fallbackInterval = primaryInterval * 2;
297     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
298 
299     // We need to make a wdog with the right fallback options
300     // The interval is set to be noticeably different from the default
301     // so we can always tell the difference
302     Watchdog::Fallback fallback;
303     fallback.action = Watchdog::Action::PowerOff;
304     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
305     fallback.always = false;
306     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
307                                       Watchdog::ActionTargetMap(),
308                                       std::move(fallback));
309     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
310     EXPECT_FALSE(wdog->enabled());
311     EXPECT_EQ(0, wdog->timeRemaining());
312     EXPECT_FALSE(wdog->timerExpired());
313     EXPECT_FALSE(wdog->timerEnabled());
314 
315     // Enable and then verify
316     EXPECT_TRUE(wdog->enabled(true));
317 
318     // Waiting default expiration
319     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
320 
321     // We should now have entered the fallback once the primary expires
322     EXPECT_FALSE(wdog->enabled());
323     auto remaining = milliseconds(wdog->timeRemaining());
324     EXPECT_GE(fallbackInterval, remaining);
325     EXPECT_LT(primaryInterval, remaining);
326     EXPECT_FALSE(wdog->timerExpired());
327     EXPECT_TRUE(wdog->timerEnabled());
328 
329     EXPECT_TRUE(wdog->enabled(true));
330 
331     // We should have re-entered the primary
332     EXPECT_TRUE(wdog->enabled());
333     EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
334     EXPECT_FALSE(wdog->timerExpired());
335     EXPECT_TRUE(wdog->timerEnabled());
336 }
337 
338 /** @brief Make sure the watchdog is started and enabled with a fallback
339  *         Wait through the initial trip and ensure the fallback is observed
340  *         Make sure that changing the primary interval and calling reset timer
341  *         will enable the primary watchdog with primary interval.
342  */
343 TEST_F(WdogTest, enableWdogWithFallbackResetTimerEnable)
344 {
345     auto primaryInterval = Quantum(5);
346     auto primaryIntervalMs = milliseconds(primaryInterval).count();
347     auto fallbackInterval = primaryInterval * 2;
348     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
349     auto newInterval = fallbackInterval * 2;
350     auto newIntervalMs = milliseconds(newInterval).count();
351 
352     // We need to make a wdog with the right fallback options
353     // The interval is set to be noticeably different from the default
354     // so we can always tell the difference
355     Watchdog::Fallback fallback;
356     fallback.action = Watchdog::Action::PowerOff;
357     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
358     fallback.always = false;
359     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
360                                       Watchdog::ActionTargetMap(),
361                                       std::move(fallback));
362     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
363     EXPECT_FALSE(wdog->enabled());
364     EXPECT_EQ(0, wdog->timeRemaining());
365     EXPECT_FALSE(wdog->timerExpired());
366     EXPECT_FALSE(wdog->timerEnabled());
367 
368     // Enable and then verify
369     EXPECT_TRUE(wdog->enabled(true));
370 
371     // Waiting default expiration
372     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
373 
374     // We should now have entered the fallback once the primary expires
375     EXPECT_FALSE(wdog->enabled());
376     auto remaining = milliseconds(wdog->timeRemaining());
377     EXPECT_GE(fallbackInterval, remaining);
378     EXPECT_LT(primaryInterval, remaining);
379     EXPECT_FALSE(wdog->timerExpired());
380     EXPECT_TRUE(wdog->timerEnabled());
381 
382     // Setting the interval should take effect once resetTimer re-enables wdog
383     EXPECT_EQ(newIntervalMs, wdog->interval(newIntervalMs));
384     wdog->resetTimeRemaining(true);
385 
386     // We should have re-entered the primary
387     EXPECT_TRUE(wdog->enabled());
388     remaining = milliseconds(wdog->timeRemaining());
389     EXPECT_GE(newInterval, remaining);
390     EXPECT_LE(newInterval - Quantum(1), remaining);
391     EXPECT_FALSE(wdog->timerExpired());
392     EXPECT_TRUE(wdog->timerEnabled());
393 }
394 
395 /** @brief Make sure the watchdog is started and with a fallback without
396  *         sending an enable
397  *         Then enable the watchdog
398  *         Wait through the initial trip and ensure the fallback is observed
399  *         Make sure that fallback runs to completion and ensure the watchdog
400  *         is in the fallback state again
401  */
402 TEST_F(WdogTest, enableWdogWithFallbackAlways)
403 {
404     auto primaryInterval = Quantum(5);
405     auto primaryIntervalMs = milliseconds(primaryInterval).count();
406     auto fallbackInterval = primaryInterval * 2;
407     auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
408 
409     // We need to make a wdog with the right fallback options
410     // The interval is set to be noticeably different from the default
411     // so we can always tell the difference
412     Watchdog::Fallback fallback;
413     fallback.action = Watchdog::Action::PowerOff;
414     fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
415     fallback.always = true;
416     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
417                                       Watchdog::ActionTargetMap(), fallback,
418                                       milliseconds(TEST_MIN_INTERVAL).count());
419 
420     // Make sure defualt interval is biggger than min interval
421     EXPECT_LT(milliseconds((TEST_MIN_INTERVAL).count()),
422               milliseconds(wdog->interval()));
423 
424     EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
425     EXPECT_FALSE(wdog->enabled());
426     auto remaining = milliseconds(wdog->timeRemaining());
427     EXPECT_GE(fallbackInterval, remaining);
428     EXPECT_LT(primaryInterval, remaining);
429     EXPECT_FALSE(wdog->timerExpired());
430     EXPECT_TRUE(wdog->timerEnabled());
431 
432     // Enable and then verify
433     EXPECT_TRUE(wdog->enabled(true));
434     EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
435 
436     // Waiting default expiration
437     EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
438 
439     // We should now have entered the fallback once the primary expires
440     EXPECT_FALSE(wdog->enabled());
441     remaining = milliseconds(wdog->timeRemaining());
442     EXPECT_GE(fallbackInterval, remaining);
443     EXPECT_LT(primaryInterval, remaining);
444     EXPECT_FALSE(wdog->timerExpired());
445     EXPECT_TRUE(wdog->timerEnabled());
446 
447     // Waiting fallback expiration
448     EXPECT_EQ(fallbackInterval - Quantum(1), waitForWatchdog(fallbackInterval));
449 
450     // We should now enter the fallback again
451     EXPECT_FALSE(wdog->enabled());
452     remaining = milliseconds(wdog->timeRemaining());
453     EXPECT_GE(fallbackInterval, remaining);
454     EXPECT_LT(primaryInterval, remaining);
455     EXPECT_FALSE(wdog->timerExpired());
456     EXPECT_TRUE(wdog->timerEnabled());
457 }
458 
459 /** @brief Test minimal interval
460  *  The minimal interval was set 2 seconds
461  *  Test that when setting interval to 1s , it is still returning 2s
462  */
463 TEST_F(WdogTest, verifyMinIntervalSetting)
464 {
465     auto newInterval = Quantum(1);
466     auto newIntervalMs = milliseconds(newInterval).count();
467     auto minIntervalMs = milliseconds(TEST_MIN_INTERVAL).count();
468 
469     // Check first that the current interval is greater than minInterval
470     EXPECT_LT(minIntervalMs, wdog->interval());
471     // Check that the interval was not set to smaller value than minInterval
472     EXPECT_EQ(minIntervalMs, wdog->interval(newIntervalMs));
473     // Check that the interval was not set to smaller value than minInterval
474     EXPECT_EQ(minIntervalMs, wdog->interval());
475 }
476 
477 /** @brief Test minimal interval
478  *  Initiate default Watchdog in order to get the default
479  *  interval.
480  *  Initiate watchdog with minInterval greater than default
481  *  interval, and make sure the default interval was set to the
482  *  minInterval.
483  */
484 TEST_F(WdogTest, verifyConstructorMinIntervalSetting)
485 {
486     // Initiate default Watchdog and get the default interval value.
487     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event);
488     auto defaultInterval = wdog->interval();
489     auto minInterval = defaultInterval + 100;
490     // We initiate a new Watchdog with min interval greater than the default
491     // intrval
492     wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
493                                       Watchdog::ActionTargetMap(), std::nullopt,
494                                       minInterval);
495     // Check that the interval was set to the minInterval
496     EXPECT_EQ(minInterval, wdog->interval());
497 }
498 
499 } // namespace watchdog
500 } // namespace phosphor
501