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_device.hpp"
18 #include "mock_services.hpp"
19 #include "rail.hpp"
20 
21 #include <cstdint>
22 #include <map>
23 #include <optional>
24 #include <string>
25 #include <vector>
26 
27 #include <gmock/gmock.h>
28 #include <gtest/gtest.h>
29 
30 using namespace phosphor::power::sequencer;
31 
32 using ::testing::Return;
33 using ::testing::Throw;
34 
35 TEST(GPIOTests, Initialization)
36 {
37     // Default initialization
38     {
39         GPIO gpio;
40         EXPECT_EQ(gpio.line, 0);
41         EXPECT_FALSE(gpio.activeLow);
42     }
43 
44     // Explicit initialization
45     {
46         GPIO gpio{48, true};
47         EXPECT_EQ(gpio.line, 48);
48         EXPECT_TRUE(gpio.activeLow);
49     }
50 }
51 
52 TEST(RailTests, Constructor)
53 {
54     // Test where succeeds: No optional parameters have values
55     {
56         std::string name{"12.0V"};
57         std::optional<std::string> presence{};
58         std::optional<uint8_t> page{};
59         bool isPowerSupplyRail{true};
60         bool checkStatusVout{false};
61         bool compareVoltageToLimit{false};
62         std::optional<GPIO> gpio{};
63         Rail rail{name,
64                   presence,
65                   page,
66                   isPowerSupplyRail,
67                   checkStatusVout,
68                   compareVoltageToLimit,
69                   gpio};
70 
71         EXPECT_EQ(rail.getName(), "12.0V");
72         EXPECT_FALSE(rail.getPresence().has_value());
73         EXPECT_FALSE(rail.getPage().has_value());
74         EXPECT_TRUE(rail.isPowerSupplyRail());
75         EXPECT_FALSE(rail.getCheckStatusVout());
76         EXPECT_FALSE(rail.getCompareVoltageToLimit());
77         EXPECT_FALSE(rail.getGPIO().has_value());
78     }
79 
80     // Test where succeeds: All optional parameters have values
81     {
82         std::string name{"VCS_CPU1"};
83         std::optional<std::string> presence{
84             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1"};
85         std::optional<uint8_t> page{11};
86         bool isPowerSupplyRail{false};
87         bool checkStatusVout{true};
88         bool compareVoltageToLimit{true};
89         std::optional<GPIO> gpio{GPIO(60, true)};
90         Rail rail{name,
91                   presence,
92                   page,
93                   isPowerSupplyRail,
94                   checkStatusVout,
95                   compareVoltageToLimit,
96                   gpio};
97 
98         EXPECT_EQ(rail.getName(), "VCS_CPU1");
99         EXPECT_TRUE(rail.getPresence().has_value());
100         EXPECT_EQ(
101             rail.getPresence().value(),
102             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1");
103         EXPECT_TRUE(rail.getPage().has_value());
104         EXPECT_EQ(rail.getPage().value(), 11);
105         EXPECT_FALSE(rail.isPowerSupplyRail());
106         EXPECT_TRUE(rail.getCheckStatusVout());
107         EXPECT_TRUE(rail.getCompareVoltageToLimit());
108         EXPECT_TRUE(rail.getGPIO().has_value());
109         EXPECT_EQ(rail.getGPIO().value().line, 60);
110         EXPECT_TRUE(rail.getGPIO().value().activeLow);
111     }
112 
113     // Test where fails: checkStatusVout is true and page has no value
114     {
115         std::string name{"VDD1"};
116         std::optional<std::string> presence{};
117         std::optional<uint8_t> page{};
118         bool isPowerSupplyRail{false};
119         bool checkStatusVout{true};
120         bool compareVoltageToLimit{false};
121         std::optional<GPIO> gpio{};
122         EXPECT_THROW((Rail{name, presence, page, isPowerSupplyRail,
123                            checkStatusVout, compareVoltageToLimit, gpio}),
124                      std::invalid_argument);
125     }
126 
127     // Test where fails: compareVoltageToLimit is true and page has no value
128     {
129         std::string name{"VDD1"};
130         std::optional<std::string> presence{};
131         std::optional<uint8_t> page{};
132         bool isPowerSupplyRail{false};
133         bool checkStatusVout{false};
134         bool compareVoltageToLimit{true};
135         std::optional<GPIO> gpio{};
136         EXPECT_THROW((Rail{name, presence, page, isPowerSupplyRail,
137                            checkStatusVout, compareVoltageToLimit, gpio}),
138                      std::invalid_argument);
139     }
140 }
141 
142 TEST(RailTests, GetName)
143 {
144     std::string name{"VDD2"};
145     std::optional<std::string> presence{};
146     std::optional<uint8_t> page{};
147     bool isPowerSupplyRail{false};
148     bool checkStatusVout{false};
149     bool compareVoltageToLimit{false};
150     std::optional<GPIO> gpio{};
151     Rail rail{name,
152               presence,
153               page,
154               isPowerSupplyRail,
155               checkStatusVout,
156               compareVoltageToLimit,
157               gpio};
158 
159     EXPECT_EQ(rail.getName(), "VDD2");
160 }
161 
162 TEST(RailTests, GetPresence)
163 {
164     std::string name{"VDDR2"};
165     std::optional<uint8_t> page{};
166     bool isPowerSupplyRail{false};
167     bool checkStatusVout{false};
168     bool compareVoltageToLimit{false};
169     std::optional<GPIO> gpio{};
170 
171     // Test where presence has no value
172     {
173         std::optional<std::string> presence{};
174         Rail rail{name,
175                   presence,
176                   page,
177                   isPowerSupplyRail,
178                   checkStatusVout,
179                   compareVoltageToLimit,
180                   gpio};
181         EXPECT_FALSE(rail.getPresence().has_value());
182     }
183 
184     // Test where presence has a value
185     {
186         std::optional<std::string> presence{
187             "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm2"};
188         Rail rail{name,
189                   presence,
190                   page,
191                   isPowerSupplyRail,
192                   checkStatusVout,
193                   compareVoltageToLimit,
194                   gpio};
195         EXPECT_TRUE(rail.getPresence().has_value());
196         EXPECT_EQ(
197             rail.getPresence().value(),
198             "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm2");
199     }
200 }
201 
202 TEST(RailTests, GetPage)
203 {
204     std::string name{"VDD2"};
205     std::optional<std::string> presence{};
206     bool isPowerSupplyRail{false};
207     bool checkStatusVout{false};
208     bool compareVoltageToLimit{false};
209     std::optional<GPIO> gpio{};
210 
211     // Test where page has no value
212     {
213         std::optional<uint8_t> page{};
214         Rail rail{name,
215                   presence,
216                   page,
217                   isPowerSupplyRail,
218                   checkStatusVout,
219                   compareVoltageToLimit,
220                   gpio};
221         EXPECT_FALSE(rail.getPage().has_value());
222     }
223 
224     // Test where page has a value
225     {
226         std::optional<uint8_t> page{7};
227         Rail rail{name,
228                   presence,
229                   page,
230                   isPowerSupplyRail,
231                   checkStatusVout,
232                   compareVoltageToLimit,
233                   gpio};
234         EXPECT_TRUE(rail.getPage().has_value());
235         EXPECT_EQ(rail.getPage().value(), 7);
236     }
237 }
238 
239 TEST(RailTests, IsPowerSupplyRail)
240 {
241     std::string name{"12.0V"};
242     std::optional<std::string> presence{};
243     std::optional<uint8_t> page{};
244     bool isPowerSupplyRail{true};
245     bool checkStatusVout{false};
246     bool compareVoltageToLimit{false};
247     std::optional<GPIO> gpio{};
248     Rail rail{name,
249               presence,
250               page,
251               isPowerSupplyRail,
252               checkStatusVout,
253               compareVoltageToLimit,
254               gpio};
255 
256     EXPECT_TRUE(rail.isPowerSupplyRail());
257 }
258 
259 TEST(RailTests, GetCheckStatusVout)
260 {
261     std::string name{"VDD2"};
262     std::optional<std::string> presence{};
263     std::optional<uint8_t> page{};
264     bool isPowerSupplyRail{false};
265     bool checkStatusVout{false};
266     bool compareVoltageToLimit{false};
267     std::optional<GPIO> gpio{};
268     Rail rail{name,
269               presence,
270               page,
271               isPowerSupplyRail,
272               checkStatusVout,
273               compareVoltageToLimit,
274               gpio};
275 
276     EXPECT_FALSE(rail.getCheckStatusVout());
277 }
278 
279 TEST(RailTests, GetCompareVoltageToLimit)
280 {
281     std::string name{"VDD2"};
282     std::optional<std::string> presence{};
283     std::optional<uint8_t> page{13};
284     bool isPowerSupplyRail{false};
285     bool checkStatusVout{false};
286     bool compareVoltageToLimit{true};
287     std::optional<GPIO> gpio{};
288     Rail rail{name,
289               presence,
290               page,
291               isPowerSupplyRail,
292               checkStatusVout,
293               compareVoltageToLimit,
294               gpio};
295 
296     EXPECT_TRUE(rail.getCompareVoltageToLimit());
297 }
298 
299 TEST(RailTests, GetGPIO)
300 {
301     std::string name{"VDD2"};
302     std::optional<std::string> presence{};
303     std::optional<uint8_t> page{};
304     bool isPowerSupplyRail{false};
305     bool checkStatusVout{false};
306     bool compareVoltageToLimit{false};
307 
308     // Test where gpio has no value
309     {
310         std::optional<GPIO> gpio{};
311         Rail rail{name,
312                   presence,
313                   page,
314                   isPowerSupplyRail,
315                   checkStatusVout,
316                   compareVoltageToLimit,
317                   gpio};
318         EXPECT_FALSE(rail.getGPIO().has_value());
319     }
320 
321     // Test where gpio has a value
322     {
323         std::optional<GPIO> gpio{GPIO(12, false)};
324         Rail rail{name,
325                   presence,
326                   page,
327                   isPowerSupplyRail,
328                   checkStatusVout,
329                   compareVoltageToLimit,
330                   gpio};
331         EXPECT_TRUE(rail.getGPIO().has_value());
332         EXPECT_EQ(rail.getGPIO().value().line, 12);
333         EXPECT_FALSE(rail.getGPIO().value().activeLow);
334     }
335 }
336 
337 TEST(RailTests, IsPresent)
338 {
339     std::string name{"VDD2"};
340     std::optional<uint8_t> page{};
341     bool isPowerSupplyRail{false};
342     bool checkStatusVout{false};
343     bool compareVoltageToLimit{false};
344     std::optional<GPIO> gpio{};
345 
346     // Test where inventory path not specified; always returns true
347     {
348         std::optional<std::string> presence{};
349         Rail rail{name,
350                   presence,
351                   page,
352                   isPowerSupplyRail,
353                   checkStatusVout,
354                   compareVoltageToLimit,
355                   gpio};
356 
357         MockServices services{};
358         EXPECT_CALL(services, isPresent).Times(0);
359 
360         EXPECT_TRUE(rail.isPresent(services));
361     }
362 
363     // Test where inventory path is not present
364     {
365         std::optional<std::string> presence{
366             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
367         Rail rail{name,
368                   presence,
369                   page,
370                   isPowerSupplyRail,
371                   checkStatusVout,
372                   compareVoltageToLimit,
373                   gpio};
374 
375         MockServices services{};
376         EXPECT_CALL(services, isPresent(*presence))
377             .Times(1)
378             .WillOnce(Return(false));
379 
380         EXPECT_FALSE(rail.isPresent(services));
381     }
382 
383     // Test where inventory path is present
384     {
385         std::optional<std::string> presence{
386             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
387         Rail rail{name,
388                   presence,
389                   page,
390                   isPowerSupplyRail,
391                   checkStatusVout,
392                   compareVoltageToLimit,
393                   gpio};
394 
395         MockServices services{};
396         EXPECT_CALL(services, isPresent(*presence))
397             .Times(1)
398             .WillOnce(Return(true));
399 
400         EXPECT_TRUE(rail.isPresent(services));
401     }
402 
403     // Test where exception occurs trying to get presence
404     {
405         std::optional<std::string> presence{
406             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
407         Rail rail{name,
408                   presence,
409                   page,
410                   isPowerSupplyRail,
411                   checkStatusVout,
412                   compareVoltageToLimit,
413                   gpio};
414 
415         MockServices services{};
416         EXPECT_CALL(services, isPresent(*presence))
417             .Times(1)
418             .WillOnce(Throw(std::runtime_error{"Invalid object path"}));
419 
420         try
421         {
422             rail.isPresent(services);
423             ADD_FAILURE() << "Should not have reached this line.";
424         }
425         catch (const std::exception& e)
426         {
427             EXPECT_STREQ(
428                 e.what(),
429                 "Unable to determine presence of rail VDD2 using "
430                 "inventory path "
431                 "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2: "
432                 "Invalid object path");
433         }
434     }
435 }
436 
437 TEST(RailTests, GetStatusWord)
438 {
439     std::string name{"VDD2"};
440     std::optional<std::string> presence{};
441     bool isPowerSupplyRail{false};
442     bool checkStatusVout{false};
443     bool compareVoltageToLimit{false};
444     std::optional<GPIO> gpio{};
445 
446     // Test where page was not specified: Throws exception
447     {
448         std::optional<uint8_t> page{};
449         Rail rail{name,
450                   presence,
451                   page,
452                   isPowerSupplyRail,
453                   checkStatusVout,
454                   compareVoltageToLimit,
455                   gpio};
456 
457         MockDevice device{};
458         EXPECT_CALL(device, getStatusWord).Times(0);
459 
460         try
461         {
462             rail.getStatusWord(device);
463             ADD_FAILURE() << "Should not have reached this line.";
464         }
465         catch (const std::exception& e)
466         {
467             EXPECT_STREQ(e.what(),
468                          "Unable to read STATUS_WORD value for rail VDD2: "
469                          "No PAGE number defined for rail VDD2");
470         }
471     }
472 
473     // Test where value read successfully
474     {
475         std::optional<uint8_t> page{2};
476         Rail rail{name,
477                   presence,
478                   page,
479                   isPowerSupplyRail,
480                   checkStatusVout,
481                   compareVoltageToLimit,
482                   gpio};
483 
484         MockDevice device{};
485         EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
486 
487         EXPECT_EQ(rail.getStatusWord(device), 0xbeef);
488     }
489 
490     // Test where exception occurs trying to read value
491     {
492         std::optional<uint8_t> page{2};
493         Rail rail{name,
494                   presence,
495                   page,
496                   isPowerSupplyRail,
497                   checkStatusVout,
498                   compareVoltageToLimit,
499                   gpio};
500 
501         MockDevice device{};
502         EXPECT_CALL(device, getStatusWord(2))
503             .Times(1)
504             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
505 
506         try
507         {
508             rail.getStatusWord(device);
509             ADD_FAILURE() << "Should not have reached this line.";
510         }
511         catch (const std::exception& e)
512         {
513             EXPECT_STREQ(e.what(),
514                          "Unable to read STATUS_WORD value for rail VDD2: "
515                          "File does not exist");
516         }
517     }
518 }
519 
520 TEST(RailTests, GetStatusVout)
521 {
522     std::string name{"VDD2"};
523     std::optional<std::string> presence{};
524     bool isPowerSupplyRail{false};
525     bool checkStatusVout{false};
526     bool compareVoltageToLimit{false};
527     std::optional<GPIO> gpio{};
528 
529     // Test where page was not specified: Throws exception
530     {
531         std::optional<uint8_t> page{};
532         Rail rail{name,
533                   presence,
534                   page,
535                   isPowerSupplyRail,
536                   checkStatusVout,
537                   compareVoltageToLimit,
538                   gpio};
539 
540         MockDevice device{};
541         EXPECT_CALL(device, getStatusVout).Times(0);
542 
543         try
544         {
545             rail.getStatusVout(device);
546             ADD_FAILURE() << "Should not have reached this line.";
547         }
548         catch (const std::exception& e)
549         {
550             EXPECT_STREQ(e.what(),
551                          "Unable to read STATUS_VOUT value for rail VDD2: "
552                          "No PAGE number defined for rail VDD2");
553         }
554     }
555 
556     // Test where value read successfully
557     {
558         std::optional<uint8_t> page{2};
559         Rail rail{name,
560                   presence,
561                   page,
562                   isPowerSupplyRail,
563                   checkStatusVout,
564                   compareVoltageToLimit,
565                   gpio};
566 
567         MockDevice device{};
568         EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0xad));
569 
570         EXPECT_EQ(rail.getStatusVout(device), 0xad);
571     }
572 
573     // Test where exception occurs trying to read value
574     {
575         std::optional<uint8_t> page{2};
576         Rail rail{name,
577                   presence,
578                   page,
579                   isPowerSupplyRail,
580                   checkStatusVout,
581                   compareVoltageToLimit,
582                   gpio};
583 
584         MockDevice device{};
585         EXPECT_CALL(device, getStatusVout(2))
586             .Times(1)
587             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
588 
589         try
590         {
591             rail.getStatusVout(device);
592             ADD_FAILURE() << "Should not have reached this line.";
593         }
594         catch (const std::exception& e)
595         {
596             EXPECT_STREQ(e.what(),
597                          "Unable to read STATUS_VOUT value for rail VDD2: "
598                          "File does not exist");
599         }
600     }
601 }
602 
603 TEST(RailTests, GetReadVout)
604 {
605     std::string name{"VDD2"};
606     std::optional<std::string> presence{};
607     bool isPowerSupplyRail{false};
608     bool checkStatusVout{false};
609     bool compareVoltageToLimit{false};
610     std::optional<GPIO> gpio{};
611 
612     // Test where page was not specified: Throws exception
613     {
614         std::optional<uint8_t> page{};
615         Rail rail{name,
616                   presence,
617                   page,
618                   isPowerSupplyRail,
619                   checkStatusVout,
620                   compareVoltageToLimit,
621                   gpio};
622 
623         MockDevice device{};
624         EXPECT_CALL(device, getReadVout).Times(0);
625 
626         try
627         {
628             rail.getReadVout(device);
629             ADD_FAILURE() << "Should not have reached this line.";
630         }
631         catch (const std::exception& e)
632         {
633             EXPECT_STREQ(e.what(),
634                          "Unable to read READ_VOUT value for rail VDD2: "
635                          "No PAGE number defined for rail VDD2");
636         }
637     }
638 
639     // Test where value read successfully
640     {
641         std::optional<uint8_t> page{2};
642         Rail rail{name,
643                   presence,
644                   page,
645                   isPowerSupplyRail,
646                   checkStatusVout,
647                   compareVoltageToLimit,
648                   gpio};
649 
650         MockDevice device{};
651         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.23));
652 
653         EXPECT_EQ(rail.getReadVout(device), 1.23);
654     }
655 
656     // Test where exception occurs trying to read value
657     {
658         std::optional<uint8_t> page{2};
659         Rail rail{name,
660                   presence,
661                   page,
662                   isPowerSupplyRail,
663                   checkStatusVout,
664                   compareVoltageToLimit,
665                   gpio};
666 
667         MockDevice device{};
668         EXPECT_CALL(device, getReadVout(2))
669             .Times(1)
670             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
671 
672         try
673         {
674             rail.getReadVout(device);
675             ADD_FAILURE() << "Should not have reached this line.";
676         }
677         catch (const std::exception& e)
678         {
679             EXPECT_STREQ(e.what(),
680                          "Unable to read READ_VOUT value for rail VDD2: "
681                          "File does not exist");
682         }
683     }
684 }
685 
686 TEST(RailTests, GetVoutUVFaultLimit)
687 {
688     std::string name{"VDD2"};
689     std::optional<std::string> presence{};
690     bool isPowerSupplyRail{false};
691     bool checkStatusVout{false};
692     bool compareVoltageToLimit{false};
693     std::optional<GPIO> gpio{};
694 
695     // Test where page was not specified: Throws exception
696     {
697         std::optional<uint8_t> page{};
698         Rail rail{name,
699                   presence,
700                   page,
701                   isPowerSupplyRail,
702                   checkStatusVout,
703                   compareVoltageToLimit,
704                   gpio};
705 
706         MockDevice device{};
707         EXPECT_CALL(device, getVoutUVFaultLimit).Times(0);
708 
709         try
710         {
711             rail.getVoutUVFaultLimit(device);
712             ADD_FAILURE() << "Should not have reached this line.";
713         }
714         catch (const std::exception& e)
715         {
716             EXPECT_STREQ(
717                 e.what(),
718                 "Unable to read VOUT_UV_FAULT_LIMIT value for rail VDD2: "
719                 "No PAGE number defined for rail VDD2");
720         }
721     }
722 
723     // Test where value read successfully
724     {
725         std::optional<uint8_t> page{2};
726         Rail rail{name,
727                   presence,
728                   page,
729                   isPowerSupplyRail,
730                   checkStatusVout,
731                   compareVoltageToLimit,
732                   gpio};
733 
734         MockDevice device{};
735         EXPECT_CALL(device, getVoutUVFaultLimit(2))
736             .Times(1)
737             .WillOnce(Return(0.9));
738 
739         EXPECT_EQ(rail.getVoutUVFaultLimit(device), 0.9);
740     }
741 
742     // Test where exception occurs trying to read value
743     {
744         std::optional<uint8_t> page{2};
745         Rail rail{name,
746                   presence,
747                   page,
748                   isPowerSupplyRail,
749                   checkStatusVout,
750                   compareVoltageToLimit,
751                   gpio};
752 
753         MockDevice device{};
754         EXPECT_CALL(device, getVoutUVFaultLimit(2))
755             .Times(1)
756             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
757 
758         try
759         {
760             rail.getVoutUVFaultLimit(device);
761             ADD_FAILURE() << "Should not have reached this line.";
762         }
763         catch (const std::exception& e)
764         {
765             EXPECT_STREQ(
766                 e.what(),
767                 "Unable to read VOUT_UV_FAULT_LIMIT value for rail VDD2: "
768                 "File does not exist");
769         }
770     }
771 }
772 
773 TEST(RailTests, HasPgoodFault)
774 {
775     std::string name{"VDD2"};
776     std::optional<std::string> presence{};
777     std::optional<uint8_t> page{2};
778     bool isPowerSupplyRail{false};
779     bool checkStatusVout{true};
780     bool compareVoltageToLimit{true};
781     bool activeLow{true};
782     std::optional<GPIO> gpio{GPIO(3, activeLow)};
783     Rail rail{name,
784               presence,
785               page,
786               isPowerSupplyRail,
787               checkStatusVout,
788               compareVoltageToLimit,
789               gpio};
790 
791     // No fault detected
792     {
793         MockDevice device{};
794         EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
795         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
796         EXPECT_CALL(device, getVoutUVFaultLimit(2))
797             .Times(1)
798             .WillOnce(Return(1.0));
799 
800         MockServices services{};
801 
802         std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
803         std::map<std::string, std::string> additionalData{};
804         EXPECT_FALSE(
805             rail.hasPgoodFault(device, services, gpioValues, additionalData));
806         EXPECT_EQ(additionalData.size(), 0);
807     }
808 
809     // Fault detected via STATUS_VOUT
810     {
811         MockDevice device{};
812         EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x10));
813         EXPECT_CALL(device, getReadVout(2)).Times(0);
814         EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
815 
816         MockServices services{};
817         EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
818             .Times(1);
819         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
820             .Times(1);
821         EXPECT_CALL(
822             services,
823             logErrorMsg("Rail VDD2 has fault bits set in STATUS_VOUT: 0x10"))
824             .Times(1);
825 
826         std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
827         std::map<std::string, std::string> additionalData{};
828         EXPECT_TRUE(
829             rail.hasPgoodFault(device, services, gpioValues, additionalData));
830         EXPECT_EQ(additionalData.size(), 3);
831         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
832         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x10");
833         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
834     }
835 
836     // Fault detected via GPIO
837     {
838         MockDevice device{};
839         EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
840         EXPECT_CALL(device, getReadVout(2)).Times(0);
841         EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
842 
843         MockServices services{};
844         EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
845             .Times(1);
846         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
847             .Times(1);
848         EXPECT_CALL(
849             services,
850             logErrorMsg(
851                 "Rail VDD2 pgood GPIO line offset 3 has inactive value 1"))
852             .Times(1);
853 
854         std::vector<int> gpioValues{0, 0, 0, 1, 0, 0};
855         std::map<std::string, std::string> additionalData{};
856         EXPECT_TRUE(
857             rail.hasPgoodFault(device, services, gpioValues, additionalData));
858         EXPECT_EQ(additionalData.size(), 4);
859         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
860         EXPECT_EQ(additionalData["GPIO_LINE"], "3");
861         EXPECT_EQ(additionalData["GPIO_VALUE"], "1");
862         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
863     }
864 
865     // Fault detected via output voltage
866     {
867         MockDevice device{};
868         EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
869         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
870         EXPECT_CALL(device, getVoutUVFaultLimit(2))
871             .Times(1)
872             .WillOnce(Return(1.1));
873         EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
874 
875         MockServices services{};
876         EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
877             .Times(1);
878         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
879             .Times(1);
880         EXPECT_CALL(
881             services,
882             logErrorMsg(
883                 "Rail VDD2 output voltage 1.1V is <= UV fault limit 1.1V"))
884             .Times(1);
885 
886         std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
887         std::map<std::string, std::string> additionalData{};
888         EXPECT_TRUE(
889             rail.hasPgoodFault(device, services, gpioValues, additionalData));
890         EXPECT_EQ(additionalData.size(), 4);
891         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
892         EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
893         EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.1");
894         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
895     }
896 }
897 
898 TEST(RailTests, HasPgoodFaultStatusVout)
899 {
900     std::string name{"VDD2"};
901     std::optional<uint8_t> page{3};
902     bool isPowerSupplyRail{false};
903     bool compareVoltageToLimit{false};
904     std::optional<GPIO> gpio{};
905 
906     // Test where presence check defined: Rail is not present
907     {
908         std::optional<std::string> presence{
909             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
910         bool checkStatusVout{true};
911         Rail rail{name,
912                   presence,
913                   page,
914                   isPowerSupplyRail,
915                   checkStatusVout,
916                   compareVoltageToLimit,
917                   gpio};
918 
919         MockDevice device{};
920         EXPECT_CALL(device, getStatusVout(3)).Times(0);
921 
922         MockServices services{};
923         EXPECT_CALL(services, isPresent(*presence))
924             .Times(1)
925             .WillOnce(Return(false));
926 
927         std::map<std::string, std::string> additionalData{};
928         EXPECT_FALSE(
929             rail.hasPgoodFaultStatusVout(device, services, additionalData));
930         EXPECT_EQ(additionalData.size(), 0);
931     }
932 
933     // Test where presence check defined: Rail is present: No fault detected
934     {
935         std::optional<std::string> presence{
936             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
937         bool checkStatusVout{true};
938         Rail rail{name,
939                   presence,
940                   page,
941                   isPowerSupplyRail,
942                   checkStatusVout,
943                   compareVoltageToLimit,
944                   gpio};
945 
946         MockDevice device{};
947         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
948 
949         MockServices services{};
950         EXPECT_CALL(services, isPresent(*presence))
951             .Times(1)
952             .WillOnce(Return(true));
953 
954         std::map<std::string, std::string> additionalData{};
955         EXPECT_FALSE(
956             rail.hasPgoodFaultStatusVout(device, services, additionalData));
957         EXPECT_EQ(additionalData.size(), 0);
958     }
959 
960     // Test where STATUS_VOUT check is not defined
961     {
962         std::optional<std::string> presence{};
963         bool checkStatusVout{false};
964         Rail rail{name,
965                   presence,
966                   page,
967                   isPowerSupplyRail,
968                   checkStatusVout,
969                   compareVoltageToLimit,
970                   gpio};
971 
972         MockDevice device{};
973         EXPECT_CALL(device, getStatusVout(3)).Times(0);
974 
975         MockServices services{};
976 
977         std::map<std::string, std::string> additionalData{};
978         EXPECT_FALSE(
979             rail.hasPgoodFaultStatusVout(device, services, additionalData));
980         EXPECT_EQ(additionalData.size(), 0);
981     }
982 
983     // Test where no fault detected: No warning bits set
984     {
985         std::optional<std::string> presence{};
986         bool checkStatusVout{true};
987         Rail rail{name,
988                   presence,
989                   page,
990                   isPowerSupplyRail,
991                   checkStatusVout,
992                   compareVoltageToLimit,
993                   gpio};
994 
995         MockDevice device{};
996         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x00));
997 
998         MockServices services{};
999         EXPECT_CALL(services, logInfoMsg).Times(0);
1000 
1001         std::map<std::string, std::string> additionalData{};
1002         EXPECT_FALSE(
1003             rail.hasPgoodFaultStatusVout(device, services, additionalData));
1004         EXPECT_EQ(additionalData.size(), 0);
1005     }
1006 
1007     // Test where no fault detected: Warning bits set
1008     {
1009         std::optional<std::string> presence{};
1010         bool checkStatusVout{true};
1011         Rail rail{name,
1012                   presence,
1013                   page,
1014                   isPowerSupplyRail,
1015                   checkStatusVout,
1016                   compareVoltageToLimit,
1017                   gpio};
1018 
1019         MockDevice device{};
1020         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x6a));
1021 
1022         MockServices services{};
1023         EXPECT_CALL(
1024             services,
1025             logInfoMsg("Rail VDD2 has warning bits set in STATUS_VOUT: 0x6a"))
1026             .Times(1);
1027 
1028         std::map<std::string, std::string> additionalData{};
1029         EXPECT_FALSE(
1030             rail.hasPgoodFaultStatusVout(device, services, additionalData));
1031         EXPECT_EQ(additionalData.size(), 0);
1032     }
1033 
1034     // Test where fault detected
1035     // STATUS_WORD captured in additional data
1036     {
1037         std::optional<std::string> presence{};
1038         bool checkStatusVout{true};
1039         Rail rail{name,
1040                   presence,
1041                   page,
1042                   isPowerSupplyRail,
1043                   checkStatusVout,
1044                   compareVoltageToLimit,
1045                   gpio};
1046 
1047         MockDevice device{};
1048         EXPECT_CALL(device, getStatusVout(3)).Times(1).WillOnce(Return(0x10));
1049         EXPECT_CALL(device, getStatusWord(3)).Times(1).WillOnce(Return(0xbeef));
1050 
1051         MockServices services{};
1052         EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
1053             .Times(1);
1054         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
1055             .Times(1);
1056         EXPECT_CALL(
1057             services,
1058             logErrorMsg("Rail VDD2 has fault bits set in STATUS_VOUT: 0x10"))
1059             .Times(1);
1060 
1061         std::map<std::string, std::string> additionalData{};
1062         EXPECT_TRUE(
1063             rail.hasPgoodFaultStatusVout(device, services, additionalData));
1064         EXPECT_EQ(additionalData.size(), 3);
1065         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
1066         EXPECT_EQ(additionalData["STATUS_VOUT"], "0x10");
1067         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
1068     }
1069 
1070     // Test where exception thrown
1071     {
1072         std::optional<std::string> presence{};
1073         bool checkStatusVout{true};
1074         Rail rail{name,
1075                   presence,
1076                   page,
1077                   isPowerSupplyRail,
1078                   checkStatusVout,
1079                   compareVoltageToLimit,
1080                   gpio};
1081 
1082         MockDevice device{};
1083         EXPECT_CALL(device, getStatusVout(3))
1084             .Times(1)
1085             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
1086 
1087         MockServices services{};
1088 
1089         std::map<std::string, std::string> additionalData{};
1090         try
1091         {
1092             rail.hasPgoodFaultStatusVout(device, services, additionalData);
1093             ADD_FAILURE() << "Should not have reached this line.";
1094         }
1095         catch (const std::exception& e)
1096         {
1097             EXPECT_STREQ(e.what(),
1098                          "Unable to read STATUS_VOUT value for rail VDD2: "
1099                          "File does not exist");
1100         }
1101     }
1102 }
1103 
1104 TEST(RailTests, HasPgoodFaultGPIO)
1105 {
1106     std::string name{"VDD2"};
1107     bool isPowerSupplyRail{false};
1108     bool checkStatusVout{false};
1109     bool compareVoltageToLimit{false};
1110 
1111     // Test where presence check defined: Rail is not present
1112     {
1113         std::optional<std::string> presence{
1114             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
1115         std::optional<uint8_t> page{3};
1116         bool activeLow{false};
1117         std::optional<GPIO> gpio{GPIO(3, activeLow)};
1118         Rail rail{name,
1119                   presence,
1120                   page,
1121                   isPowerSupplyRail,
1122                   checkStatusVout,
1123                   compareVoltageToLimit,
1124                   gpio};
1125 
1126         MockDevice device{};
1127 
1128         MockServices services{};
1129         EXPECT_CALL(services, isPresent(*presence))
1130             .Times(1)
1131             .WillOnce(Return(false));
1132 
1133         std::vector<int> gpioValues{1, 1, 1, 0, 1, 1};
1134         std::map<std::string, std::string> additionalData{};
1135         EXPECT_FALSE(rail.hasPgoodFaultGPIO(device, services, gpioValues,
1136                                             additionalData));
1137         EXPECT_EQ(additionalData.size(), 0);
1138     }
1139 
1140     // Test where presence check defined: Rail is present: No fault detected
1141     {
1142         std::optional<std::string> presence{
1143             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
1144         std::optional<uint8_t> page{3};
1145         bool activeLow{false};
1146         std::optional<GPIO> gpio{GPIO(3, activeLow)};
1147         Rail rail{name,
1148                   presence,
1149                   page,
1150                   isPowerSupplyRail,
1151                   checkStatusVout,
1152                   compareVoltageToLimit,
1153                   gpio};
1154 
1155         MockDevice device{};
1156 
1157         MockServices services{};
1158         EXPECT_CALL(services, isPresent(*presence))
1159             .Times(1)
1160             .WillOnce(Return(true));
1161 
1162         std::vector<int> gpioValues{1, 1, 1, 1, 1, 1};
1163         std::map<std::string, std::string> additionalData{};
1164         EXPECT_FALSE(rail.hasPgoodFaultGPIO(device, services, gpioValues,
1165                                             additionalData));
1166         EXPECT_EQ(additionalData.size(), 0);
1167     }
1168 
1169     // Test where GPIO check not defined
1170     {
1171         std::optional<std::string> presence{};
1172         std::optional<uint8_t> page{3};
1173         std::optional<GPIO> gpio{};
1174         Rail rail{name,
1175                   presence,
1176                   page,
1177                   isPowerSupplyRail,
1178                   checkStatusVout,
1179                   compareVoltageToLimit,
1180                   gpio};
1181 
1182         MockDevice device{};
1183 
1184         MockServices services{};
1185 
1186         std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
1187         std::map<std::string, std::string> additionalData{};
1188         EXPECT_FALSE(rail.hasPgoodFaultGPIO(device, services, gpioValues,
1189                                             additionalData));
1190         EXPECT_EQ(additionalData.size(), 0);
1191     }
1192 
1193     // Test where no fault detected
1194     // GPIO value is 1 and GPIO is active high
1195     {
1196         std::optional<std::string> presence{};
1197         std::optional<uint8_t> page{};
1198         bool activeLow{false};
1199         std::optional<GPIO> gpio{GPIO(3, activeLow)};
1200         Rail rail{name,
1201                   presence,
1202                   page,
1203                   isPowerSupplyRail,
1204                   checkStatusVout,
1205                   compareVoltageToLimit,
1206                   gpio};
1207 
1208         MockDevice device{};
1209 
1210         MockServices services{};
1211 
1212         std::vector<int> gpioValues{1, 1, 1, 1, 1, 1};
1213         std::map<std::string, std::string> additionalData{};
1214         EXPECT_FALSE(rail.hasPgoodFaultGPIO(device, services, gpioValues,
1215                                             additionalData));
1216         EXPECT_EQ(additionalData.size(), 0);
1217     }
1218 
1219     // Test where fault detected
1220     // GPIO value is 0 and GPIO is active high
1221     // STATUS_WORD not captured since no PMBus page defined
1222     {
1223         std::optional<std::string> presence{};
1224         std::optional<uint8_t> page{};
1225         bool activeLow{false};
1226         std::optional<GPIO> gpio{GPIO(3, activeLow)};
1227         Rail rail{name,
1228                   presence,
1229                   page,
1230                   isPowerSupplyRail,
1231                   checkStatusVout,
1232                   compareVoltageToLimit,
1233                   gpio};
1234 
1235         MockDevice device{};
1236 
1237         MockServices services{};
1238         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
1239             .Times(1);
1240         EXPECT_CALL(
1241             services,
1242             logErrorMsg(
1243                 "Rail VDD2 pgood GPIO line offset 3 has inactive value 0"))
1244             .Times(1);
1245 
1246         std::vector<int> gpioValues{1, 1, 1, 0, 1, 1};
1247         std::map<std::string, std::string> additionalData{};
1248         EXPECT_TRUE(rail.hasPgoodFaultGPIO(device, services, gpioValues,
1249                                            additionalData));
1250         EXPECT_EQ(additionalData.size(), 3);
1251         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
1252         EXPECT_EQ(additionalData["GPIO_LINE"], "3");
1253         EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
1254     }
1255 
1256     // Test where fault detected
1257     // GPIO value is 1 and GPIO is active low
1258     {
1259         std::optional<std::string> presence{};
1260         std::optional<uint8_t> page{2};
1261         bool activeLow{true};
1262         std::optional<GPIO> gpio{GPIO(3, activeLow)};
1263         Rail rail{name,
1264                   presence,
1265                   page,
1266                   isPowerSupplyRail,
1267                   checkStatusVout,
1268                   compareVoltageToLimit,
1269                   gpio};
1270 
1271         MockDevice device{};
1272         EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
1273 
1274         MockServices services{};
1275         EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
1276             .Times(1);
1277         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
1278             .Times(1);
1279         EXPECT_CALL(
1280             services,
1281             logErrorMsg(
1282                 "Rail VDD2 pgood GPIO line offset 3 has inactive value 1"))
1283             .Times(1);
1284 
1285         std::vector<int> gpioValues{0, 0, 0, 1, 0, 0};
1286         std::map<std::string, std::string> additionalData{};
1287         EXPECT_TRUE(rail.hasPgoodFaultGPIO(device, services, gpioValues,
1288                                            additionalData));
1289         EXPECT_EQ(additionalData.size(), 4);
1290         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
1291         EXPECT_EQ(additionalData["GPIO_LINE"], "3");
1292         EXPECT_EQ(additionalData["GPIO_VALUE"], "1");
1293         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
1294     }
1295 
1296     // Test where exception thrown
1297     {
1298         std::optional<std::string> presence{};
1299         std::optional<uint8_t> page{};
1300         bool activeLow{false};
1301         std::optional<GPIO> gpio{GPIO(6, activeLow)};
1302         Rail rail{name,
1303                   presence,
1304                   page,
1305                   isPowerSupplyRail,
1306                   checkStatusVout,
1307                   compareVoltageToLimit,
1308                   gpio};
1309 
1310         MockDevice device{};
1311 
1312         MockServices services{};
1313 
1314         std::vector<int> gpioValues{1, 1, 1, 1, 1, 1};
1315         std::map<std::string, std::string> additionalData{};
1316         try
1317         {
1318             rail.hasPgoodFaultGPIO(device, services, gpioValues,
1319                                    additionalData);
1320             ADD_FAILURE() << "Should not have reached this line.";
1321         }
1322         catch (const std::exception& e)
1323         {
1324             EXPECT_STREQ(e.what(), "Invalid GPIO line offset 6 for rail VDD2: "
1325                                    "Device only has 6 GPIO values");
1326         }
1327     }
1328 }
1329 
1330 TEST(RailTests, HasPgoodFaultOutputVoltage)
1331 {
1332     std::string name{"VDD2"};
1333     std::optional<uint8_t> page{2};
1334     bool isPowerSupplyRail{false};
1335     bool checkStatusVout{false};
1336     std::optional<GPIO> gpio{};
1337 
1338     // Test where presence check defined: Rail is not present
1339     {
1340         std::optional<std::string> presence{
1341             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
1342         bool compareVoltageToLimit{true};
1343         Rail rail{name,
1344                   presence,
1345                   page,
1346                   isPowerSupplyRail,
1347                   checkStatusVout,
1348                   compareVoltageToLimit,
1349                   gpio};
1350 
1351         MockDevice device{};
1352         EXPECT_CALL(device, getReadVout(2)).Times(0);
1353         EXPECT_CALL(device, getVoutUVFaultLimit(2)).Times(0);
1354 
1355         MockServices services{};
1356         EXPECT_CALL(services, isPresent(*presence))
1357             .Times(1)
1358             .WillOnce(Return(false));
1359 
1360         std::map<std::string, std::string> additionalData{};
1361         EXPECT_FALSE(
1362             rail.hasPgoodFaultOutputVoltage(device, services, additionalData));
1363         EXPECT_EQ(additionalData.size(), 0);
1364     }
1365 
1366     // Test where presence check defined: Rail is present: No fault detected
1367     {
1368         std::optional<std::string> presence{
1369             "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
1370         bool compareVoltageToLimit{true};
1371         Rail rail{name,
1372                   presence,
1373                   page,
1374                   isPowerSupplyRail,
1375                   checkStatusVout,
1376                   compareVoltageToLimit,
1377                   gpio};
1378 
1379         MockDevice device{};
1380         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
1381         EXPECT_CALL(device, getVoutUVFaultLimit(2))
1382             .Times(1)
1383             .WillOnce(Return(1.0));
1384 
1385         MockServices services{};
1386         EXPECT_CALL(services, isPresent(*presence))
1387             .Times(1)
1388             .WillOnce(Return(true));
1389 
1390         std::map<std::string, std::string> additionalData{};
1391         EXPECT_FALSE(
1392             rail.hasPgoodFaultOutputVoltage(device, services, additionalData));
1393         EXPECT_EQ(additionalData.size(), 0);
1394     }
1395 
1396     // Test where voltage output check not specified
1397     {
1398         std::optional<std::string> presence{};
1399         bool compareVoltageToLimit{false};
1400         Rail rail{name,
1401                   presence,
1402                   page,
1403                   isPowerSupplyRail,
1404                   checkStatusVout,
1405                   compareVoltageToLimit,
1406                   gpio};
1407 
1408         MockDevice device{};
1409         EXPECT_CALL(device, getReadVout(2)).Times(0);
1410         EXPECT_CALL(device, getVoutUVFaultLimit(2)).Times(0);
1411 
1412         MockServices services{};
1413 
1414         std::map<std::string, std::string> additionalData{};
1415         EXPECT_FALSE(
1416             rail.hasPgoodFaultOutputVoltage(device, services, additionalData));
1417         EXPECT_EQ(additionalData.size(), 0);
1418     }
1419 
1420     // Test where no fault detected: Output voltage > UV limit
1421     {
1422         std::optional<std::string> presence{};
1423         bool compareVoltageToLimit{true};
1424         Rail rail{name,
1425                   presence,
1426                   page,
1427                   isPowerSupplyRail,
1428                   checkStatusVout,
1429                   compareVoltageToLimit,
1430                   gpio};
1431 
1432         MockDevice device{};
1433         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
1434         EXPECT_CALL(device, getVoutUVFaultLimit(2))
1435             .Times(1)
1436             .WillOnce(Return(1.0));
1437 
1438         MockServices services{};
1439 
1440         std::map<std::string, std::string> additionalData{};
1441         EXPECT_FALSE(
1442             rail.hasPgoodFaultOutputVoltage(device, services, additionalData));
1443         EXPECT_EQ(additionalData.size(), 0);
1444     }
1445 
1446     // Test where fault detected: Output voltage < UV limit
1447     {
1448         std::optional<std::string> presence{};
1449         bool compareVoltageToLimit{true};
1450         Rail rail{name,
1451                   presence,
1452                   page,
1453                   isPowerSupplyRail,
1454                   checkStatusVout,
1455                   compareVoltageToLimit,
1456                   gpio};
1457 
1458         MockDevice device{};
1459         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
1460         EXPECT_CALL(device, getVoutUVFaultLimit(2))
1461             .Times(1)
1462             .WillOnce(Return(1.2));
1463         EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
1464 
1465         MockServices services{};
1466         EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
1467             .Times(1);
1468         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
1469             .Times(1);
1470         EXPECT_CALL(
1471             services,
1472             logErrorMsg(
1473                 "Rail VDD2 output voltage 1.1V is <= UV fault limit 1.2V"))
1474             .Times(1);
1475 
1476         std::map<std::string, std::string> additionalData{};
1477         EXPECT_TRUE(
1478             rail.hasPgoodFaultOutputVoltage(device, services, additionalData));
1479         EXPECT_EQ(additionalData.size(), 4);
1480         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
1481         EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
1482         EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.2");
1483         EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
1484     }
1485 
1486     // Test where fault detected: Output voltage == UV limit
1487     // STATUS_WORD not captured because reading it caused an exception
1488     {
1489         std::optional<std::string> presence{};
1490         bool compareVoltageToLimit{true};
1491         Rail rail{name,
1492                   presence,
1493                   page,
1494                   isPowerSupplyRail,
1495                   checkStatusVout,
1496                   compareVoltageToLimit,
1497                   gpio};
1498 
1499         MockDevice device{};
1500         EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
1501         EXPECT_CALL(device, getVoutUVFaultLimit(2))
1502             .Times(1)
1503             .WillOnce(Return(1.1));
1504         EXPECT_CALL(device, getStatusWord(2))
1505             .Times(1)
1506             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
1507 
1508         MockServices services{};
1509         EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
1510             .Times(1);
1511         EXPECT_CALL(
1512             services,
1513             logErrorMsg(
1514                 "Rail VDD2 output voltage 1.1V is <= UV fault limit 1.1V"))
1515             .Times(1);
1516 
1517         std::map<std::string, std::string> additionalData{};
1518         EXPECT_TRUE(
1519             rail.hasPgoodFaultOutputVoltage(device, services, additionalData));
1520         EXPECT_EQ(additionalData.size(), 3);
1521         EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
1522         EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
1523         EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.1");
1524     }
1525 
1526     // Test where exception thrown
1527     {
1528         std::optional<std::string> presence{};
1529         bool compareVoltageToLimit{true};
1530         Rail rail{name,
1531                   presence,
1532                   page,
1533                   isPowerSupplyRail,
1534                   checkStatusVout,
1535                   compareVoltageToLimit,
1536                   gpio};
1537 
1538         MockDevice device{};
1539         EXPECT_CALL(device, getReadVout(2))
1540             .Times(1)
1541             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
1542 
1543         MockServices services{};
1544 
1545         std::map<std::string, std::string> additionalData{};
1546         try
1547         {
1548             rail.hasPgoodFaultOutputVoltage(device, services, additionalData);
1549             ADD_FAILURE() << "Should not have reached this line.";
1550         }
1551         catch (const std::exception& e)
1552         {
1553             EXPECT_STREQ(e.what(),
1554                          "Unable to read READ_VOUT value for rail VDD2: "
1555                          "File does not exist");
1556         }
1557     }
1558 }
1559