1 /**
2  * Copyright © 2020 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.hpp"
17 #include "chassis.hpp"
18 #include "compare_presence_action.hpp"
19 #include "device.hpp"
20 #include "i2c_interface.hpp"
21 #include "mock_action.hpp"
22 #include "mock_error_logging.hpp"
23 #include "mock_journal.hpp"
24 #include "mock_presence_service.hpp"
25 #include "mock_services.hpp"
26 #include "mocked_i2c_interface.hpp"
27 #include "presence_detection.hpp"
28 #include "rule.hpp"
29 #include "system.hpp"
30 
31 #include <sdbusplus/exception.hpp>
32 
33 #include <memory>
34 #include <stdexcept>
35 #include <string>
36 #include <tuple>
37 #include <utility>
38 #include <vector>
39 
40 #include <gmock/gmock.h>
41 #include <gtest/gtest.h>
42 
43 using namespace phosphor::power::regulators;
44 
45 using ::testing::Ref;
46 using ::testing::Return;
47 using ::testing::Throw;
48 
49 /**
50  * Concrete subclass of sdbusplus::exception_t abstract base class.
51  */
52 class TestSDBusError : public sdbusplus::exception_t
53 {
54   public:
55     TestSDBusError(const std::string& error) : error{error}
56     {
57     }
58 
59     const char* what() const noexcept override
60     {
61         return error.c_str();
62     }
63 
64     const char* name() const noexcept override
65     {
66         return "";
67     }
68 
69     const char* description() const noexcept override
70     {
71         return "";
72     }
73 
74   private:
75     const std::string error{};
76 };
77 
78 /**
79  * Creates the parent objects that normally contain a PresenceDetection object.
80  *
81  * A PresenceDetection object is normally contained within a hierarchy of
82  * System, Chassis, and Device objects.  These objects are required in order to
83  * call the execute() method.
84  *
85  * Creates the System, Chassis, and Device objects.  The PresenceDetection
86  * object is moved into the Device object.
87  *
88  * @param detection PresenceDetection object to move into object hierarchy
89  * @return Pointers to the System, Chassis, and Device objects.  The Chassis and
90  *         Device objects are contained within the System object and will be
91  *         automatically destructed.
92  */
93 std::tuple<std::unique_ptr<System>, Chassis*, Device*>
94     createParentObjects(std::unique_ptr<PresenceDetection> detection)
95 {
96     // Create mock I2CInterface
97     std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
98         std::make_unique<i2c::MockedI2CInterface>();
99 
100     // Create Device that contains PresenceDetection
101     std::unique_ptr<Device> device = std::make_unique<Device>(
102         "vdd_reg", true,
103         "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg2",
104         std::move(i2cInterface), std::move(detection));
105     Device* devicePtr = device.get();
106 
107     // Create Chassis that contains Device
108     std::vector<std::unique_ptr<Device>> devices{};
109     devices.emplace_back(std::move(device));
110     std::unique_ptr<Chassis> chassis =
111         std::make_unique<Chassis>(1, std::move(devices));
112     Chassis* chassisPtr = chassis.get();
113 
114     // Create System that contains Chassis
115     std::vector<std::unique_ptr<Rule>> rules{};
116     std::vector<std::unique_ptr<Chassis>> chassisVec{};
117     chassisVec.emplace_back(std::move(chassis));
118     std::unique_ptr<System> system =
119         std::make_unique<System>(std::move(rules), std::move(chassisVec));
120 
121     return std::make_tuple(std::move(system), chassisPtr, devicePtr);
122 }
123 
124 TEST(PresenceDetectionTests, Constructor)
125 {
126     std::vector<std::unique_ptr<Action>> actions{};
127     actions.emplace_back(std::make_unique<MockAction>());
128 
129     PresenceDetection detection{std::move(actions)};
130     EXPECT_EQ(detection.getActions().size(), 1);
131     EXPECT_FALSE(detection.getCachedPresence().has_value());
132 }
133 
134 TEST(PresenceDetectionTests, ClearCache)
135 {
136     // Create MockAction that will return true once
137     std::unique_ptr<MockAction> action = std::make_unique<MockAction>();
138     EXPECT_CALL(*action, execute).Times(1).WillOnce(Return(true));
139 
140     // Create PresenceDetection
141     std::vector<std::unique_ptr<Action>> actions{};
142     actions.emplace_back(std::move(action));
143     PresenceDetection* detection = new PresenceDetection(std::move(actions));
144 
145     // Create parent System, Chassis, and Device objects
146     auto [system, chassis, device] =
147         createParentObjects(std::unique_ptr<PresenceDetection>{detection});
148 
149     // Verify that initially no presence value is cached
150     EXPECT_FALSE(detection->getCachedPresence().has_value());
151 
152     // Call execute() which should obtain and cache presence value
153     MockServices services{};
154     EXPECT_TRUE(detection->execute(services, *system, *chassis, *device));
155 
156     // Verify true presence value was cached
157     EXPECT_TRUE(detection->getCachedPresence().has_value());
158     EXPECT_TRUE(detection->getCachedPresence().value());
159 
160     // Clear cached presence value
161     detection->clearCache();
162 
163     // Verify that no presence value is cached
164     EXPECT_FALSE(detection->getCachedPresence().has_value());
165 }
166 
167 TEST(PresenceDetectionTests, Execute)
168 {
169     // Create ComparePresenceAction
170     std::unique_ptr<ComparePresenceAction> action =
171         std::make_unique<ComparePresenceAction>(
172             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2",
173             true);
174 
175     // Create PresenceDetection
176     std::vector<std::unique_ptr<Action>> actions{};
177     actions.emplace_back(std::move(action));
178     PresenceDetection* detection = new PresenceDetection(std::move(actions));
179 
180     // Create parent System, Chassis, and Device objects
181     auto [system, chassis, device] =
182         createParentObjects(std::unique_ptr<PresenceDetection>{detection});
183 
184     // Test where works: Present: Value is not cached
185     {
186         EXPECT_FALSE(detection->getCachedPresence().has_value());
187 
188         // Create MockServices.  MockPresenceService::isPresent() should return
189         // true.
190         MockServices services{};
191         MockPresenceService& presenceService =
192             services.getMockPresenceService();
193         EXPECT_CALL(presenceService,
194                     isPresent("/xyz/openbmc_project/inventory/system/chassis/"
195                               "motherboard/cpu2"))
196             .Times(1)
197             .WillOnce(Return(true));
198 
199         // Execute PresenceDetection
200         EXPECT_TRUE(detection->execute(services, *system, *chassis, *device));
201 
202         EXPECT_TRUE(detection->getCachedPresence().has_value());
203         EXPECT_TRUE(detection->getCachedPresence().value());
204     }
205 
206     // Test where works: Present: Value is cached
207     {
208         EXPECT_TRUE(detection->getCachedPresence().has_value());
209 
210         // Create MockServices.  MockPresenceService::isPresent() should not be
211         // called.
212         MockServices services{};
213         MockPresenceService& presenceService =
214             services.getMockPresenceService();
215         EXPECT_CALL(presenceService, isPresent).Times(0);
216 
217         // Execute PresenceDetection
218         EXPECT_TRUE(detection->execute(services, *system, *chassis, *device));
219     }
220 
221     // Test where works: Not present: Value is not cached
222     {
223         // Clear cached presence value
224         detection->clearCache();
225         EXPECT_FALSE(detection->getCachedPresence().has_value());
226 
227         // Create MockServices.  MockPresenceService::isPresent() should return
228         // false.
229         MockServices services{};
230         MockPresenceService& presenceService =
231             services.getMockPresenceService();
232         EXPECT_CALL(presenceService,
233                     isPresent("/xyz/openbmc_project/inventory/system/chassis/"
234                               "motherboard/cpu2"))
235             .Times(1)
236             .WillOnce(Return(false));
237 
238         // Execute PresenceDetection
239         EXPECT_FALSE(detection->execute(services, *system, *chassis, *device));
240 
241         EXPECT_TRUE(detection->getCachedPresence().has_value());
242         EXPECT_FALSE(detection->getCachedPresence().value());
243     }
244 
245     // Test where works: Not present: Value is cached
246     {
247         EXPECT_TRUE(detection->getCachedPresence().has_value());
248 
249         // Create MockServices.  MockPresenceService::isPresent() should not be
250         // called.
251         MockServices services{};
252         MockPresenceService& presenceService =
253             services.getMockPresenceService();
254         EXPECT_CALL(presenceService, isPresent).Times(0);
255 
256         // Execute PresenceDetection
257         EXPECT_FALSE(detection->execute(services, *system, *chassis, *device));
258     }
259 
260     // Test where fails
261     {
262         // Clear cached presence value
263         detection->clearCache();
264         EXPECT_FALSE(detection->getCachedPresence().has_value());
265 
266         // Create MockServices.  MockPresenceService::isPresent() should throw
267         // an exception.
268         MockServices services{};
269         MockPresenceService& presenceService =
270             services.getMockPresenceService();
271         EXPECT_CALL(presenceService,
272                     isPresent("/xyz/openbmc_project/inventory/system/chassis/"
273                               "motherboard/cpu2"))
274             .Times(1)
275             .WillOnce(Throw(TestSDBusError{"DBusError: Invalid object path."}));
276 
277         // Define expected journal messages that should be passed to MockJournal
278         MockJournal& journal = services.getMockJournal();
279         std::vector<std::string> exceptionMessages{
280             "DBusError: Invalid object path.",
281             "ActionError: compare_presence: { fru: "
282             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2, "
283             "value: true }"};
284         EXPECT_CALL(journal, logError(exceptionMessages)).Times(1);
285         EXPECT_CALL(journal,
286                     logError("Unable to determine presence of vdd_reg"))
287             .Times(1);
288 
289         // Expect logDBusError() to be called
290         MockErrorLogging& errorLogging = services.getMockErrorLogging();
291         EXPECT_CALL(errorLogging,
292                     logDBusError(Entry::Level::Warning, Ref(journal)))
293             .Times(1);
294 
295         // Execute PresenceDetection.  Should return true when an error occurs.
296         EXPECT_TRUE(detection->execute(services, *system, *chassis, *device));
297 
298         EXPECT_TRUE(detection->getCachedPresence().has_value());
299         EXPECT_TRUE(detection->getCachedPresence().value());
300     }
301 }
302 
303 TEST(PresenceDetectionTests, GetActions)
304 {
305     std::vector<std::unique_ptr<Action>> actions{};
306 
307     MockAction* action1 = new MockAction{};
308     actions.emplace_back(std::unique_ptr<MockAction>{action1});
309 
310     MockAction* action2 = new MockAction{};
311     actions.emplace_back(std::unique_ptr<MockAction>{action2});
312 
313     PresenceDetection detection{std::move(actions)};
314     EXPECT_EQ(detection.getActions().size(), 2);
315     EXPECT_EQ(detection.getActions()[0].get(), action1);
316     EXPECT_EQ(detection.getActions()[1].get(), action2);
317 }
318 
319 TEST(PresenceDetectionTests, GetCachedPresence)
320 {
321     // Create MockAction that will return false once
322     std::unique_ptr<MockAction> action = std::make_unique<MockAction>();
323     EXPECT_CALL(*action, execute).Times(1).WillOnce(Return(false));
324 
325     // Create PresenceDetection
326     std::vector<std::unique_ptr<Action>> actions{};
327     actions.emplace_back(std::move(action));
328     PresenceDetection* detection = new PresenceDetection(std::move(actions));
329 
330     // Create parent System, Chassis, and Device objects
331     auto [system, chassis, device] =
332         createParentObjects(std::unique_ptr<PresenceDetection>{detection});
333 
334     // Verify that initially no presence value is cached
335     EXPECT_FALSE(detection->getCachedPresence().has_value());
336 
337     // Call execute() which should obtain and cache presence value
338     MockServices services{};
339     EXPECT_FALSE(detection->execute(services, *system, *chassis, *device));
340 
341     // Verify false presence value was cached
342     EXPECT_TRUE(detection->getCachedPresence().has_value());
343     EXPECT_FALSE(detection->getCachedPresence().value());
344 
345     // Clear cached presence value
346     detection->clearCache();
347 
348     // Verify that no presence value is cached
349     EXPECT_FALSE(detection->getCachedPresence().has_value());
350 }
351