1 /**
2  * Copyright © 2021 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "action_environment.hpp"
17 #include "action_error.hpp"
18 #include "device.hpp"
19 #include "i2c_capture_bytes_action.hpp"
20 #include "i2c_interface.hpp"
21 #include "id_map.hpp"
22 #include "mock_services.hpp"
23 #include "mocked_i2c_interface.hpp"
24 
25 #include <cstdint>
26 #include <memory>
27 #include <stdexcept>
28 #include <string>
29 #include <utility>
30 
31 #include <gmock/gmock.h>
32 #include <gtest/gtest.h>
33 
34 using namespace phosphor::power::regulators;
35 
36 using ::testing::NotNull;
37 using ::testing::Return;
38 using ::testing::SetArrayArgument;
39 using ::testing::Throw;
40 using ::testing::TypedEq;
41 
42 TEST(I2CCaptureBytesActionTests, Constructor)
43 {
44     // Test where works
45     try
46     {
47         I2CCaptureBytesAction action{0x2A, 2};
48         EXPECT_EQ(action.getRegister(), 0x2A);
49         EXPECT_EQ(action.getCount(), 2);
50     }
51     catch (...)
52     {
53         ADD_FAILURE() << "Should not have caught exception.";
54     }
55 
56     // Test where fails: Count < 1
57     try
58     {
59         I2CCaptureBytesAction action{0x2A, 0};
60         ADD_FAILURE() << "Should not have reached this line.";
61     }
62     catch (const std::invalid_argument& e)
63     {
64         EXPECT_STREQ(e.what(), "Invalid byte count: Less than 1");
65     }
66     catch (...)
67     {
68         ADD_FAILURE() << "Should not have caught exception.";
69     }
70 }
71 
72 TEST(I2CCaptureBytesActionTests, Execute)
73 {
74     // Test where works: One byte captured
75     try
76     {
77         // Create mock I2CInterface: read() returns value 0xD7
78         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
79             std::make_unique<i2c::MockedI2CInterface>();
80         uint8_t values[] = {0xD7};
81         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
82         EXPECT_CALL(*i2cInterface, read(0xA0, TypedEq<uint8_t&>(1), NotNull(),
83                                         i2c::I2CInterface::Mode::I2C))
84             .Times(1)
85             .WillOnce(SetArrayArgument<2>(values, values + 1));
86 
87         // Create Device, IDMap, MockServices, and ActionEnvironment
88         Device device{
89             "vdd1", true,
90             "/xyz/openbmc_project/inventory/system/chassis/motherboard/vdd1",
91             std::move(i2cInterface)};
92         IDMap idMap{};
93         idMap.addDevice(device);
94         MockServices services{};
95         ActionEnvironment env{idMap, "vdd1", services};
96 
97         I2CCaptureBytesAction action{0xA0, 1};
98         EXPECT_EQ(action.execute(env), true);
99         EXPECT_EQ(env.getAdditionalErrorData().size(), 1);
100         EXPECT_EQ(env.getAdditionalErrorData().at("vdd1_register_0xA0"),
101                   "[ 0xD7 ]");
102     }
103     catch (...)
104     {
105         ADD_FAILURE() << "Should not have caught exception.";
106     }
107 
108     // Test where works: Multiple bytes captured
109     try
110     {
111         // Create mock I2CInterface: read() returns values 0x56, 0x14, 0xDA
112         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
113             std::make_unique<i2c::MockedI2CInterface>();
114         uint8_t values[] = {0x56, 0x14, 0xDA};
115         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
116         EXPECT_CALL(*i2cInterface, read(0x7C, TypedEq<uint8_t&>(3), NotNull(),
117                                         i2c::I2CInterface::Mode::I2C))
118             .Times(1)
119             .WillOnce(SetArrayArgument<2>(values, values + 3));
120 
121         // Create Device, IDMap, MockServices, and ActionEnvironment
122         Device device{
123             "vdd1", true,
124             "/xyz/openbmc_project/inventory/system/chassis/motherboard/vdd1",
125             std::move(i2cInterface)};
126         IDMap idMap{};
127         idMap.addDevice(device);
128         MockServices services{};
129         ActionEnvironment env{idMap, "vdd1", services};
130 
131         I2CCaptureBytesAction action{0x7C, 3};
132         EXPECT_EQ(action.execute(env), true);
133         EXPECT_EQ(env.getAdditionalErrorData().size(), 1);
134         EXPECT_EQ(env.getAdditionalErrorData().at("vdd1_register_0x7C"),
135                   "[ 0x56, 0x14, 0xDA ]");
136     }
137     catch (...)
138     {
139         ADD_FAILURE() << "Should not have caught exception.";
140     }
141 
142     // Test where works: Same device + register captured multiple times
143     try
144     {
145         // Create mock I2CInterface: read() will be called 3 times and will
146         // return values 0xD7, 0x13, and 0xFB
147         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
148             std::make_unique<i2c::MockedI2CInterface>();
149         uint8_t values_1[] = {0xD7};
150         uint8_t values_2[] = {0x13};
151         uint8_t values_3[] = {0xFB};
152         EXPECT_CALL(*i2cInterface, isOpen)
153             .Times(3)
154             .WillRepeatedly(Return(true));
155         EXPECT_CALL(*i2cInterface, read(0xCA, TypedEq<uint8_t&>(1), NotNull(),
156                                         i2c::I2CInterface::Mode::I2C))
157             .Times(3)
158             .WillOnce(SetArrayArgument<2>(values_1, values_1 + 1))
159             .WillOnce(SetArrayArgument<2>(values_2, values_2 + 1))
160             .WillOnce(SetArrayArgument<2>(values_3, values_3 + 1));
161 
162         // Create Device, IDMap, MockServices, and ActionEnvironment
163         Device device{
164             "vdd1", true,
165             "/xyz/openbmc_project/inventory/system/chassis/motherboard/vdd1",
166             std::move(i2cInterface)};
167         IDMap idMap{};
168         idMap.addDevice(device);
169         MockServices services{};
170         ActionEnvironment env{idMap, "vdd1", services};
171 
172         I2CCaptureBytesAction action{0xCA, 1};
173         EXPECT_EQ(action.execute(env), true);
174         EXPECT_EQ(action.execute(env), true);
175         EXPECT_EQ(action.execute(env), true);
176         EXPECT_EQ(env.getAdditionalErrorData().size(), 3);
177         EXPECT_EQ(env.getAdditionalErrorData().at("vdd1_register_0xCA"),
178                   "[ 0xD7 ]");
179         EXPECT_EQ(env.getAdditionalErrorData().at("vdd1_register_0xCA_2"),
180                   "[ 0x13 ]");
181         EXPECT_EQ(env.getAdditionalErrorData().at("vdd1_register_0xCA_3"),
182                   "[ 0xFB ]");
183     }
184     catch (...)
185     {
186         ADD_FAILURE() << "Should not have caught exception.";
187     }
188 
189     // Test where fails: Getting I2CInterface fails
190     try
191     {
192         // Create IDMap, MockServices, and ActionEnvironment
193         IDMap idMap{};
194         MockServices services{};
195         ActionEnvironment env{idMap, "vdd1", services};
196 
197         I2CCaptureBytesAction action{0x7C, 2};
198         action.execute(env);
199         ADD_FAILURE() << "Should not have reached this line.";
200     }
201     catch (const std::invalid_argument& e)
202     {
203         EXPECT_STREQ(e.what(), "Unable to find device with ID \"vdd1\"");
204     }
205     catch (...)
206     {
207         ADD_FAILURE() << "Should not have caught exception.";
208     }
209 
210     // Test where fails: Reading bytes fails
211     try
212     {
213         // Create mock I2CInterface: read() throws an I2CException
214         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
215             std::make_unique<i2c::MockedI2CInterface>();
216         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
217         EXPECT_CALL(*i2cInterface, read(0x7C, TypedEq<uint8_t&>(2), NotNull(),
218                                         i2c::I2CInterface::Mode::I2C))
219             .Times(1)
220             .WillOnce(Throw(i2c::I2CException{"Failed to read i2c block data",
221                                               "/dev/i2c-1", 0x70}));
222 
223         // Create Device, IDMap, MockServices, and ActionEnvironment
224         Device device{
225             "vdd1", true,
226             "/xyz/openbmc_project/inventory/system/chassis/motherboard/vdd1",
227             std::move(i2cInterface)};
228         IDMap idMap{};
229         idMap.addDevice(device);
230         MockServices services{};
231         ActionEnvironment env{idMap, "vdd1", services};
232 
233         I2CCaptureBytesAction action{0x7C, 2};
234         action.execute(env);
235         ADD_FAILURE() << "Should not have reached this line.";
236     }
237     catch (const ActionError& e)
238     {
239         EXPECT_STREQ(
240             e.what(),
241             "ActionError: i2c_capture_bytes: { register: 0x7C, count: 2 }");
242         try
243         {
244             // Re-throw inner I2CException
245             std::rethrow_if_nested(e);
246             ADD_FAILURE() << "Should not have reached this line.";
247         }
248         catch (const i2c::I2CException& ie)
249         {
250             EXPECT_STREQ(ie.what(),
251                          "I2CException: Failed to read i2c block data: bus "
252                          "/dev/i2c-1, addr 0x70");
253         }
254         catch (...)
255         {
256             ADD_FAILURE() << "Should not have caught exception.";
257         }
258     }
259     catch (...)
260     {
261         ADD_FAILURE() << "Should not have caught exception.";
262     }
263 }
264 
265 TEST(I2CCaptureBytesActionTests, GetCount)
266 {
267     I2CCaptureBytesAction action{0xA0, 3};
268     EXPECT_EQ(action.getCount(), 3);
269 }
270 
271 TEST(I2CCaptureBytesActionTests, GetRegister)
272 {
273     I2CCaptureBytesAction action{0xA0, 3};
274     EXPECT_EQ(action.getRegister(), 0xA0);
275 }
276 
277 TEST(I2CCaptureBytesActionTests, ToString)
278 {
279     I2CCaptureBytesAction action{0xA0, 3};
280     EXPECT_EQ(action.toString(),
281               "i2c_capture_bytes: { register: 0xA0, count: 3 }");
282 }
283