Implement HostEpoch set time logic

1. When setting host epoch, follow below logic:

        Mode  | Owner | Set Host Time
        ----- | ----- | -------------
        NTP   | BMC   | Not allowed
        NTP   | HOST  | Not allowed
        NTP   | SPLIT | OK, and just save offset
        NTP   | BOTH  | Not allowed
        MANUAL| BMC   | Not allowed
        MANUAL| HOST  | OK, and set time to BMC
        MANUAL| SPLIT | OK, and just save offset
        MANUAL| BOTH  | OK, and set time to BMC

2. If owner is SPLIT and BMC time is changed, update the offset accordinly;
3. Use timerfd to get notified on BMC time change, and update host time
diff accordingly;
4. Add unit test cases.

Change-Id: I2d60a821f7da9b689c579ae7ab672cc37967322c
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/test/TestBmcEpoch.cpp b/test/TestBmcEpoch.cpp
index 2fd55a5..4661e20 100644
--- a/test/TestBmcEpoch.cpp
+++ b/test/TestBmcEpoch.cpp
@@ -1,45 +1,67 @@
 #include <sdbusplus/bus.hpp>
 #include <gtest/gtest.h>
+#include <memory>
 
 #include "bmc_epoch.hpp"
 #include "config.h"
 #include "types.hpp"
+#include "mocked_bmc_time_change_listener.hpp"
 
 namespace phosphor
 {
 namespace time
 {
 
+using ::testing::_;
 using namespace std::chrono;
+
 class TestBmcEpoch : public testing::Test
 {
     public:
         sdbusplus::bus::bus bus;
-        BmcEpoch bmcEpoch;
+        sd_event* event;
+        MockBmcTimeChangeListener listener;
+        std::unique_ptr<BmcEpoch> bmcEpoch;
 
         TestBmcEpoch()
-            : bus(sdbusplus::bus::new_default()),
-              bmcEpoch(bus, OBJPATH_BMC)
+            : bus(sdbusplus::bus::new_default())
         {
-            // Empty
+            // BmcEpoch requires sd_event to init
+            sd_event_default(&event);
+            bus.attach_event(event, SD_EVENT_PRIORITY_NORMAL);
+            bmcEpoch = std::make_unique<BmcEpoch>(bus, OBJPATH_BMC);
+            bmcEpoch->setBmcTimeChangeListener(&listener);
+        }
+
+        ~TestBmcEpoch()
+        {
+            bus.detach_event();
+            sd_event_unref(event);
         }
 
         // Proxies for BmcEpoch's private members and functions
         Mode getTimeMode()
         {
-            return bmcEpoch.timeMode;
+            return bmcEpoch->timeMode;
         }
         Owner getTimeOwner()
         {
-            return bmcEpoch.timeOwner;
+            return bmcEpoch->timeOwner;
         }
         void setTimeOwner(Owner owner)
         {
-            bmcEpoch.timeOwner = owner;
+            bmcEpoch->timeOwner = owner;
         }
         void setTimeMode(Mode mode)
         {
-            bmcEpoch.timeMode = mode;
+            bmcEpoch->timeMode = mode;
+        }
+        void triggerTimeChange()
+        {
+            bmcEpoch->onTimeChange(nullptr,
+                                   -1,
+                                   0,
+                                   bmcEpoch.get());
         }
 };
 
@@ -51,9 +73,9 @@
 
 TEST_F(TestBmcEpoch, getElapsed)
 {
-    auto t1 = bmcEpoch.elapsed();
+    auto t1 = bmcEpoch->elapsed();
     EXPECT_NE(0, t1);
-    auto t2 = bmcEpoch.elapsed();
+    auto t2 = bmcEpoch->elapsed();
     EXPECT_GE(t2, t1);
 }
 
@@ -62,13 +84,13 @@
     auto epochNow = duration_cast<microseconds>(
         system_clock::now().time_since_epoch()).count();
     // In NTP mode, setting time is not allowed
-    auto ret = bmcEpoch.elapsed(epochNow);
+    auto ret = bmcEpoch->elapsed(epochNow);
     EXPECT_EQ(0, ret);
 
     // In Host owner, setting time is not allowed
     setTimeMode(Mode::MANUAL);
     setTimeOwner(Owner::HOST);
-    ret = bmcEpoch.elapsed(epochNow);
+    ret = bmcEpoch->elapsed(epochNow);
     EXPECT_EQ(0, ret);
 }
 
@@ -79,5 +101,12 @@
     // But for now we can not test it
 }
 
+TEST_F(TestBmcEpoch, onTimeChange)
+{
+    // On BMC time change, the listner is expected to be notified
+    EXPECT_CALL(listener, onBmcTimeChanged(_)).Times(1);
+    triggerTimeChange();
+}
+
 }
 }
diff --git a/test/TestHostEpoch.cpp b/test/TestHostEpoch.cpp
index 0ff8a11..13d604d 100644
--- a/test/TestHostEpoch.cpp
+++ b/test/TestHostEpoch.cpp
@@ -14,6 +14,8 @@
 using namespace std::chrono;
 using namespace std::chrono_literals;
 
+const constexpr microseconds USEC_ZERO{0};
+
 class TestHostEpoch : public testing::Test
 {
     public:
@@ -22,7 +24,7 @@
 
         static constexpr auto FILE_NOT_EXIST = "path/to/file-not-exist";
         static constexpr auto FILE_OFFSET = "saved_host_offset";
-        static constexpr auto delta = 2s;
+        const microseconds delta = 2s;
 
         TestHostEpoch()
             : bus(sdbusplus::bus::new_default()),
@@ -50,9 +52,86 @@
         {
             return hostEpoch.offset;
         }
+        void setOffset(microseconds us)
+        {
+            hostEpoch.offset = us;
+        }
         void setTimeOwner(Owner owner)
         {
-            hostEpoch.timeOwner = owner;
+            hostEpoch.onOwnerChanged(owner);
+        }
+        void setTimeMode(Mode mode)
+        {
+            hostEpoch.onModeChanged(mode);
+        }
+
+        void checkSettingTimeNotAllowed()
+        {
+            // By default offset shall be 0
+            EXPECT_EQ(0, getOffset().count());
+
+            // Set time is not allowed,
+            // so verify offset is still 0 after set time
+            microseconds diff = 1min;
+            hostEpoch.elapsed(hostEpoch.elapsed() + diff.count());
+            EXPECT_EQ(0, getOffset().count());
+            // TODO: when gmock is ready, check there is no call to timedatectl
+        }
+
+        void checkSetSplitTimeInFuture()
+        {
+            // Get current time, and set future +1min time
+            auto t1 = hostEpoch.elapsed();
+            EXPECT_NE(0, t1);
+            microseconds diff = 1min;
+            auto t2 = t1 + diff.count();
+            hostEpoch.elapsed(t2);
+
+            // Verify that the offset shall be positive,
+            // and less or equal to diff, and shall be not too less.
+            auto offset = getOffset();
+            EXPECT_GT(offset, USEC_ZERO);
+            EXPECT_LE(offset, diff);
+            diff -= delta;
+            EXPECT_GE(offset, diff);
+
+            // Now get time shall be around future +1min time
+            auto epochNow = duration_cast<microseconds>(
+                                system_clock::now().time_since_epoch()).count();
+            auto elapsedGot = hostEpoch.elapsed();
+            EXPECT_LT(epochNow, elapsedGot);
+            auto epochDiff = elapsedGot - epochNow;
+            diff = 1min;
+            EXPECT_GT(epochDiff, (diff - delta).count());
+            EXPECT_LT(epochDiff, (diff + delta).count());
+        }
+        void checkSetSplitTimeInPast()
+        {
+            // Get current time, and set past -1min time
+            auto t1 = hostEpoch.elapsed();
+            EXPECT_NE(0, t1);
+            microseconds diff = 1min;
+            auto t2 = t1 - diff.count();
+            hostEpoch.elapsed(t2);
+
+            // Verify that the offset shall be negative, and the absolute value
+            // shall be equal or greater than diff, and shall not be too greater
+            auto offset = getOffset();
+            EXPECT_LT(offset, USEC_ZERO);
+            offset = -offset;
+            EXPECT_GE(offset, diff);
+            diff += 10s;
+            EXPECT_LE(offset, diff);
+
+            // Now get time shall be around past -1min time
+            auto epochNow = duration_cast<microseconds>(
+                                system_clock::now().time_since_epoch()).count();
+            auto elapsedGot = hostEpoch.elapsed();
+            EXPECT_LT(elapsedGot, epochNow);
+            auto epochDiff = epochNow - elapsedGot;
+            diff = 1min;
+            EXPECT_GT(epochDiff, (diff - delta).count());
+            EXPECT_LT(epochDiff, (diff + delta).count());
         }
 };
 
@@ -80,26 +159,90 @@
     // Read it back
     microseconds offsetToRead;
     offsetToRead = microseconds(
-        utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
+                       utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
     EXPECT_EQ(offsetToWrite, offsetToRead);
 }
 
-TEST_F(TestHostEpoch, setElapsedNotAllowed)
+TEST_F(TestHostEpoch, setElapsedInNtpBmc)
 {
-    // By default offset shall be 0
-    EXPECT_EQ(0, getOffset().count());
-
-    // Set time in BMC mode is not allowed,
-    // so verify offset is still 0 after set time
-    microseconds diff = 1min;
-    hostEpoch.elapsed(hostEpoch.elapsed() + diff.count());
-    EXPECT_EQ(0, getOffset().count());
+    // Set time in NTP/BMC is not allowed
+    setTimeMode(Mode::NTP);
+    setTimeOwner(Owner::BMC);
+    checkSettingTimeNotAllowed();
 }
 
-TEST_F(TestHostEpoch, setElapsedInFutureAndGet)
+TEST_F(TestHostEpoch, setElapsedInNtpHost)
 {
-    // Set to HOST owner so that we can set elapsed
+    // Set time in NTP/HOST is not allowed
+    setTimeMode(Mode::NTP);
     setTimeOwner(Owner::HOST);
+    checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInNtpSplit)
+{
+    // Set time in NTP/SPLIT, offset will be set
+    setTimeMode(Mode::NTP);
+    setTimeOwner(Owner::SPLIT);
+
+    checkSetSplitTimeInFuture();
+
+    // Reset offset
+    setOffset(USEC_ZERO);
+    checkSetSplitTimeInPast();
+}
+
+TEST_F(TestHostEpoch, setElapsedInNtpBoth)
+{
+    // Set time in NTP/BOTH is not allowed
+    setTimeMode(Mode::NTP);
+    setTimeOwner(Owner::BOTH);
+    checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualBmc)
+{
+    // Set time in MANUAL/BMC is not allowed
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::BMC);
+    checkSettingTimeNotAllowed();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualHost)
+{
+    // Set time in MANUAL/HOST, time will be set to BMC
+    // However it requies gmock to test this case
+    // TODO: when gmock is ready, test this case.
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::HOST);
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualSplit)
+{
+    // Set to SPLIT owner so that offset will be set
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::SPLIT);
+
+    checkSetSplitTimeInFuture();
+
+    // Reset offset
+    setOffset(USEC_ZERO);
+    checkSetSplitTimeInPast();
+}
+
+TEST_F(TestHostEpoch, setElapsedInManualBoth)
+{
+    // Set time in MANUAL/BOTH, time will be set to BMC
+    // However it requies gmock to test this case
+    // TODO: when gmock is ready, test this case.
+    setTimeMode(Mode::MANUAL);
+    setTimeOwner(Owner::BOTH);
+}
+
+TEST_F(TestHostEpoch, setElapsedInSplitAndBmcTimeIsChanged)
+{
+    // Set to SPLIT owner so that offset will be set
+    setTimeOwner(Owner::SPLIT);
 
     // Get current time, and set future +1min time
     auto t1 = hostEpoch.elapsed();
@@ -111,52 +254,38 @@
     // Verify that the offset shall be positive,
     // and less or equal to diff, and shall be not too less.
     auto offset = getOffset();
-    EXPECT_GT(offset, microseconds(0));
+    EXPECT_GT(offset, USEC_ZERO);
     EXPECT_LE(offset, diff);
     diff -= delta;
     EXPECT_GE(offset, diff);
 
-    // Now get time shall be around future +1min time
-    auto epochNow = duration_cast<microseconds>(
-        system_clock::now().time_since_epoch()).count();
-    auto elapsedGot = hostEpoch.elapsed();
-    EXPECT_LT(epochNow, elapsedGot);
-    auto epochDiff = elapsedGot - epochNow;
-    diff = 1min;
-    EXPECT_GT(epochDiff, (diff - delta).count());
-    EXPECT_LT(epochDiff, (diff + delta).count());
+    // Now BMC time is changed to future +1min
+    hostEpoch.onBmcTimeChanged(microseconds(t2));
+
+    // Verify that the offset shall be around zero since it's almost
+    // the same as BMC time
+    offset = getOffset();
+    if (offset.count() < 0)
+    {
+        offset = microseconds(-offset.count());
+    }
+    EXPECT_LE(offset, delta);
 }
 
-TEST_F(TestHostEpoch, setElapsedInPastAndGet)
+TEST_F(TestHostEpoch, clearOffsetOnOwnerChange)
 {
-    // Set to HOST owner so that we can set elapsed
-    setTimeOwner(Owner::HOST);
+    EXPECT_EQ(USEC_ZERO, getOffset());
 
-    // Get current time, and set past -1min time
-    auto t1 = hostEpoch.elapsed();
-    EXPECT_NE(0, t1);
-    microseconds diff = 1min;
-    auto t2 = t1 - diff.count();
-    hostEpoch.elapsed(t2);
+    setTimeOwner(Owner::SPLIT);
+    hostEpoch.onBmcTimeChanged(microseconds(hostEpoch.elapsed()) + 1min);
 
-    // Verify that the offset shall be negative, and the absolute value
-    // shall be equal or greater than diff, and shall not be too greater
-    auto offset = getOffset();
-    EXPECT_LT(offset, microseconds(0));
-    offset = -offset;
-    EXPECT_GE(offset, diff);
-    diff += 10s;
-    EXPECT_LE(offset, diff);
+    // Now offset shall be non zero
+    EXPECT_NE(USEC_ZERO, getOffset());
 
-    // Now get time shall be around past -1min time
-    auto epochNow = duration_cast<microseconds>(
-        system_clock::now().time_since_epoch()).count();
-    auto elapsedGot = hostEpoch.elapsed();
-    EXPECT_LT(elapsedGot, epochNow);
-    auto epochDiff = epochNow - elapsedGot;
-    diff = 1min;
-    EXPECT_GT(epochDiff, (diff - delta).count());
-    EXPECT_LT(epochDiff, (diff + delta).count());
+    setTimeOwner(Owner::BOTH);
+
+    // Now owner is BOTH, the offset shall be cleared
+    EXPECT_EQ(USEC_ZERO, getOffset());
 }
 
 }
diff --git a/test/mocked_bmc_time_change_listener.hpp b/test/mocked_bmc_time_change_listener.hpp
new file mode 100644
index 0000000..9b2c81c
--- /dev/null
+++ b/test/mocked_bmc_time_change_listener.hpp
@@ -0,0 +1,16 @@
+#pragma once
+#include <gmock/gmock.h>
+#include "bmc_time_change_listener.hpp"
+
+namespace phosphor {
+namespace time {
+
+class MockBmcTimeChangeListener : public BmcTimeChangeListener {
+ public:
+  MOCK_METHOD1(onBmcTimeChanged,
+      void(const std::chrono::microseconds&));
+};
+
+}  // namespace time
+}  // namespace phosphor
+