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/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());
 }
 
 }