Update analyze function to check STATUS_WORD
The STATUS_WORD PMBus command response will be the start of the power
supply fault analysis. Update the analyze() function to read its value
and process (select) fault bits.
Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: If7274ad237c0604a56008676ae64804a5fd2854e
diff --git a/phosphor-power-supply/test/meson.build b/phosphor-power-supply/test/meson.build
new file mode 100644
index 0000000..a71ac57
--- /dev/null
+++ b/phosphor-power-supply/test/meson.build
@@ -0,0 +1,22 @@
+test('phosphor-power-supply-tests',
+ executable('phosphor-power-supply-tests',
+ 'power_supply_tests.cpp',
+ 'mock.cpp',
+ dependencies: [
+ gmock,
+ gtest,
+ sdbusplus,
+ sdeventplus,
+ phosphor_logging,
+ ],
+ implicit_include_directories: false,
+ include_directories: [
+ '.',
+ '..',
+ '../..'
+ ],
+ link_args: dynamic_linker,
+ build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
+ objects: power_supply,
+ )
+)
diff --git a/phosphor-power-supply/test/mock.cpp b/phosphor-power-supply/test/mock.cpp
new file mode 100644
index 0000000..e13db2b
--- /dev/null
+++ b/phosphor-power-supply/test/mock.cpp
@@ -0,0 +1,15 @@
+#include <mock.hpp>
+
+namespace phosphor
+{
+namespace pmbus
+{
+
+std::unique_ptr<PMBusBase> createPMBus(std::uint8_t /*bus*/,
+ const std::string& /*address*/)
+{
+ return std::make_unique<MockedPMBus>();
+}
+
+} // namespace pmbus
+} // namespace phosphor
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
new file mode 100644
index 0000000..0c64e00
--- /dev/null
+++ b/phosphor-power-supply/test/mock.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "pmbus.hpp"
+#include "util_base.hpp"
+
+#include <gmock/gmock.h>
+
+namespace phosphor
+{
+namespace pmbus
+{
+class MockedPMBus : public PMBusBase
+{
+
+ public:
+ virtual ~MockedPMBus() = default;
+
+ MOCK_METHOD(uint64_t, read, (const std::string& name, Type type),
+ (override));
+};
+} // namespace pmbus
+
+namespace power
+{
+namespace psu
+{
+class MockedUtil : public UtilBase
+{
+ public:
+ virtual ~MockedUtil() = default;
+
+ MOCK_METHOD(bool, getPresence,
+ (sdbusplus::bus::bus & bus, const std::string& invpath),
+ (const, override));
+};
+
+static std::unique_ptr<MockedUtil> util;
+inline const UtilBase& getUtils()
+{
+ if (!util)
+ {
+ util = std::make_unique<MockedUtil>();
+ }
+ return *util;
+}
+
+inline void freeUtils()
+{
+ util.reset();
+}
+
+} // namespace psu
+} // namespace power
+
+} // namespace phosphor
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
new file mode 100644
index 0000000..9674153
--- /dev/null
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -0,0 +1,277 @@
+#include "../power_supply.hpp"
+#include "mock.hpp"
+
+#include <xyz/openbmc_project/Common/Device/error.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::psu;
+using namespace phosphor::pmbus;
+
+using ::testing::_;
+using ::testing::Assign;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::StrEq;
+
+static auto PSUInventoryPath = "/xyz/bmc/inv/sys/chassis/board/powersupply0";
+
+class PowerSupplyTests : public ::testing::Test
+{
+ public:
+ PowerSupplyTests() :
+ mockedUtil(reinterpret_cast<const MockedUtil&>(getUtils()))
+ {
+ ON_CALL(mockedUtil, getPresence(_, _)).WillByDefault(Return(false));
+ }
+
+ ~PowerSupplyTests() override
+ {
+ freeUtils();
+ }
+
+ const MockedUtil& mockedUtil;
+};
+
+TEST_F(PowerSupplyTests, Constructor)
+{
+ /**
+ * @param[in] invpath - String for inventory path to use
+ * @param[in] i2cbus - The bus number this power supply is on
+ * @param[in] i2caddr - The 16-bit I2C address of the power supply
+ */
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
+ auto psu = std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, "0068");
+
+ EXPECT_EQ(psu->isPresent(), false);
+ EXPECT_EQ(psu->isFaulted(), false);
+ EXPECT_EQ(psu->hasInputFault(), false);
+ EXPECT_EQ(psu->hasMFRFault(), false);
+ EXPECT_EQ(psu->hasVINUVFault(), false);
+}
+
+TEST_F(PowerSupplyTests, Analyze)
+{
+ auto bus = sdbusplus::bus::new_default();
+
+ EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
+ PowerSupply psu{bus, PSUInventoryPath, 4, "0069"};
+ psu.analyze();
+ // By default, nothing should change.
+ EXPECT_EQ(psu.isPresent(), false);
+ EXPECT_EQ(psu.isFaulted(), false);
+ EXPECT_EQ(psu.hasInputFault(), false);
+ EXPECT_EQ(psu.hasMFRFault(), false);
+ EXPECT_EQ(psu.hasVINUVFault(), false);
+
+ // In order to get the various faults tested, the power supply needs to be
+ // present in order to read from the PMBus device(s).
+ EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+ .Times(1)
+ .WillOnce(Return(true)); // present
+ PowerSupply psu2{bus, PSUInventoryPath, 5, "006a"};
+ EXPECT_EQ(psu2.isPresent(), true);
+
+ // STATUS_WORD 0x0000 is powered on, no faults.
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu2.getPMBus());
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), false);
+ EXPECT_EQ(psu2.hasInputFault(), false);
+ EXPECT_EQ(psu2.hasMFRFault(), false);
+ EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+ // STATUS_WORD input fault/warn
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .Times(1)
+ .WillOnce(Return(status_word::INPUT_FAULT_WARN));
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), true);
+ EXPECT_EQ(psu2.hasInputFault(), true);
+ EXPECT_EQ(psu2.hasMFRFault(), false);
+ EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+ // STATUS_WORD INPUT/UV fault.
+ // First need it to return good status, then the fault
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .WillOnce(Return(0x0000))
+ .WillOnce(Return(status_word::VIN_UV_FAULT));
+ psu2.analyze();
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), true);
+ EXPECT_EQ(psu2.hasInputFault(), false);
+ EXPECT_EQ(psu2.hasMFRFault(), false);
+ EXPECT_EQ(psu2.hasVINUVFault(), true);
+
+ // STATUS_WORD MFR fault.
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .WillOnce(Return(0x0000))
+ .WillOnce(Return(status_word::MFR_SPECIFIC_FAULT));
+ psu2.analyze();
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), true);
+ EXPECT_EQ(psu2.hasInputFault(), false);
+ EXPECT_EQ(psu2.hasMFRFault(), true);
+ EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+ // Ignore Temperature fault.
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .WillOnce(Return(0x0000))
+ .WillOnce(Return(status_word::TEMPERATURE_FAULT_WARN));
+ psu2.analyze();
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), false);
+ EXPECT_EQ(psu2.hasInputFault(), false);
+ EXPECT_EQ(psu2.hasMFRFault(), false);
+ EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+ // Ignore fan fault
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .WillOnce(Return(0x0000))
+ .WillOnce(Return(status_word::FAN_FAULT));
+ psu2.analyze();
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), false);
+ EXPECT_EQ(psu2.hasInputFault(), false);
+ EXPECT_EQ(psu2.hasMFRFault(), false);
+ EXPECT_EQ(psu2.hasVINUVFault(), false);
+
+ // TODO: ReadFailure
+}
+
+TEST_F(PowerSupplyTests, ClearFaults)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+ .Times(1)
+ .WillOnce(Return(true)); // present
+ PowerSupply psu{bus, PSUInventoryPath, 13, "0068"};
+ EXPECT_EQ(psu.isPresent(), true);
+ EXPECT_EQ(psu.isFaulted(), false);
+ EXPECT_EQ(psu.hasInputFault(), false);
+ EXPECT_EQ(psu.hasMFRFault(), false);
+ EXPECT_EQ(psu.hasVINUVFault(), false);
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0xFFFF));
+ psu.analyze();
+ EXPECT_EQ(psu.isPresent(), true);
+ EXPECT_EQ(psu.isFaulted(), true);
+ EXPECT_EQ(psu.hasInputFault(), true);
+ EXPECT_EQ(psu.hasMFRFault(), true);
+ EXPECT_EQ(psu.hasVINUVFault(), true);
+ psu.clearFaults();
+ EXPECT_EQ(psu.isPresent(), true);
+ EXPECT_EQ(psu.isFaulted(), false);
+ EXPECT_EQ(psu.hasInputFault(), false);
+ EXPECT_EQ(psu.hasMFRFault(), false);
+ EXPECT_EQ(psu.hasVINUVFault(), false);
+}
+
+TEST_F(PowerSupplyTests, UpdateInventory)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath)))
+ .Times(1)
+ .WillOnce(Return(true)); // present
+ PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+ psu.updateInventory();
+ // TODO: Checks / Story #921
+}
+
+TEST_F(PowerSupplyTests, IsPresent)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, StrEq(PSUInventoryPath))).Times(1);
+ PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+ EXPECT_EQ(psu.isPresent(), false);
+
+ EXPECT_CALL(mockedUtil, getPresence(_, _))
+ .WillOnce(Return(true)); // present
+ PowerSupply psu2{bus, PSUInventoryPath, 10, "006b"};
+ EXPECT_EQ(psu2.isPresent(), true);
+}
+
+TEST_F(PowerSupplyTests, IsFaulted)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, _))
+ .WillOnce(Return(true)); // present
+ PowerSupply psu{bus, PSUInventoryPath, 11, "006f"};
+ EXPECT_EQ(psu.isFaulted(), false);
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0xFFFF));
+ psu.analyze();
+ EXPECT_EQ(psu.isFaulted(), true);
+}
+
+TEST_F(PowerSupplyTests, HasInputFault)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, _))
+ .WillOnce(Return(true)); // present
+ PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+ EXPECT_EQ(psu.hasInputFault(), false);
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu.analyze();
+ EXPECT_EQ(psu.hasInputFault(), false);
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .Times(1)
+ .WillOnce(Return(status_word::INPUT_FAULT_WARN));
+ psu.analyze();
+ EXPECT_EQ(psu.hasInputFault(), true);
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu.analyze();
+ EXPECT_EQ(psu.hasInputFault(), false);
+}
+
+TEST_F(PowerSupplyTests, HasMFRFault)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, _))
+ .WillOnce(Return(true)); // present
+ PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+ EXPECT_EQ(psu.hasMFRFault(), false);
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu.analyze();
+ EXPECT_EQ(psu.hasMFRFault(), false);
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .Times(1)
+ .WillOnce(Return(status_word::MFR_SPECIFIC_FAULT));
+ psu.analyze();
+ EXPECT_EQ(psu.hasMFRFault(), true);
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu.analyze();
+ EXPECT_EQ(psu.hasMFRFault(), false);
+}
+
+TEST_F(PowerSupplyTests, HasVINUVFault)
+{
+ auto bus = sdbusplus::bus::new_default();
+ EXPECT_CALL(mockedUtil, getPresence(_, _))
+ .WillOnce(Return(true)); // present
+ PowerSupply psu{bus, PSUInventoryPath, 3, "0068"};
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+ EXPECT_EQ(psu.hasVINUVFault(), false);
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu.analyze();
+ EXPECT_EQ(psu.hasVINUVFault(), false);
+ EXPECT_CALL(mockPMBus, read(_, _))
+ .Times(1)
+ .WillOnce(Return(status_word::VIN_UV_FAULT));
+ psu.analyze();
+ EXPECT_EQ(psu.hasVINUVFault(), true);
+ EXPECT_CALL(mockPMBus, read(_, _)).Times(1).WillOnce(Return(0x0000));
+ psu.analyze();
+ EXPECT_EQ(psu.hasVINUVFault(), false);
+}