| #include <sdbusplus/bus.hpp> |
| #include <gtest/gtest.h> |
| |
| #include "host_epoch.hpp" |
| #include "utils.hpp" |
| #include "config.h" |
| #include "types.hpp" |
| |
| namespace phosphor |
| { |
| namespace time |
| { |
| |
| using namespace std::chrono; |
| using namespace std::chrono_literals; |
| |
| const constexpr microseconds USEC_ZERO{0}; |
| |
| class TestHostEpoch : public testing::Test |
| { |
| public: |
| 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"; |
| const microseconds 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; |
| } |
| microseconds getOffset() |
| { |
| return hostEpoch.offset; |
| } |
| void setOffset(microseconds us) |
| { |
| hostEpoch.offset = us; |
| } |
| void setTimeOwner(Owner 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()); |
| } |
| }; |
| |
| TEST_F(TestHostEpoch, empty) |
| { |
| // Default mode/owner is MANUAL/BOTH |
| EXPECT_EQ(Mode::Manual, getTimeMode()); |
| EXPECT_EQ(Owner::Both, getTimeOwner()); |
| } |
| |
| TEST_F(TestHostEpoch, readDataFileNotExist) |
| { |
| // When file does not exist, the default offset shall be 0 |
| microseconds offset(0); |
| auto value = utils::readData<decltype(offset)::rep>(FILE_NOT_EXIST); |
| EXPECT_EQ(0, value); |
| } |
| |
| TEST_F(TestHostEpoch, writeAndReadData) |
| { |
| // Write offset to file |
| microseconds offsetToWrite(1234567); |
| utils::writeData<decltype(offsetToWrite)::rep>( |
| FILE_OFFSET, offsetToWrite.count()); |
| |
| // Read it back |
| microseconds offsetToRead; |
| offsetToRead = microseconds( |
| utils::readData<decltype(offsetToRead)::rep>(FILE_OFFSET)); |
| EXPECT_EQ(offsetToWrite, offsetToRead); |
| } |
| |
| TEST_F(TestHostEpoch, setElapsedInNtpBmc) |
| { |
| // Set time in NTP/BMC is not allowed |
| setTimeMode(Mode::NTP); |
| setTimeOwner(Owner::BMC); |
| checkSettingTimeNotAllowed(); |
| } |
| |
| TEST_F(TestHostEpoch, setElapsedInNtpHost) |
| { |
| // 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(); |
| 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 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, clearOffsetOnOwnerChange) |
| { |
| EXPECT_EQ(USEC_ZERO, getOffset()); |
| |
| setTimeOwner(Owner::Split); |
| hostEpoch.onBmcTimeChanged(microseconds(hostEpoch.elapsed()) + 1min); |
| |
| // Now offset shall be non zero |
| EXPECT_NE(USEC_ZERO, getOffset()); |
| |
| setTimeOwner(Owner::Both); |
| |
| // Now owner is BOTH, the offset shall be cleared |
| EXPECT_EQ(USEC_ZERO, getOffset()); |
| } |
| |
| } |
| } |