Initial implementation of HostEpoch

When host time is set, the diff between the BmcTime and the value is
saved to persistent storage;
When host time is retrieved, return the BmcTime plus the diff as
host's time.

Add the unit test cases for HostEpoch.

Change-Id: Ia55b93bfcba4f226ceaed8491136ea7afda7bd77
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/test/TestHostEpoch.cpp b/test/TestHostEpoch.cpp
new file mode 100644
index 0000000..dca8d72
--- /dev/null
+++ b/test/TestHostEpoch.cpp
@@ -0,0 +1,173 @@
+#include <sdbusplus/bus.hpp>
+#include <gtest/gtest.h>
+
+#include "host_epoch.hpp"
+#include "config.h"
+
+namespace phosphor
+{
+namespace time
+{
+
+using namespace std::chrono;
+using namespace std::chrono_literals;
+
+class TestHostEpoch : public testing::Test
+{
+    public:
+        using Mode = EpochBase::Mode;
+        using Owner = EpochBase::Owner;
+
+        sdbusplus::bus::bus bus;
+        HostEpoch hostEpoch;
+
+        static constexpr auto FILE_NOT_EXIST = "path/to/file-not-exist";
+        static constexpr auto FILE_OFFSET = "saved_host_offset";
+        static constexpr auto delta = 2s;
+
+        TestHostEpoch()
+            : bus(sdbusplus::bus::new_default()),
+              hostEpoch(bus, OBJPATH_HOST)
+        {
+            // Make sure the file does not exist
+            std::remove(FILE_NOT_EXIST);
+        }
+        ~TestHostEpoch()
+        {
+            // Cleanup test file
+            std::remove(FILE_OFFSET);
+        }
+
+        // Proxies for HostEpoch's private members and functions
+        Mode getTimeMode()
+        {
+            return hostEpoch.timeMode;
+        }
+        Owner getTimeOwner()
+        {
+            return hostEpoch.timeOwner;
+        }
+        template <typename T>
+        T readData(const char* fileName)
+        {
+            return HostEpoch::readData<T>(fileName);
+        }
+        template <typename T>
+        void writeData(const char* fileName, T&& data)
+        {
+            HostEpoch::writeData<T>(fileName, std::forward<T>(data));
+        }
+        microseconds getOffset()
+        {
+            return hostEpoch.offset;
+        }
+        void setTimeOwner(Owner owner)
+        {
+            hostEpoch.timeOwner = owner;
+        }
+};
+
+TEST_F(TestHostEpoch, empty)
+{
+    EXPECT_EQ(Mode::NTP, getTimeMode());
+    EXPECT_EQ(Owner::BMC, getTimeOwner());
+}
+
+TEST_F(TestHostEpoch, readDataFileNotExist)
+{
+    // When file does not exist, the default offset shall be 0
+    microseconds offset(0);
+    auto value = readData<decltype(offset)::rep>(FILE_NOT_EXIST);
+    EXPECT_EQ(0, value);
+}
+
+TEST_F(TestHostEpoch, writeAndReadData)
+{
+    // Write offset to file
+    microseconds offsetToWrite(1234567);
+    writeData<decltype(offsetToWrite)::rep>(FILE_OFFSET, offsetToWrite.count());
+
+    // Read it back
+    microseconds offsetToRead;
+    offsetToRead = microseconds(
+        readData<decltype(offsetToRead)::rep>(FILE_OFFSET));
+    EXPECT_EQ(offsetToWrite, offsetToRead);
+}
+
+TEST_F(TestHostEpoch, setElapsedNotAllowed)
+{
+    // 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());
+}
+
+TEST_F(TestHostEpoch, setElapsedInFutureAndGet)
+{
+    // Set to HOST owner so that we can set elapsed
+    setTimeOwner(Owner::HOST);
+
+    // 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, microseconds(0));
+    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());
+}
+
+TEST_F(TestHostEpoch, setElapsedInPastAndGet)
+{
+    // Set to HOST owner so that we can set elapsed
+    setTimeOwner(Owner::HOST);
+
+    // 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, microseconds(0));
+    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());
+}
+
+}
+}