| /** |
| * 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 <cstdint> |
| #include <map> |
| #include <memory> |
| #include <stdexcept> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| 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<i2c::MockedI2CInterface>(); |
| this->regI2CInterface = regI2CInterface.get(); |
| |
| // Create regulator Device and save pointer |
| auto regulator = std::make_unique<Device>( |
| "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<i2c::MockedI2CInterface>(); |
| this->ioExpI2CInterface = ioExpI2CInterface.get(); |
| |
| // Create I/O expander Device and save pointer |
| auto ioExpander = std::make_unique<Device>( |
| "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<std::unique_ptr<Device>> devices{}; |
| devices.emplace_back(std::move(regulator)); |
| devices.emplace_back(std::move(ioExpander)); |
| auto chassis = std::make_unique<Chassis>( |
| 1, "/xyz/openbmc_project/inventory/system/chassis", |
| std::move(devices)); |
| this->chassis = chassis.get(); |
| |
| // Create System that contains Chassis and save pointer |
| std::vector<std::unique_ptr<Rule>> rules{}; |
| std::vector<std::unique_ptr<Chassis>> chassisVec{}; |
| chassisVec.emplace_back(std::move(chassis)); |
| this->system = std::make_unique<System>(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> system{}; |
| }; |
| |
| TEST_F(PhaseFaultDetectionTests, Constructor) |
| { |
| // Test where device ID not specified |
| { |
| std::vector<std::unique_ptr<Action>> actions{}; |
| actions.push_back(std::make_unique<MockAction>()); |
| |
| PhaseFaultDetection detection{std::move(actions)}; |
| EXPECT_EQ(detection.getActions().size(), 1); |
| EXPECT_EQ(detection.getDeviceID(), ""); |
| } |
| |
| // Test where device ID not specified |
| { |
| std::vector<std::unique_ptr<Action>> actions{}; |
| actions.push_back(std::make_unique<MockAction>()); |
| actions.push_back(std::make_unique<MockAction>()); |
| |
| PhaseFaultDetection detection{std::move(actions), "ioexp1"}; |
| EXPECT_EQ(detection.getActions().size(), 2); |
| EXPECT_EQ(detection.getDeviceID(), "ioexp1"); |
| } |
| } |
| |
| TEST_F(PhaseFaultDetectionTests, ClearErrorHistory) |
| { |
| std::vector<std::unique_ptr<Action>> 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<MockAction>(); |
| 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<LogPhaseFaultAction>(PhaseFaultType::n)); |
| |
| // Create a LogPhaseFaultAction that will log N+1 faults |
| actions.push_back( |
| std::make_unique<LogPhaseFaultAction>(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<std::string>{"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<I2CCompareBitAction>(0x1C, 2, 0); |
| |
| // Create PhaseFaultDetection. Specify device ID of I/O expander. |
| std::vector<std::unique_ptr<Action>> 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<uint8_t&>())).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<uint8_t&>())).Times(1); |
| |
| // Create mock services. Expect no errors to be logged. |
| MockServices services{}; |
| MockJournal& journal = services.getMockJournal(); |
| EXPECT_CALL(journal, logError(A<const std::string&>())).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<I2CCompareBitAction>(0x1C, 2, 0); |
| |
| // Create PhaseFaultDetection. Specify no device ID, which means the |
| // regulator should be used. |
| std::vector<std::unique_ptr<Action>> 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<uint8_t&>())).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<uint8_t&>())).Times(0); |
| |
| // Create mock services. Expect no errors to be logged. |
| MockServices services{}; |
| MockJournal& journal = services.getMockJournal(); |
| EXPECT_CALL(journal, logError(A<const std::string&>())).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<MockAction>(); |
| EXPECT_CALL(*action, execute).Times(3).WillRepeatedly(Return(true)); |
| |
| // Create PhaseFaultDetection |
| std::vector<std::unique_ptr<Action>> 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<const std::string&>())).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<MockAction>(); |
| 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<LogPhaseFaultAction>(PhaseFaultType::n); |
| |
| // Create an IfAction that will log an N phase fault in the |
| // ActionEnvironment if the mock condition is true. |
| std::vector<std::unique_ptr<Action>> thenActions{}; |
| thenActions.push_back(std::move(logPhaseFaultAction)); |
| auto ifAction = std::make_unique<IfAction>(std::move(conditionAction), |
| std::move(thenActions)); |
| |
| // Create PhaseFaultDetection |
| std::vector<std::unique_ptr<Action>> 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<MockAction>(); |
| 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<LogPhaseFaultAction>(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<std::unique_ptr<Action>> thenActions{}; |
| thenActions.push_back(std::move(logPhaseFaultAction)); |
| auto ifAction = std::make_unique<IfAction>(std::move(conditionAction), |
| std::move(thenActions)); |
| |
| // Create PhaseFaultDetection |
| std::vector<std::unique_ptr<Action>> 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<LogPhaseFaultAction>(PhaseFaultType::n); |
| |
| // Create PhaseFaultDetection |
| std::vector<std::unique_ptr<Action>> 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<std::string, std::string> 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<LogPhaseFaultAction>(PhaseFaultType::n_plus_1); |
| |
| // Create PhaseFaultDetection |
| std::vector<std::unique_ptr<Action>> 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<std::string, std::string> 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<std::unique_ptr<Action>> actions{}; |
| |
| // Create action that will log an N+1 phase fault in ActionEnvironment |
| actions.push_back( |
| std::make_unique<LogPhaseFaultAction>(PhaseFaultType::n_plus_1)); |
| |
| // Create action that will log an N phase fault in ActionEnvironment |
| actions.push_back( |
| std::make_unique<LogPhaseFaultAction>(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<std::string, std::string> 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<std::unique_ptr<Action>> actions{}; |
| |
| // Create action that will capture 1 byte from register 0x0F |
| actions.push_back(std::make_unique<I2CCaptureBytesAction>(0x0F, 1)); |
| |
| // Create action that will capture 2 bytes from register 0x21 |
| actions.push_back(std::make_unique<I2CCaptureBytesAction>(0x21, 2)); |
| |
| // Create action that will log an N phase fault in ActionEnvironment |
| actions.push_back( |
| std::make_unique<LogPhaseFaultAction>(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<uint8_t&>(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<uint8_t&>(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<const std::string&>())).Times(2); |
| MockErrorLogging& errorLogging = services.getMockErrorLogging(); |
| std::map<std::string, std::string> 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<I2CCompareBitAction>(0x7C, 2, 0); |
| |
| // Create PhaseFaultDetection |
| std::vector<std::unique_ptr<Action>> 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<uint8_t&>())) |
| .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<std::string> 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<std::unique_ptr<Action>> actions{}; |
| |
| MockAction* action1 = new MockAction{}; |
| actions.push_back(std::unique_ptr<MockAction>{action1}); |
| |
| MockAction* action2 = new MockAction{}; |
| actions.push_back(std::unique_ptr<MockAction>{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<std::unique_ptr<Action>> actions{}; |
| actions.push_back(std::make_unique<MockAction>()); |
| |
| PhaseFaultDetection detection{std::move(actions)}; |
| EXPECT_EQ(detection.getDeviceID(), ""); |
| } |
| |
| // Test where device ID not specified |
| { |
| std::vector<std::unique_ptr<Action>> actions{}; |
| actions.push_back(std::make_unique<MockAction>()); |
| |
| PhaseFaultDetection detection{std::move(actions), "ioexp1"}; |
| EXPECT_EQ(detection.getDeviceID(), "ioexp1"); |
| } |
| } |