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