1 /**
2  * Copyright © 2024 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 
17 #include "mock_services.hpp"
18 #include "rail.hpp"
19 #include "services.hpp"
20 #include "standard_device.hpp"
21 
22 #include <cstdint>
23 #include <map>
24 #include <memory>
25 #include <optional>
26 #include <string>
27 #include <utility>
28 #include <vector>
29 
30 #include <gmock/gmock.h>
31 #include <gtest/gtest.h>
32 
33 using namespace phosphor::power::sequencer;
34 
35 using ::testing::Return;
36 using ::testing::Throw;
37 
38 /**
39  * @class StandardDeviceImpl
40  *
41  * Concrete subclass of the StandardDevice abstract class.
42  *
43  * This subclass is required for two reasons:
44  * - StandardDevice has some pure virtual methods so it cannot be instantiated.
45  * - The pure virtual methods provide the PMBus and GPIO information.  Mocking
46  *   these makes it possible to test the pgood fault detection algorithm.
47  *
48  * This class is not intended to be used outside of this file.  It is
49  * implementation detail for testing the the StandardDevice class.
50  */
51 class StandardDeviceImpl : public StandardDevice
52 {
53   public:
54     // Specify which compiler-generated methods we want
55     StandardDeviceImpl() = delete;
56     StandardDeviceImpl(const StandardDeviceImpl&) = delete;
57     StandardDeviceImpl(StandardDeviceImpl&&) = delete;
58     StandardDeviceImpl& operator=(const StandardDeviceImpl&) = delete;
59     StandardDeviceImpl& operator=(StandardDeviceImpl&&) = delete;
60     virtual ~StandardDeviceImpl() = default;
61 
62     // Constructor just calls StandardDevice constructor
63     explicit StandardDeviceImpl(const std::string& name,
64                                 std::vector<std::unique_ptr<Rail>> rails) :
65         StandardDevice(name, std::move(rails))
66     {}
67 
68     // Mock pure virtual methods
69     MOCK_METHOD(std::vector<int>, getGPIOValues, (Services & services),
70                 (override));
71     MOCK_METHOD(uint16_t, getStatusWord, (uint8_t page), (override));
72     MOCK_METHOD(uint8_t, getStatusVout, (uint8_t page), (override));
73     MOCK_METHOD(double, getReadVout, (uint8_t page), (override));
74     MOCK_METHOD(double, getVoutUVFaultLimit, (uint8_t page), (override));
75 
76     // Override empty implementation with mock so we can verify it is called
77     MOCK_METHOD(void, prepareForPgoodFaultDetection, (Services & services),
78                 (override));
79 };
80 
81 /**
82  * Creates a Rail object that checks for a pgood fault using STATUS_VOUT.
83  *
84  * @param name Unique name for the rail
85  * @param isPowerSupplyRail Specifies whether the rail is produced by a
86                             power supply
87  * @param pageNum PMBus PAGE number of the rail
88  * @return Rail object
89  */
90 std::unique_ptr<Rail> createRailStatusVout(const std::string& name,
91                                            bool isPowerSupplyRail,
92                                            uint8_t pageNum)
93 {
94     std::optional<std::string> presence{};
95     std::optional<uint8_t> page{pageNum};
96     bool checkStatusVout{true};
97     bool compareVoltageToLimit{false};
98     std::optional<GPIO> gpio{};
99     return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
100                                   checkStatusVout, compareVoltageToLimit, gpio);
101 }
102 
103 /**
104  * Creates a Rail object that checks for a pgood fault using a GPIO.
105  *
106  * @param name Unique name for the rail
107  * @param isPowerSupplyRail Specifies whether the rail is produced by a
108                             power supply
109  * @param gpio GPIO line to read to determine the pgood status of the rail
110  * @return Rail object
111  */
112 std::unique_ptr<Rail> createRailGPIO(const std::string& name,
113                                      bool isPowerSupplyRail,
114                                      unsigned int gpioLine)
115 {
116     std::optional<std::string> presence{};
117     std::optional<uint8_t> page{};
118     bool checkStatusVout{false};
119     bool compareVoltageToLimit{false};
120     bool activeLow{false};
121     std::optional<GPIO> gpio{GPIO{gpioLine, activeLow}};
122     return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
123                                   checkStatusVout, compareVoltageToLimit, gpio);
124 }
125 
126 /**
127  * Creates a Rail object that checks for a pgood fault using output voltage.
128  *
129  * @param name Unique name for the rail
130  * @param isPowerSupplyRail Specifies whether the rail is produced by a
131                             power supply
132  * @param pageNum PMBus PAGE number of the rail
133  * @return Rail object
134  */
135 std::unique_ptr<Rail> createRailOutputVoltage(const std::string& name,
136                                               bool isPowerSupplyRail,
137                                               uint8_t pageNum)
138 {
139     std::optional<std::string> presence{};
140     std::optional<uint8_t> page{pageNum};
141     bool checkStatusVout{false};
142     bool compareVoltageToLimit{true};
143     std::optional<GPIO> gpio{};
144     return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
145                                   checkStatusVout, compareVoltageToLimit, gpio);
146 }
147 
148 TEST(StandardDeviceTests, Constructor)
149 {
150     // Empty vector of rails
151     {
152         std::vector<std::unique_ptr<Rail>> rails{};
153         StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
154 
155         EXPECT_EQ(device.getName(), "xyz_pseq");
156         EXPECT_TRUE(device.getRails().empty());
157     }
158 
159     // Non-empty vector of rails
160     {
161         std::vector<std::unique_ptr<Rail>> rails{};
162         rails.emplace_back(createRailGPIO("PSU", true, 3));
163         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
164         rails.emplace_back(createRailStatusVout("VIO", false, 7));
165         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
166 
167         EXPECT_EQ(device.getName(), "abc_pseq");
168         EXPECT_EQ(device.getRails().size(), 3);
169         EXPECT_EQ(device.getRails()[0]->getName(), "PSU");
170         EXPECT_EQ(device.getRails()[1]->getName(), "VDD");
171         EXPECT_EQ(device.getRails()[2]->getName(), "VIO");
172     }
173 }
174 
175 TEST(StandardDeviceTests, GetName)
176 {
177     std::vector<std::unique_ptr<Rail>> rails{};
178     StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
179 
180     EXPECT_EQ(device.getName(), "xyz_pseq");
181 }
182 
183 TEST(StandardDeviceTests, GetRails)
184 {
185     // Empty vector of rails
186     {
187         std::vector<std::unique_ptr<Rail>> rails{};
188         StandardDeviceImpl device{"xyz_pseq", std::move(rails)};
189 
190         EXPECT_TRUE(device.getRails().empty());
191     }
192 
193     // Non-empty vector of rails
194     {
195         std::vector<std::unique_ptr<Rail>> rails{};
196         rails.emplace_back(createRailGPIO("PSU", true, 3));
197         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
198         rails.emplace_back(createRailStatusVout("VIO", false, 7));
199         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
200 
201         EXPECT_EQ(device.getRails().size(), 3);
202         EXPECT_EQ(device.getRails()[0]->getName(), "PSU");
203         EXPECT_EQ(device.getRails()[1]->getName(), "VDD");
204         EXPECT_EQ(device.getRails()[2]->getName(), "VIO");
205     }
206 }
207 
208 TEST(StandardDeviceTests, FindPgoodFault)
209 {
210     // No rail has a pgood fault
211     {
212         std::vector<std::unique_ptr<Rail>> rails{};
213         rails.emplace_back(createRailGPIO("PSU", true, 2));
214         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
215         rails.emplace_back(createRailStatusVout("VIO", false, 7));
216         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
217 
218         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
219         std::vector<int> gpioValues{1, 1, 1};
220         EXPECT_CALL(device, getGPIOValues)
221             .Times(1)
222             .WillOnce(Return(gpioValues));
223         EXPECT_CALL(device, getReadVout(5)).Times(1).WillOnce(Return(1.2));
224         EXPECT_CALL(device, getVoutUVFaultLimit(5))
225             .Times(1)
226             .WillOnce(Return(1.1));
227         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
228 
229         MockServices services{};
230 
231         std::string powerSupplyError{};
232         std::map<std::string, std::string> additionalData{};
233         std::string error = device.findPgoodFault(services, powerSupplyError,
234                                                   additionalData);
235         EXPECT_TRUE(error.empty());
236         EXPECT_EQ(additionalData.size(), 0);
237     }
238 
239     // First rail has a pgood fault detected via GPIO
240     // Is a PSU rail: No PSU error specified
241     {
242         std::vector<std::unique_ptr<Rail>> rails{};
243         rails.emplace_back(createRailGPIO("PSU", true, 2));
244         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
245         rails.emplace_back(createRailStatusVout("VIO", false, 7));
246         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
247 
248         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
249         std::vector<int> gpioValues{1, 1, 0};
250         EXPECT_CALL(device, getGPIOValues)
251             .Times(1)
252             .WillOnce(Return(gpioValues));
253         EXPECT_CALL(device, getReadVout(5)).Times(0);
254         EXPECT_CALL(device, getVoutUVFaultLimit(5)).Times(0);
255         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
256 
257         MockServices services{};
258         EXPECT_CALL(services,
259                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
260             .Times(1);
261         EXPECT_CALL(
262             services,
263             logErrorMsg(
264                 "Pgood fault found in rail monitored by device abc_pseq"))
265             .Times(1);
266         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail PSU"))
267             .Times(1);
268         EXPECT_CALL(
269             services,
270             logErrorMsg(
271                 "Rail PSU pgood GPIO line offset 2 has inactive value 0"))
272             .Times(1);
273 
274         std::string powerSupplyError{};
275         std::map<std::string, std::string> additionalData{};
276         std::string error = device.findPgoodFault(services, powerSupplyError,
277                                                   additionalData);
278         EXPECT_EQ(error,
279                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
280         EXPECT_EQ(additionalData.size(), 5);
281         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
282         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
283         EXPECT_EQ(additionalData["RAIL_NAME"], "PSU");
284         EXPECT_EQ(additionalData["GPIO_LINE"], "2");
285         EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
286     }
287 
288     // First rail has a pgood fault detected via GPIO
289     // Is a PSU rail: PSU error specified
290     {
291         std::vector<std::unique_ptr<Rail>> rails{};
292         rails.emplace_back(createRailGPIO("PSU", true, 2));
293         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
294         rails.emplace_back(createRailStatusVout("VIO", false, 7));
295         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
296 
297         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
298         std::vector<int> gpioValues{1, 1, 0};
299         EXPECT_CALL(device, getGPIOValues)
300             .Times(1)
301             .WillOnce(Return(gpioValues));
302         EXPECT_CALL(device, getReadVout(5)).Times(0);
303         EXPECT_CALL(device, getVoutUVFaultLimit(5)).Times(0);
304         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
305 
306         MockServices services{};
307         EXPECT_CALL(services,
308                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
309             .Times(1);
310         EXPECT_CALL(
311             services,
312             logErrorMsg(
313                 "Pgood fault found in rail monitored by device abc_pseq"))
314             .Times(1);
315         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail PSU"))
316             .Times(1);
317         EXPECT_CALL(
318             services,
319             logErrorMsg(
320                 "Rail PSU pgood GPIO line offset 2 has inactive value 0"))
321             .Times(1);
322 
323         std::string powerSupplyError{"Undervoltage fault: PSU1"};
324         std::map<std::string, std::string> additionalData{};
325         std::string error = device.findPgoodFault(services, powerSupplyError,
326                                                   additionalData);
327         EXPECT_EQ(error, powerSupplyError);
328         EXPECT_EQ(additionalData.size(), 5);
329         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
330         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
331         EXPECT_EQ(additionalData["RAIL_NAME"], "PSU");
332         EXPECT_EQ(additionalData["GPIO_LINE"], "2");
333         EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
334     }
335 
336     // Second rail has a pgood fault detected via output voltage
337     // Not a PSU rail: PSU error specified
338     {
339         std::vector<std::unique_ptr<Rail>> rails{};
340         rails.emplace_back(createRailGPIO("PSU", true, 2));
341         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
342         rails.emplace_back(createRailStatusVout("VIO", false, 7));
343         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
344 
345         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
346         std::vector<int> gpioValues{1, 1, 1};
347         EXPECT_CALL(device, getGPIOValues)
348             .Times(1)
349             .WillOnce(Return(gpioValues));
350         EXPECT_CALL(device, getReadVout(5)).Times(1).WillOnce(Return(1.1));
351         EXPECT_CALL(device, getVoutUVFaultLimit(5))
352             .Times(1)
353             .WillOnce(Return(1.2));
354         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
355         EXPECT_CALL(device, getStatusWord(5)).Times(1).WillOnce(Return(0xbeef));
356 
357         MockServices services{};
358         EXPECT_CALL(services,
359                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 1]"))
360             .Times(1);
361         EXPECT_CALL(
362             services,
363             logErrorMsg(
364                 "Pgood fault found in rail monitored by device abc_pseq"))
365             .Times(1);
366         EXPECT_CALL(services, logInfoMsg("Rail VDD STATUS_WORD: 0xbeef"))
367             .Times(1);
368         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD"))
369             .Times(1);
370         EXPECT_CALL(
371             services,
372             logErrorMsg(
373                 "Rail VDD output voltage 1.1V is <= UV fault limit 1.2V"))
374             .Times(1);
375 
376         std::string powerSupplyError{"Undervoltage fault: PSU1"};
377         std::map<std::string, std::string> additionalData{};
378         std::string error = device.findPgoodFault(services, powerSupplyError,
379                                                   additionalData);
380         EXPECT_EQ(error,
381                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
382         EXPECT_EQ(additionalData.size(), 6);
383         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
384         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 1]");
385         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD");
386         EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
387         EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.2");
388         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
389     }
390 
391     // Third rail has a pgood fault detected via STATUS_VOUT
392     // Device returns 0 GPIO values
393     // Does not halt pgood fault detection because GPIO values not used by rails
394     {
395         std::vector<std::unique_ptr<Rail>> rails{};
396         rails.emplace_back(createRailStatusVout("PSU", true, 3));
397         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
398         rails.emplace_back(createRailStatusVout("VIO", false, 7));
399         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
400 
401         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
402         std::vector<int> gpioValues{};
403         EXPECT_CALL(device, getGPIOValues)
404             .Times(1)
405             .WillOnce(Return(gpioValues));
406         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
407         EXPECT_CALL(device, getReadVout(5)).Times(0);
408         EXPECT_CALL(device, getVoutUVFaultLimit(5)).Times(0);
409         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
410         EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
411 
412         MockServices services{};
413         EXPECT_CALL(
414             services,
415             logErrorMsg(
416                 "Pgood fault found in rail monitored by device abc_pseq"))
417             .Times(1);
418         EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
419             .Times(1);
420         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
421             .Times(1);
422         EXPECT_CALL(
423             services,
424             logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
425             .Times(1);
426 
427         std::string powerSupplyError{};
428         std::map<std::string, std::string> additionalData{};
429         std::string error = device.findPgoodFault(services, powerSupplyError,
430                                                   additionalData);
431         EXPECT_EQ(error,
432                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
433         EXPECT_EQ(additionalData.size(), 4);
434         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
435         EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
436         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
437         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
438     }
439 
440     // Third rail has a pgood fault detected via STATUS_VOUT
441     // Exception occurs trying to obtain GPIO values from device
442     // Does not halt pgood fault detection because GPIO values not used by rails
443     {
444         std::vector<std::unique_ptr<Rail>> rails{};
445         rails.emplace_back(createRailStatusVout("PSU", true, 3));
446         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
447         rails.emplace_back(createRailStatusVout("VIO", false, 7));
448         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
449 
450         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
451         EXPECT_CALL(device, getGPIOValues)
452             .Times(1)
453             .WillOnce(Throw(std::runtime_error{"Unable to acquire GPIO line"}));
454         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
455         EXPECT_CALL(device, getReadVout(5)).Times(0);
456         EXPECT_CALL(device, getVoutUVFaultLimit(5)).Times(0);
457         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
458         EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
459 
460         MockServices services{};
461         EXPECT_CALL(
462             services,
463             logErrorMsg(
464                 "Pgood fault found in rail monitored by device abc_pseq"))
465             .Times(1);
466         EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
467             .Times(1);
468         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
469             .Times(1);
470         EXPECT_CALL(
471             services,
472             logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
473             .Times(1);
474 
475         std::string powerSupplyError{};
476         std::map<std::string, std::string> additionalData{};
477         std::string error = device.findPgoodFault(services, powerSupplyError,
478                                                   additionalData);
479         EXPECT_EQ(error,
480                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
481         EXPECT_EQ(additionalData.size(), 4);
482         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
483         EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
484         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
485         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
486     }
487 
488     // All three rails appear to have a pgood fault.  Verify third rail is
489     // selected, even though it is last in the power on sequence, because it is
490     // checked using STATUS_VOUT.  That check happens before the other checks.
491     {
492         std::vector<std::unique_ptr<Rail>> rails{};
493         rails.emplace_back(createRailGPIO("PSU", true, 2));
494         rails.emplace_back(createRailGPIO("VDD", false, 1));
495         rails.emplace_back(createRailStatusVout("VIO", false, 7));
496         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
497 
498         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
499         std::vector<int> gpioValues{0, 0, 0};
500         EXPECT_CALL(device, getGPIOValues)
501             .Times(1)
502             .WillOnce(Return(gpioValues));
503         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x11));
504         EXPECT_CALL(device, getStatusWord(7)).Times(1).WillOnce(Return(0xbeef));
505 
506         MockServices services{};
507         EXPECT_CALL(services,
508                     logInfoMsg("Device abc_pseq GPIO values: [0, 0, 0]"))
509             .Times(1);
510         EXPECT_CALL(
511             services,
512             logErrorMsg(
513                 "Pgood fault found in rail monitored by device abc_pseq"))
514             .Times(1);
515         EXPECT_CALL(services, logInfoMsg("Rail VIO STATUS_WORD: 0xbeef"))
516             .Times(1);
517         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VIO"))
518             .Times(1);
519         EXPECT_CALL(
520             services,
521             logErrorMsg("Rail VIO has fault bits set in STATUS_VOUT: 0x11"))
522             .Times(1);
523 
524         std::string powerSupplyError{};
525         std::map<std::string, std::string> additionalData{};
526         std::string error = device.findPgoodFault(services, powerSupplyError,
527                                                   additionalData);
528         EXPECT_EQ(error,
529                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
530         EXPECT_EQ(additionalData.size(), 5);
531         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
532         EXPECT_EQ(additionalData["GPIO_VALUES"], "[0, 0, 0]");
533         EXPECT_EQ(additionalData["RAIL_NAME"], "VIO");
534         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x11");
535         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
536     }
537 
538     // Two rails appear to have a pgood fault.  One is found via output voltage
539     // and one is found via a GPIO.  Verify the first rail in the sequence with
540     // a fault is selected.
541     {
542         std::vector<std::unique_ptr<Rail>> rails{};
543         rails.emplace_back(createRailStatusVout("VIO", false, 7));
544         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
545         rails.emplace_back(createRailGPIO("PSU", true, 2));
546         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
547 
548         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
549         std::vector<int> gpioValues{1, 1, 0};
550         EXPECT_CALL(device, getGPIOValues)
551             .Times(1)
552             .WillOnce(Return(gpioValues));
553         EXPECT_CALL(device, getStatusVout(7)).Times(1).WillOnce(Return(0x00));
554         EXPECT_CALL(device, getReadVout(5)).Times(1).WillOnce(Return(1.1));
555         EXPECT_CALL(device, getVoutUVFaultLimit(5))
556             .Times(1)
557             .WillOnce(Return(1.2));
558         EXPECT_CALL(device, getStatusWord(5)).Times(1).WillOnce(Return(0xbeef));
559 
560         MockServices services{};
561         EXPECT_CALL(services,
562                     logInfoMsg("Device abc_pseq GPIO values: [1, 1, 0]"))
563             .Times(1);
564         EXPECT_CALL(
565             services,
566             logErrorMsg(
567                 "Pgood fault found in rail monitored by device abc_pseq"))
568             .Times(1);
569         EXPECT_CALL(services, logInfoMsg("Rail VDD STATUS_WORD: 0xbeef"))
570             .Times(1);
571         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD"))
572             .Times(1);
573         EXPECT_CALL(
574             services,
575             logErrorMsg(
576                 "Rail VDD output voltage 1.1V is <= UV fault limit 1.2V"))
577             .Times(1);
578 
579         std::string powerSupplyError{};
580         std::map<std::string, std::string> additionalData{};
581         std::string error = device.findPgoodFault(services, powerSupplyError,
582                                                   additionalData);
583         EXPECT_EQ(error,
584                   "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault");
585         EXPECT_EQ(additionalData.size(), 6);
586         EXPECT_EQ(additionalData["DEVICE_NAME"], "abc_pseq");
587         EXPECT_EQ(additionalData["GPIO_VALUES"], "[1, 1, 0]");
588         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD");
589         EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
590         EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.2");
591         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
592     }
593 
594     // Exception is thrown during pgood fault detection
595     {
596         std::vector<std::unique_ptr<Rail>> rails{};
597         rails.emplace_back(createRailGPIO("PSU", true, 2));
598         rails.emplace_back(createRailOutputVoltage("VDD", false, 5));
599         rails.emplace_back(createRailStatusVout("VIO", false, 7));
600         StandardDeviceImpl device{"abc_pseq", std::move(rails)};
601 
602         EXPECT_CALL(device, prepareForPgoodFaultDetection).Times(1);
603         std::vector<int> gpioValues{1, 1, 1};
604         EXPECT_CALL(device, getGPIOValues)
605             .Times(1)
606             .WillOnce(Return(gpioValues));
607         EXPECT_CALL(device, getReadVout(5)).Times(0);
608         EXPECT_CALL(device, getVoutUVFaultLimit(5)).Times(0);
609         EXPECT_CALL(device, getStatusVout(7))
610             .Times(1)
611             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
612 
613         MockServices services{};
614 
615         std::string powerSupplyError{};
616         std::map<std::string, std::string> additionalData{};
617         try
618         {
619             device.findPgoodFault(services, powerSupplyError, additionalData);
620             ADD_FAILURE() << "Should not have reached this line.";
621         }
622         catch (const std::exception& e)
623         {
624             EXPECT_STREQ(
625                 e.what(),
626                 "Unable to determine if a pgood fault occurred in device abc_pseq: "
627                 "Unable to read STATUS_VOUT value for rail VIO: "
628                 "File does not exist");
629         }
630     }
631 }
632