blob: 4f4a4cc3f329329295bf68cd19b12222651c4481 [file] [log] [blame]
#include "watchdog.hpp"
#include <chrono>
#include <memory>
#include <sdbusplus/bus.hpp>
#include <sdeventplus/event.hpp>
#include <thread>
#include <utility>
#include <gtest/gtest.h>
namespace phosphor
{
namespace watchdog
{
using namespace std::chrono;
using namespace std::chrono_literals;
constexpr auto TEST_MIN_INTERVAL = duration<uint64_t, std::deci>(2);
// Test Watchdog functionality
class WdogTest : public ::testing::Test
{
public:
// The unit time used to measure the timer
// This should be large enough to accomodate drift
using Quantum = duration<uint64_t, std::deci>;
// Gets called as part of each TEST_F construction
WdogTest() :
event(sdeventplus::Event::get_default()),
bus(sdbusplus::bus::new_default()),
wdog(std::make_unique<Watchdog>(
bus, TEST_PATH, event, Watchdog::ActionTargetMap(), std::nullopt,
milliseconds(TEST_MIN_INTERVAL).count())),
defaultInterval(Quantum(3))
{
wdog->interval(milliseconds(defaultInterval).count());
// Initially the watchdog would be disabled
EXPECT_FALSE(wdog->enabled());
}
// sdevent Event handle
sdeventplus::Event event;
// sdbusplus handle
sdbusplus::bus_t bus;
// Watchdog object
std::unique_ptr<Watchdog> wdog;
// This is the default interval as given in Interface definition
Quantum defaultInterval;
protected:
// Dummy name for object path
// This is just to satisfy the constructor. Does not have
// a need to check if the objects paths have been created.
static constexpr auto TEST_PATH = "/test/path";
// Returns how long it took for the current watchdog timer to be
// disabled or have its timeRemaining reset.
Quantum waitForWatchdog(Quantum timeLimit)
{
auto previousTimeRemaining = wdog->timeRemaining();
auto ret = Quantum(0);
while (ret < timeLimit &&
previousTimeRemaining >= wdog->timeRemaining() &&
wdog->timerEnabled())
{
previousTimeRemaining = wdog->timeRemaining();
constexpr auto sleepTime = Quantum(1);
if (event.run(sleepTime) == 0)
{
ret += sleepTime;
}
}
return ret;
}
};
/** @brief Make sure that watchdog is started and not enabled */
TEST_F(WdogTest, createWdogAndDontEnable)
{
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_FALSE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
// We should be able to configure persistent properties
// while disabled
auto newAction = Watchdog::Action::PowerOff;
EXPECT_EQ(newAction, wdog->expireAction(newAction));
auto newIntervalMs = milliseconds(defaultInterval * 2).count();
EXPECT_EQ(newIntervalMs, wdog->interval(newIntervalMs));
EXPECT_EQ(newAction, wdog->expireAction());
EXPECT_EQ(newIntervalMs, wdog->interval());
// We won't be able to configure timeRemaining
EXPECT_EQ(0, wdog->timeRemaining(1000));
EXPECT_EQ(0, wdog->timeRemaining());
// Timer should not have become enabled
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_FALSE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
}
/** @brief Make sure that watchdog is started and enabled */
TEST_F(WdogTest, createWdogAndEnable)
{
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Get the configured interval
auto remaining = milliseconds(wdog->timeRemaining());
// Its possible that we are off by few msecs depending on
// how we get scheduled. So checking a range here.
EXPECT_TRUE((remaining >= defaultInterval - Quantum(1)) &&
(remaining <= defaultInterval));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
/** @brief Make sure that watchdog is started and enabled.
* Later, disable watchdog
*/
TEST_F(WdogTest, createWdogAndEnableThenDisable)
{
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Disable and then verify
EXPECT_FALSE(wdog->enabled(false));
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_FALSE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
}
/** @brief Make sure that watchdog is started and enabled.
* Wait for 5 quantums and make sure that the remaining
* time shows 5 fewer quantums.
*/
TEST_F(WdogTest, enableWdogAndWait5Quantums)
{
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Sleep for 5 quantums
auto sleepTime = Quantum(2);
ASSERT_LT(sleepTime, defaultInterval);
std::this_thread::sleep_for(sleepTime);
// Get the remaining time again and expectation is that we get fewer
auto remaining = milliseconds(wdog->timeRemaining());
auto expected = defaultInterval - sleepTime;
// Its possible that we are off by few msecs depending on
// how we get scheduled. So checking a range here.
EXPECT_TRUE((remaining >= expected - Quantum(1)) &&
(remaining <= expected));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
/** @brief Make sure that watchdog is started and enabled.
* Wait 1 quantum and then reset the timer to 5 quantums
* and then expect the watchdog to expire in 5 quantums
*/
TEST_F(WdogTest, enableWdogAndResetTo5Quantums)
{
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Sleep for 1 second
std::this_thread::sleep_for(Quantum(1));
// Timer should still be running unexpired
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Next timer will expire in 5 quantums from now.
auto expireTime = Quantum(5);
auto expireTimeMs = milliseconds(expireTime).count();
EXPECT_EQ(expireTimeMs, wdog->timeRemaining(expireTimeMs));
// Waiting for expiration
EXPECT_EQ(expireTime - Quantum(1), waitForWatchdog(expireTime));
EXPECT_TRUE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
}
/** @brief Make sure the Interval can be updated directly.
*/
TEST_F(WdogTest, verifyIntervalUpdateReceived)
{
auto expireTime = Quantum(5);
auto expireTimeMs = milliseconds(expireTime).count();
EXPECT_EQ(expireTimeMs, wdog->interval(expireTimeMs));
// Expect an update in the Interval
EXPECT_EQ(expireTimeMs, wdog->interval());
}
/** @brief Make sure the Interval can be updated while the timer is running.
*/
TEST_F(WdogTest, verifyIntervalUpdateRunning)
{
const auto oldInterval = milliseconds(wdog->interval());
const auto newInterval = 5s;
EXPECT_TRUE(wdog->enabled(true));
auto remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(oldInterval, remaining);
EXPECT_LE(oldInterval - Quantum(1), remaining);
EXPECT_EQ(newInterval,
milliseconds(wdog->interval(milliseconds(newInterval).count())));
// Expect only the interval to update
remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(oldInterval, remaining);
EXPECT_LE(oldInterval - Quantum(1), remaining);
EXPECT_EQ(newInterval, milliseconds(wdog->interval()));
// Expect reset to use the new interval
wdog->resetTimeRemaining(false);
remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(newInterval, remaining);
EXPECT_LE(newInterval - Quantum(1), remaining);
}
/** @brief Make sure that watchdog is started and enabled.
* Wait default interval quantums and make sure that wdog has died
*/
TEST_F(WdogTest, enableWdogAndWaitTillEnd)
{
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Waiting default expiration
EXPECT_EQ(defaultInterval - Quantum(1), waitForWatchdog(defaultInterval));
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_TRUE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
}
/** @brief Make sure the watchdog is started and enabled with a fallback
* Wait through the initial trip and ensure the fallback is observed
* Make sure that fallback runs to completion and ensure the watchdog
* is disabled
*/
TEST_F(WdogTest, enableWdogWithFallbackTillEnd)
{
auto primaryInterval = Quantum(5);
auto primaryIntervalMs = milliseconds(primaryInterval).count();
auto fallbackInterval = primaryInterval * 2;
auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
// We need to make a wdog with the right fallback options
// The interval is set to be noticeably different from the default
// so we can always tell the difference
Watchdog::Fallback fallback;
fallback.action = Watchdog::Action::PowerOff;
fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
fallback.always = false;
wdog.reset();
wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
Watchdog::ActionTargetMap(), fallback);
EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Waiting default expiration
EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
// We should now have entered the fallback once the primary expires
EXPECT_FALSE(wdog->enabled());
auto remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LT(primaryInterval, remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// We should still be ticking in fallback when setting action or interval
auto newInterval = primaryInterval - Quantum(1);
auto newIntervalMs = milliseconds(newInterval).count();
EXPECT_EQ(newInterval, milliseconds(wdog->interval(newIntervalMs)));
EXPECT_EQ(Watchdog::Action::None,
wdog->expireAction(Watchdog::Action::None));
EXPECT_FALSE(wdog->enabled());
EXPECT_GE(remaining, milliseconds(wdog->timeRemaining()));
EXPECT_LT(primaryInterval, milliseconds(wdog->timeRemaining()));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Test that setting the timeRemaining always resets the timer to the
// fallback interval
EXPECT_EQ(fallback.interval, wdog->timeRemaining(primaryInterval.count()));
EXPECT_FALSE(wdog->enabled());
remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LE(fallbackInterval - Quantum(1), remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Waiting fallback expiration
EXPECT_EQ(fallbackInterval - Quantum(1), waitForWatchdog(fallbackInterval));
// We should now have disabled the watchdog after the fallback expires
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_TRUE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
// Make sure enabling the watchdog again works
EXPECT_TRUE(wdog->enabled(true));
// We should have re-entered the primary
EXPECT_TRUE(wdog->enabled());
EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
/** @brief Make sure the watchdog is started and enabled with a fallback
* Wait through the initial trip and ensure the fallback is observed
* Make sure that we can re-enable the watchdog during fallback
*/
TEST_F(WdogTest, enableWdogWithFallbackReEnable)
{
auto primaryInterval = Quantum(5);
auto primaryIntervalMs = milliseconds(primaryInterval).count();
auto fallbackInterval = primaryInterval * 2;
auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
// We need to make a wdog with the right fallback options
// The interval is set to be noticeably different from the default
// so we can always tell the difference
Watchdog::Fallback fallback;
fallback.action = Watchdog::Action::PowerOff;
fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
fallback.always = false;
wdog.reset();
wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
Watchdog::ActionTargetMap(), fallback);
EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_FALSE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Waiting default expiration
EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
// We should now have entered the fallback once the primary expires
EXPECT_FALSE(wdog->enabled());
auto remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LT(primaryInterval, remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
EXPECT_TRUE(wdog->enabled(true));
// We should have re-entered the primary
EXPECT_TRUE(wdog->enabled());
EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
/** @brief Make sure the watchdog is started and enabled with a fallback
* Wait through the initial trip and ensure the fallback is observed
* Make sure that changing the primary interval and calling reset timer
* will enable the primary watchdog with primary interval.
*/
TEST_F(WdogTest, enableWdogWithFallbackResetTimerEnable)
{
auto primaryInterval = Quantum(5);
auto primaryIntervalMs = milliseconds(primaryInterval).count();
auto fallbackInterval = primaryInterval * 2;
auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
auto newInterval = fallbackInterval * 2;
auto newIntervalMs = milliseconds(newInterval).count();
// We need to make a wdog with the right fallback options
// The interval is set to be noticeably different from the default
// so we can always tell the difference
Watchdog::Fallback fallback;
fallback.action = Watchdog::Action::PowerOff;
fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
fallback.always = false;
wdog.reset();
wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
Watchdog::ActionTargetMap(), fallback);
EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
EXPECT_FALSE(wdog->enabled());
EXPECT_EQ(0, wdog->timeRemaining());
EXPECT_FALSE(wdog->timerExpired());
EXPECT_FALSE(wdog->timerEnabled());
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
// Waiting default expiration
EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
// We should now have entered the fallback once the primary expires
EXPECT_FALSE(wdog->enabled());
auto remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LT(primaryInterval, remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Setting the interval should take effect once resetTimer re-enables wdog
EXPECT_EQ(newIntervalMs, wdog->interval(newIntervalMs));
wdog->resetTimeRemaining(true);
// We should have re-entered the primary
EXPECT_TRUE(wdog->enabled());
remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(newInterval, remaining);
EXPECT_LE(newInterval - Quantum(1), remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
/** @brief Make sure the watchdog is started and with a fallback without
* sending an enable
* Then enable the watchdog
* Wait through the initial trip and ensure the fallback is observed
* Make sure that fallback runs to completion and ensure the watchdog
* is in the fallback state again
*/
TEST_F(WdogTest, enableWdogWithFallbackAlways)
{
auto primaryInterval = Quantum(5);
auto primaryIntervalMs = milliseconds(primaryInterval).count();
auto fallbackInterval = primaryInterval * 2;
auto fallbackIntervalMs = milliseconds(fallbackInterval).count();
// We need to make a wdog with the right fallback options
// The interval is set to be noticeably different from the default
// so we can always tell the difference
Watchdog::Fallback fallback;
fallback.action = Watchdog::Action::PowerOff;
fallback.interval = static_cast<uint64_t>(fallbackIntervalMs);
fallback.always = true;
wdog.reset();
wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
Watchdog::ActionTargetMap(), fallback,
milliseconds(TEST_MIN_INTERVAL).count());
// Make sure defualt interval is biggger than min interval
EXPECT_LT(milliseconds((TEST_MIN_INTERVAL).count()),
milliseconds(wdog->interval()));
EXPECT_EQ(primaryInterval, milliseconds(wdog->interval(primaryIntervalMs)));
EXPECT_FALSE(wdog->enabled());
auto remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LT(primaryInterval, remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
EXPECT_GE(primaryInterval, milliseconds(wdog->timeRemaining()));
// Waiting default expiration
EXPECT_EQ(primaryInterval - Quantum(1), waitForWatchdog(primaryInterval));
// We should now have entered the fallback once the primary expires
EXPECT_FALSE(wdog->enabled());
remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LT(primaryInterval, remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Waiting fallback expiration
EXPECT_EQ(fallbackInterval - Quantum(1), waitForWatchdog(fallbackInterval));
// We should now enter the fallback again
EXPECT_FALSE(wdog->enabled());
remaining = milliseconds(wdog->timeRemaining());
EXPECT_GE(fallbackInterval, remaining);
EXPECT_LT(primaryInterval, remaining);
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
/** @brief Test minimal interval
* The minimal interval was set 2 seconds
* Test that when setting interval to 1s , it is still returning 2s
*/
TEST_F(WdogTest, verifyMinIntervalSetting)
{
auto newInterval = Quantum(1);
auto newIntervalMs = milliseconds(newInterval).count();
auto minIntervalMs = milliseconds(TEST_MIN_INTERVAL).count();
// Check first that the current interval is greater than minInterval
EXPECT_LT(minIntervalMs, wdog->interval());
// Check that the interval was not set to smaller value than minInterval
EXPECT_EQ(minIntervalMs, wdog->interval(newIntervalMs));
// Check that the interval was not set to smaller value than minInterval
EXPECT_EQ(minIntervalMs, wdog->interval());
}
/** @brief Test minimal interval
* Initiate default Watchdog in order to get the default
* interval.
* Initiate watchdog with minInterval greater than default
* interval, and make sure the default interval was set to the
* minInterval.
*/
TEST_F(WdogTest, verifyConstructorMinIntervalSetting)
{
// Initiate default Watchdog and get the default interval value.
wdog.reset();
wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event);
auto defaultIntervalMs = wdog->interval();
auto defaultInterval = milliseconds(defaultIntervalMs);
auto minInterval = defaultInterval + Quantum(30);
auto minIntervalMs = milliseconds(minInterval).count();
// We initiate a new Watchdog with min interval greater than the default
// intrval
wdog.reset();
wdog = std::make_unique<Watchdog>(bus, TEST_PATH, event,
Watchdog::ActionTargetMap(), std::nullopt,
minIntervalMs);
// Check that the interval was set to the minInterval
EXPECT_EQ(minIntervalMs, wdog->interval());
// Enable and then verify
EXPECT_TRUE(wdog->enabled(true));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
// Set remaining time shorter than minInterval will actually set it to
// minInterval
auto remaining = milliseconds(wdog->timeRemaining(defaultIntervalMs));
// Its possible that we are off by few msecs depending on
// how we get scheduled. So checking a range here.
EXPECT_TRUE((remaining >= minInterval - Quantum(1)) &&
(remaining <= minInterval));
EXPECT_FALSE(wdog->timerExpired());
EXPECT_TRUE(wdog->timerEnabled());
}
} // namespace watchdog
} // namespace phosphor