/** * Copyright © 2021 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "action.hpp" #include "chassis.hpp" #include "device.hpp" #include "i2c_capture_bytes_action.hpp" #include "i2c_compare_bit_action.hpp" #include "i2c_interface.hpp" #include "if_action.hpp" #include "log_phase_fault_action.hpp" #include "mock_action.hpp" #include "mock_error_logging.hpp" #include "mock_journal.hpp" #include "mock_services.hpp" #include "mocked_i2c_interface.hpp" #include "phase_fault.hpp" #include "phase_fault_detection.hpp" #include "rule.hpp" #include "system.hpp" #include #include #include #include #include #include #include #include #include using namespace phosphor::power::regulators; using ::testing::_; using ::testing::A; using ::testing::NotNull; using ::testing::Ref; using ::testing::Return; using ::testing::SetArrayArgument; using ::testing::Throw; using ::testing::TypedEq; class PhaseFaultDetectionTests : public ::testing::Test { public: /** * Constructor. * * Creates the following objects needed for calling the * PhaseFaultDetection::execute() method: * - Regulator Device * - I/O expander Device * - Chassis that contains the Devices * - System that contains the Chassis * * Saves pointers to these objects in data members so they can be easily * accessed in tests. Also saves pointers to the MockI2CInterface objects * so they can be used in mock expectations. */ PhaseFaultDetectionTests() : ::testing::Test{} { // Create mock I2CInterface for regulator Device and save pointer auto regI2CInterface = std::make_unique(); this->regI2CInterface = regI2CInterface.get(); // Create regulator Device and save pointer auto regulator = std::make_unique( "vdd1", true, "/xyz/openbmc_project/inventory/system/chassis/motherboard/vdd1", std::move(regI2CInterface)); this->regulator = regulator.get(); // Create mock I2CInterface for I/O expander Device and save pointer auto ioExpI2CInterface = std::make_unique(); this->ioExpI2CInterface = ioExpI2CInterface.get(); // Create I/O expander Device and save pointer auto ioExpander = std::make_unique( "ioexp1", false, "/xyz/openbmc_project/inventory/system/chassis/motherboard/ioexp1", std::move(ioExpI2CInterface)); this->ioExpander = ioExpander.get(); // Create Chassis that contains Devices and save pointer std::vector> devices{}; devices.emplace_back(std::move(regulator)); devices.emplace_back(std::move(ioExpander)); auto chassis = std::make_unique( 1, "/xyz/openbmc_project/inventory/system/chassis", std::move(devices)); this->chassis = chassis.get(); // Create System that contains Chassis and save pointer std::vector> rules{}; std::vector> chassisVec{}; chassisVec.emplace_back(std::move(chassis)); this->system = std::make_unique(std::move(rules), std::move(chassisVec)); } protected: /** * Note: The following pointers do NOT need to be explicitly deleted. They * point to objects that are owned by the System object. All the objects * will be automatically deleted. */ i2c::MockedI2CInterface* regI2CInterface{nullptr}; Device* regulator{nullptr}; i2c::MockedI2CInterface* ioExpI2CInterface{nullptr}; Device* ioExpander{nullptr}; Chassis* chassis{nullptr}; /** * System object. Owns all the other objects and will automatically delete * them. */ std::unique_ptr system{}; }; TEST_F(PhaseFaultDetectionTests, Constructor) { // Test where device ID not specified { std::vector> actions{}; actions.push_back(std::make_unique()); PhaseFaultDetection detection{std::move(actions)}; EXPECT_EQ(detection.getActions().size(), 1); EXPECT_EQ(detection.getDeviceID(), ""); } // Test where device ID not specified { std::vector> actions{}; actions.push_back(std::make_unique()); actions.push_back(std::make_unique()); PhaseFaultDetection detection{std::move(actions), "ioexp1"}; EXPECT_EQ(detection.getActions().size(), 2); EXPECT_EQ(detection.getDeviceID(), "ioexp1"); } } TEST_F(PhaseFaultDetectionTests, ClearErrorHistory) { std::vector> actions{}; // Create MockAction that will switch every 5 times between working and // throwing an exception. Expect it to be executed 20 times. std::logic_error error{"Logic error"}; auto action = std::make_unique(); EXPECT_CALL(*action, execute) .Times(20) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Return(true)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Throw(error)) .WillOnce(Throw(error)); actions.push_back(std::move(action)); // Create a LogPhaseFaultAction that will log N faults actions.push_back(std::make_unique(PhaseFaultType::n)); // Create a LogPhaseFaultAction that will log N+1 faults actions.push_back( std::make_unique(PhaseFaultType::n_plus_1)); // Create PhaseFaultDetection PhaseFaultDetection detection{std::move(actions)}; // Create lambda that sets Journal and ErrorLogging expectations when // performing phase fault detection 10 times. The lambda allows us to // set the same expectations twice without duplicate code. auto setExpectations = [](MockServices& services) { // Set Journal service expectations: // - 3 error messages for the MockAction exceptions // - 3 error messages for inability to detect phase faults // - 2 error messages for the N phase fault // - 2 error messages for the N+1 phase fault MockJournal& journal = services.getMockJournal(); EXPECT_CALL(journal, logError(std::vector{"Logic error"})) .Times(3); EXPECT_CALL(journal, logError("Unable to detect phase faults in regulator vdd1")) .Times(3); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=1")) .Times(1); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=2")) .Times(1); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=1")) .Times(1); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=2")) .Times(1); // Set ErrorLogging service expectations: // - Internal error should be logged once for the MockAction exceptions // - N phase fault error should be logged once // - N+1 phase fault error should be logged once MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logInternalError).Times(1); EXPECT_CALL(errorLogging, logPhaseFault(_, _, PhaseFaultType::n, _, _)) .Times(1); EXPECT_CALL(errorLogging, logPhaseFault(_, _, PhaseFaultType::n_plus_1, _, _)) .Times(1); }; // Perform phase fault detection 10 times to set error history data members { // Create mock services. Set expectations via lambda. MockServices services{}; setExpectations(services); // Execute PhaseFaultDetection 10 times for (int i = 1; i <= 10; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Clear error history detection.clearErrorHistory(); // Perform phase fault detection 10 more times. Verify errors logged again. { // Create mock services. Set expectations via lambda. MockServices services{}; setExpectations(services); // Execute PhaseFaultDetection 10 times for (int i = 1; i <= 10; ++i) { detection.execute(services, *system, *chassis, *regulator); } } } TEST_F(PhaseFaultDetectionTests, Execute) { // Test where Device ID was specified { // Create I2CCompareBitAction that will use an I2CInterface auto action = std::make_unique(0x1C, 2, 0); // Create PhaseFaultDetection. Specify device ID of I/O expander. std::vector> actions{}; actions.push_back(std::move(action)); PhaseFaultDetection detection{std::move(actions), "ioexp1"}; // Set expectations for regulator I2C interface. Should not be used. EXPECT_CALL(*regI2CInterface, isOpen).Times(0); EXPECT_CALL(*regI2CInterface, read(0x1C, A())).Times(0); // Set expectations for I/O expander I2C interface. Should be used. EXPECT_CALL(*ioExpI2CInterface, isOpen).Times(1).WillOnce(Return(true)); EXPECT_CALL(*ioExpI2CInterface, read(0x1C, A())).Times(1); // Create mock services. Expect no errors to be logged. MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL(journal, logError(A())).Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logPhaseFault).Times(0); // Execute PhaseFaultDetection detection.execute(services, *system, *chassis, *regulator); } // Test where Device ID was not specified { // Create I2CCompareBitAction that will use an I2CInterface auto action = std::make_unique(0x1C, 2, 0); // Create PhaseFaultDetection. Specify no device ID, which means the // regulator should be used. std::vector> actions{}; actions.push_back(std::move(action)); PhaseFaultDetection detection{std::move(actions)}; // Set expectations for regulator I2C interface. Should be used. EXPECT_CALL(*regI2CInterface, isOpen).Times(1).WillOnce(Return(true)); EXPECT_CALL(*regI2CInterface, read(0x1C, A())).Times(1); // Set expectations for I/O expander I2C interface. Should not be used. EXPECT_CALL(*ioExpI2CInterface, isOpen).Times(0); EXPECT_CALL(*ioExpI2CInterface, read(0x1C, A())).Times(0); // Create mock services. Expect no errors to be logged. MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL(journal, logError(A())).Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logPhaseFault).Times(0); // Execute PhaseFaultDetection detection.execute(services, *system, *chassis, *regulator); } // Test where no phase faults detected { // Create MockAction. Expect it to be executed 3 times. auto action = std::make_unique(); EXPECT_CALL(*action, execute).Times(3).WillRepeatedly(Return(true)); // Create PhaseFaultDetection std::vector> actions{}; actions.push_back(std::move(action)); PhaseFaultDetection detection{std::move(actions)}; // Create mock services. Expect no errors to be logged. MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL(journal, logError(A())).Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logPhaseFault).Times(0); // Execute PhaseFaultDetection 3 times for (int i = 1; i <= 3; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where N fault occurs, but not twice in a row { // Create MockAction that will alternate between returning true and // false. Expect it to be executed 6 times. Use it for the "condition" // of an IfAction. auto conditionAction = std::make_unique(); EXPECT_CALL(*conditionAction, execute) .Times(6) .WillOnce(Return(true)) .WillOnce(Return(false)) .WillOnce(Return(true)) .WillOnce(Return(false)) .WillOnce(Return(true)) .WillOnce(Return(false)); // Create a LogPhaseFaultAction that will log an N phase fault in the // ActionEnvironment. Use it for the "then" clause of an IfAction. auto logPhaseFaultAction = std::make_unique(PhaseFaultType::n); // Create an IfAction that will log an N phase fault in the // ActionEnvironment if the mock condition is true. std::vector> thenActions{}; thenActions.push_back(std::move(logPhaseFaultAction)); auto ifAction = std::make_unique(std::move(conditionAction), std::move(thenActions)); // Create PhaseFaultDetection std::vector> actions{}; actions.push_back(std::move(ifAction)); PhaseFaultDetection detection{std::move(actions)}; // Create mock services. Expect 3 error messages in the journal for // an N phase fault detected with the consecutive count = 1. Expect no // phase fault error to be logged. MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=1")) .Times(3); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=2")) .Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logPhaseFault).Times(0); // Execute PhaseFaultDetection 6 times for (int i = 1; i <= 6; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where N+1 fault occurs, but not twice in a row { // Create MockAction that will alternate between returning true and // false. Expect it to be executed 6 times. Use it for the "condition" // of an IfAction. auto conditionAction = std::make_unique(); EXPECT_CALL(*conditionAction, execute) .Times(6) .WillOnce(Return(true)) .WillOnce(Return(false)) .WillOnce(Return(true)) .WillOnce(Return(false)) .WillOnce(Return(true)) .WillOnce(Return(false)); // Create a LogPhaseFaultAction that will log an N+1 phase fault in the // ActionEnvironment. Use it for the "then" clause of an IfAction. auto logPhaseFaultAction = std::make_unique(PhaseFaultType::n_plus_1); // Create an IfAction that will log an N+1 phase fault in the // ActionEnvironment if the mock condition is true. std::vector> thenActions{}; thenActions.push_back(std::move(logPhaseFaultAction)); auto ifAction = std::make_unique(std::move(conditionAction), std::move(thenActions)); // Create PhaseFaultDetection std::vector> actions{}; actions.push_back(std::move(ifAction)); PhaseFaultDetection detection{std::move(actions)}; // Create mock services. Expect 3 error messages in the journal for // an N+1 phase fault detected with the consecutive count = 1. Expect // no phase fault error to be logged. MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=1")) .Times(3); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=2")) .Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logPhaseFault).Times(0); // Execute PhaseFaultDetection 6 times for (int i = 1; i <= 6; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where N fault detected twice in a row { // Create action that will log an N phase fault in ActionEnvironment auto action = std::make_unique(PhaseFaultType::n); // Create PhaseFaultDetection std::vector> actions{}; actions.push_back(std::move(action)); PhaseFaultDetection detection{std::move(actions)}; // Create mock services with the following expectations: // - 2 error messages in journal for N phase fault detected // - 0 error messages in journal for N+1 phase fault detected // - 1 N phase fault error logged // - 0 N+1 phase fault errors logged MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=1")) .Times(1); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=2")) .Times(1); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=1")) .Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); std::map additionalData{}; EXPECT_CALL(errorLogging, logPhaseFault(Entry::Level::Warning, Ref(journal), PhaseFaultType::n, regulator->getFRU(), additionalData)) .Times(1); EXPECT_CALL(errorLogging, logPhaseFault(_, _, PhaseFaultType::n_plus_1, _, _)) .Times(0); // Execute PhaseFaultDetection 5 times for (int i = 1; i <= 5; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where N+1 fault detected twice in a row { // Create action that will log an N+1 phase fault in ActionEnvironment auto action = std::make_unique(PhaseFaultType::n_plus_1); // Create PhaseFaultDetection std::vector> actions{}; actions.push_back(std::move(action)); PhaseFaultDetection detection{std::move(actions)}; // Create mock services with the following expectations: // - 2 error messages in journal for N+1 phase fault detected // - 0 error messages in journal for N phase fault detected // - 1 N+1 phase fault error logged // - 0 N phase fault errors logged MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=1")) .Times(1); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=2")) .Times(1); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=1")) .Times(0); MockErrorLogging& errorLogging = services.getMockErrorLogging(); std::map additionalData{}; EXPECT_CALL(errorLogging, logPhaseFault(Entry::Level::Informational, Ref(journal), PhaseFaultType::n_plus_1, regulator->getFRU(), additionalData)) .Times(1); EXPECT_CALL(errorLogging, logPhaseFault(_, _, PhaseFaultType::n, _, _)) .Times(0); // Execute PhaseFaultDetection 5 times for (int i = 1; i <= 5; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where both faults detected twice in a row { std::vector> actions{}; // Create action that will log an N+1 phase fault in ActionEnvironment actions.push_back( std::make_unique(PhaseFaultType::n_plus_1)); // Create action that will log an N phase fault in ActionEnvironment actions.push_back( std::make_unique(PhaseFaultType::n)); // Create PhaseFaultDetection PhaseFaultDetection detection{std::move(actions)}; // Create mock services with the following expectations: // - 2 error messages in journal for N+1 phase fault detected // - 2 error messages in journal for N phase fault detected // - 1 N+1 phase fault error logged // - 1 N phase fault error logged MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=1")) .Times(1); EXPECT_CALL( journal, logError("n+1 phase fault detected in regulator vdd1: count=2")) .Times(1); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=1")) .Times(1); EXPECT_CALL( journal, logError("n phase fault detected in regulator vdd1: count=2")) .Times(1); MockErrorLogging& errorLogging = services.getMockErrorLogging(); std::map additionalData{}; EXPECT_CALL(errorLogging, logPhaseFault(Entry::Level::Informational, Ref(journal), PhaseFaultType::n_plus_1, regulator->getFRU(), additionalData)) .Times(1); EXPECT_CALL(errorLogging, logPhaseFault(Entry::Level::Warning, Ref(journal), PhaseFaultType::n, regulator->getFRU(), additionalData)) .Times(1); // Execute PhaseFaultDetection 5 times for (int i = 1; i <= 5; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where additional error data is captured { std::vector> actions{}; // Create action that will capture 1 byte from register 0x0F actions.push_back(std::make_unique(0x0F, 1)); // Create action that will capture 2 bytes from register 0x21 actions.push_back(std::make_unique(0x21, 2)); // Create action that will log an N phase fault in ActionEnvironment actions.push_back( std::make_unique(PhaseFaultType::n)); // Create PhaseFaultDetection PhaseFaultDetection detection{std::move(actions)}; // Set expectations for regulator I2C interface: // - isOpen() will return true // - reading 1 byte from register 0x0F will return 0xDA // - reading 2 bytes from register 0x21 will return [ 0x56, 0x14 ] EXPECT_CALL(*regI2CInterface, isOpen).WillRepeatedly(Return(true)); uint8_t register0FValues[] = {0xDA}; EXPECT_CALL(*regI2CInterface, read(0x0F, TypedEq(1), NotNull(), i2c::I2CInterface::Mode::I2C)) .Times(5) .WillRepeatedly( SetArrayArgument<2>(register0FValues, register0FValues + 1)); uint8_t register21Values[] = {0x56, 0x14}; EXPECT_CALL(*regI2CInterface, read(0x21, TypedEq(2), NotNull(), i2c::I2CInterface::Mode::I2C)) .Times(5) .WillRepeatedly( SetArrayArgument<2>(register21Values, register21Values + 2)); // Create mock services with the following expectations: // - 2 error messages in journal for N phase fault detected // - 1 N phase fault error logged with additional data MockServices services{}; MockJournal& journal = services.getMockJournal(); EXPECT_CALL(journal, logError(A())).Times(2); MockErrorLogging& errorLogging = services.getMockErrorLogging(); std::map additionalData{ {"vdd1_register_0xF", "[ 0xDA ]"}, {"vdd1_register_0x21", "[ 0x56, 0x14 ]"}}; EXPECT_CALL(errorLogging, logPhaseFault(Entry::Level::Warning, Ref(journal), PhaseFaultType::n, regulator->getFRU(), additionalData)) .Times(1); // Execute PhaseFaultDetection 5 times for (int i = 1; i <= 5; ++i) { detection.execute(services, *system, *chassis, *regulator); } } // Test where fails: Exception thrown by actions { // Create I2CCompareBitAction that will use the I2CInterface auto action = std::make_unique(0x7C, 2, 0); // Create PhaseFaultDetection std::vector> actions{}; actions.push_back(std::move(action)); PhaseFaultDetection detection{std::move(actions)}; // Set expectations for regulator I2C interface: // - isOpen() will return true // - reading 1 byte from register 0x7C will throw an I2CException EXPECT_CALL(*regI2CInterface, isOpen).WillRepeatedly(Return(true)); EXPECT_CALL(*regI2CInterface, read(0x7C, A())) .Times(5) .WillRepeatedly(Throw( i2c::I2CException{"Failed to read byte", "/dev/i2c-1", 0x70})); // Create mock services with the following expectations: // - 3 error messages in journal for exception // - 3 error messages in journal for inability to detect phase faults // - 1 I2C error logged MockServices services{}; MockJournal& journal = services.getMockJournal(); std::vector exceptionMessages{ "I2CException: Failed to read byte: bus /dev/i2c-1, addr 0x70", "ActionError: i2c_compare_bit: { register: 0x7C, position: 2, " "value: 0 }"}; EXPECT_CALL(journal, logError(exceptionMessages)).Times(3); EXPECT_CALL(journal, logError("Unable to detect phase faults in regulator vdd1")) .Times(3); MockErrorLogging& errorLogging = services.getMockErrorLogging(); EXPECT_CALL(errorLogging, logI2CError(Entry::Level::Warning, Ref(journal), "/dev/i2c-1", 0x70, 0)) .Times(1); // Execute PhaseFaultDetection 5 times for (int i = 1; i <= 5; ++i) { detection.execute(services, *system, *chassis, *regulator); } } } TEST_F(PhaseFaultDetectionTests, GetActions) { std::vector> actions{}; MockAction* action1 = new MockAction{}; actions.push_back(std::unique_ptr{action1}); MockAction* action2 = new MockAction{}; actions.push_back(std::unique_ptr{action2}); PhaseFaultDetection detection{std::move(actions)}; EXPECT_EQ(detection.getActions().size(), 2); EXPECT_EQ(detection.getActions()[0].get(), action1); EXPECT_EQ(detection.getActions()[1].get(), action2); } TEST_F(PhaseFaultDetectionTests, GetDeviceID) { // Test where device ID not specified { std::vector> actions{}; actions.push_back(std::make_unique()); PhaseFaultDetection detection{std::move(actions)}; EXPECT_EQ(detection.getDeviceID(), ""); } // Test where device ID not specified { std::vector> actions{}; actions.push_back(std::make_unique()); PhaseFaultDetection detection{std::move(actions), "ioexp1"}; EXPECT_EQ(detection.getDeviceID(), "ioexp1"); } }