xref: /openbmc/phosphor-power/phosphor-power-sequencer/test/pmbus_driver_device_tests.cpp (revision 984be69cd5b40692f8b7267498533a52c02538f8)
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_pmbus.hpp"
18 #include "mock_services.hpp"
19 #include "pmbus.hpp"
20 #include "pmbus_driver_device.hpp"
21 #include "rail.hpp"
22 #include "services.hpp"
23 #include "temporary_subdirectory.hpp"
24 
25 #include <cstdint>
26 #include <exception>
27 #include <filesystem>
28 #include <format>
29 #include <fstream>
30 #include <map>
31 #include <memory>
32 #include <optional>
33 #include <stdexcept>
34 #include <string>
35 #include <utility>
36 #include <vector>
37 
38 #include <gmock/gmock.h>
39 #include <gtest/gtest.h>
40 
41 using namespace phosphor::power::sequencer;
42 using namespace phosphor::pmbus;
43 using namespace phosphor::power::util;
44 namespace fs = std::filesystem;
45 
46 using ::testing::Return;
47 using ::testing::Throw;
48 
49 class PMBusDriverDeviceTests : public ::testing::Test
50 {
51   protected:
52     /**
53      * Constructor.
54      *
55      * Creates a temporary directory to use in simulating sysfs files.
56      */
PMBusDriverDeviceTests()57     PMBusDriverDeviceTests() : ::testing::Test{}
58     {
59         tempDirPath = tempDir.getPath();
60     }
61 
62     /**
63      * Creates a Rail object that checks for a pgood fault using STATUS_VOUT.
64      *
65      * @param name Unique name for the rail
66      * @param pageNum PMBus PAGE number of the rail
67      * @return Rail object
68      */
createRail(const std::string & name,uint8_t pageNum)69     std::unique_ptr<Rail> createRail(const std::string& name, uint8_t pageNum)
70     {
71         std::optional<std::string> presence{};
72         std::optional<uint8_t> page{pageNum};
73         bool isPowerSupplyRail{false};
74         bool checkStatusVout{true};
75         bool compareVoltageToLimit{false};
76         std::optional<PgoodGPIO> gpio{};
77         return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail,
78                                       checkStatusVout, compareVoltageToLimit,
79                                       gpio);
80     }
81 
82     /**
83      * Creates a file with the specified contents within the temporary
84      * directory.
85      *
86      * @param name File name
87      * @param contents File contents
88      */
createFile(const std::string & name,const std::string & contents="")89     void createFile(const std::string& name, const std::string& contents = "")
90     {
91         fs::path path{tempDirPath / name};
92         std::ofstream out{path};
93         out << contents;
94         out.close();
95     }
96 
97     /**
98      * Temporary subdirectory used to create simulated sysfs / hmmon files.
99      */
100     TemporarySubDirectory tempDir;
101 
102     /**
103      * Path to temporary subdirectory.
104      */
105     fs::path tempDirPath;
106 };
107 
TEST_F(PMBusDriverDeviceTests,Constructor)108 TEST_F(PMBusDriverDeviceTests, Constructor)
109 {
110     // Test where works; optional parameters not specified
111     {
112         MockServices services;
113 
114         std::string name{"XYZ_PSEQ"};
115         uint8_t bus{3};
116         uint16_t address{0x72};
117         std::string powerControlGPIOName{"power-chassis-control"};
118         std::string powerGoodGPIOName{"power-chassis-good"};
119         std::vector<std::unique_ptr<Rail>> rails;
120         rails.emplace_back(createRail("VDD", 5));
121         rails.emplace_back(createRail("VIO", 7));
122         PMBusDriverDevice device{
123             name,
124             bus,
125             address,
126             powerControlGPIOName,
127             powerGoodGPIOName,
128             std::move(rails),
129             services};
130 
131         EXPECT_EQ(device.getName(), name);
132         EXPECT_EQ(device.getBus(), bus);
133         EXPECT_EQ(device.getAddress(), address);
134         EXPECT_EQ(device.getPowerControlGPIOName(), powerControlGPIOName);
135         EXPECT_EQ(device.getPowerGoodGPIOName(), powerGoodGPIOName);
136         EXPECT_EQ(device.getRails().size(), 2);
137         EXPECT_EQ(device.getRails()[0]->getName(), "VDD");
138         EXPECT_EQ(device.getRails()[1]->getName(), "VIO");
139         EXPECT_EQ(device.getDriverName(), "");
140         EXPECT_EQ(device.getInstance(), 0);
141         EXPECT_NE(&(device.getPMBusInterface()), nullptr);
142     }
143 
144     // Test where works; optional parameters specified
145     {
146         MockServices services;
147 
148         std::string name{"XYZ_PSEQ"};
149         uint8_t bus{3};
150         uint16_t address{0x72};
151         std::string powerControlGPIOName{"power-on"};
152         std::string powerGoodGPIOName{"pgood"};
153         std::vector<std::unique_ptr<Rail>> rails;
154         rails.emplace_back(createRail("VDD", 5));
155         rails.emplace_back(createRail("VIO", 7));
156         std::string driverName{"xyzdev"};
157         size_t instance{3};
158         PMBusDriverDevice device{
159             name,
160             bus,
161             address,
162             powerControlGPIOName,
163             powerGoodGPIOName,
164             std::move(rails),
165             services,
166             driverName,
167             instance};
168 
169         EXPECT_EQ(device.getName(), name);
170         EXPECT_EQ(device.getBus(), bus);
171         EXPECT_EQ(device.getAddress(), address);
172         EXPECT_EQ(device.getPowerControlGPIOName(), powerControlGPIOName);
173         EXPECT_EQ(device.getPowerGoodGPIOName(), powerGoodGPIOName);
174         EXPECT_EQ(device.getRails().size(), 2);
175         EXPECT_EQ(device.getRails()[0]->getName(), "VDD");
176         EXPECT_EQ(device.getRails()[1]->getName(), "VIO");
177         EXPECT_EQ(device.getDriverName(), driverName);
178         EXPECT_EQ(device.getInstance(), instance);
179         EXPECT_NE(&(device.getPMBusInterface()), nullptr);
180     }
181 }
182 
TEST_F(PMBusDriverDeviceTests,GetDriverName)183 TEST_F(PMBusDriverDeviceTests, GetDriverName)
184 {
185     MockServices services;
186 
187     std::string name{"XYZ_PSEQ"};
188     uint8_t bus{3};
189     uint16_t address{0x72};
190     std::string powerControlGPIOName{"power-chassis-control"};
191     std::string powerGoodGPIOName{"power-chassis-good"};
192     std::vector<std::unique_ptr<Rail>> rails;
193     std::string driverName{"xyzdev"};
194     PMBusDriverDevice device{
195         name,
196         bus,
197         address,
198         powerControlGPIOName,
199         powerGoodGPIOName,
200         std::move(rails),
201         services,
202         driverName};
203 
204     EXPECT_EQ(device.getDriverName(), driverName);
205 }
206 
TEST_F(PMBusDriverDeviceTests,GetInstance)207 TEST_F(PMBusDriverDeviceTests, GetInstance)
208 {
209     MockServices services;
210 
211     std::string name{"XYZ_PSEQ"};
212     uint8_t bus{3};
213     uint16_t address{0x72};
214     std::string powerControlGPIOName{"power-chassis-control"};
215     std::string powerGoodGPIOName{"power-chassis-good"};
216     std::vector<std::unique_ptr<Rail>> rails;
217     std::string driverName{"xyzdev"};
218     size_t instance{3};
219     PMBusDriverDevice device{
220         name,
221         bus,
222         address,
223         powerControlGPIOName,
224         powerGoodGPIOName,
225         std::move(rails),
226         services,
227         driverName,
228         instance};
229 
230     EXPECT_EQ(device.getInstance(), instance);
231 }
232 
TEST_F(PMBusDriverDeviceTests,GetPMBusInterface)233 TEST_F(PMBusDriverDeviceTests, GetPMBusInterface)
234 {
235     MockServices services;
236 
237     std::string name{"XYZ_PSEQ"};
238     uint8_t bus{3};
239     uint16_t address{0x72};
240     std::string powerControlGPIOName{"power-chassis-control"};
241     std::string powerGoodGPIOName{"power-chassis-good"};
242     std::vector<std::unique_ptr<Rail>> rails;
243     PMBusDriverDevice device{
244         name,
245         bus,
246         address,
247         powerControlGPIOName,
248         powerGoodGPIOName,
249         std::move(rails),
250         services};
251 
252     EXPECT_NE(&(device.getPMBusInterface()), nullptr);
253 }
254 
TEST_F(PMBusDriverDeviceTests,GetGPIOValues)255 TEST_F(PMBusDriverDeviceTests, GetGPIOValues)
256 {
257     // Test where works
258     {
259         MockServices services;
260         std::vector<int> gpioValues{1, 1, 1};
261         EXPECT_CALL(services, getGPIOValues("abc_382%#, zy"))
262             .Times(1)
263             .WillOnce(Return(gpioValues));
264 
265         std::string name{"ABC_382%#, ZY"};
266         uint8_t bus{3};
267         uint16_t address{0x72};
268         std::string powerControlGPIOName{"power-chassis-control"};
269         std::string powerGoodGPIOName{"power-chassis-good"};
270         std::vector<std::unique_ptr<Rail>> rails;
271         PMBusDriverDevice device{
272             name,
273             bus,
274             address,
275             powerControlGPIOName,
276             powerGoodGPIOName,
277             std::move(rails),
278             services};
279 
280         EXPECT_TRUE(device.getGPIOValues(services) == gpioValues);
281     }
282 
283     // Test where fails with exception
284     {
285         MockServices services;
286         EXPECT_CALL(services, getGPIOValues("xyz_pseq"))
287             .Times(1)
288             .WillOnce(
289                 Throw(std::runtime_error{"libgpiod: Unable to open chip"}));
290 
291         std::string name{"XYZ_PSEQ"};
292         uint8_t bus{3};
293         uint16_t address{0x72};
294         std::string powerControlGPIOName{"power-chassis-control"};
295         std::string powerGoodGPIOName{"power-chassis-good"};
296         std::vector<std::unique_ptr<Rail>> rails;
297         PMBusDriverDevice device{
298             name,
299             bus,
300             address,
301             powerControlGPIOName,
302             powerGoodGPIOName,
303             std::move(rails),
304             services};
305 
306         try
307         {
308             device.getGPIOValues(services);
309             ADD_FAILURE() << "Should not have reached this line.";
310         }
311         catch (const std::exception& e)
312         {
313             EXPECT_STREQ(e.what(),
314                          "Unable to read GPIO values from device XYZ_PSEQ "
315                          "using label xyz_pseq: "
316                          "libgpiod: Unable to open chip");
317         }
318     }
319 }
320 
TEST_F(PMBusDriverDeviceTests,GetStatusWord)321 TEST_F(PMBusDriverDeviceTests, GetStatusWord)
322 {
323     // Test where works
324     {
325         MockServices services;
326 
327         std::string name{"xyz_pseq"};
328         uint8_t bus{3};
329         uint16_t address{0x72};
330         std::string powerControlGPIOName{"power-chassis-control"};
331         std::string powerGoodGPIOName{"power-chassis-good"};
332         std::vector<std::unique_ptr<Rail>> rails;
333         PMBusDriverDevice device{
334             name,
335             bus,
336             address,
337             powerControlGPIOName,
338             powerGoodGPIOName,
339             std::move(rails),
340             services};
341 
342         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
343         EXPECT_CALL(pmbus, read("status13", Type::Debug, true))
344             .Times(1)
345             .WillOnce(Return(0x1234));
346 
347         uint8_t page{13};
348         EXPECT_EQ(device.getStatusWord(page), 0x1234);
349     }
350 
351     // Test where fails with exception
352     {
353         MockServices services;
354 
355         std::string name{"xyz_pseq"};
356         uint8_t bus{3};
357         uint16_t address{0x72};
358         std::string powerControlGPIOName{"power-chassis-control"};
359         std::string powerGoodGPIOName{"power-chassis-good"};
360         std::vector<std::unique_ptr<Rail>> rails;
361         PMBusDriverDevice device{
362             name,
363             bus,
364             address,
365             powerControlGPIOName,
366             powerGoodGPIOName,
367             std::move(rails),
368             services};
369 
370         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
371         EXPECT_CALL(pmbus, read("status0", Type::Debug, true))
372             .Times(1)
373             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
374 
375         try
376         {
377             uint8_t page{0};
378             device.getStatusWord(page);
379             ADD_FAILURE() << "Should not have reached this line.";
380         }
381         catch (const std::exception& e)
382         {
383             EXPECT_STREQ(
384                 e.what(),
385                 "Unable to read STATUS_WORD for PAGE 0 of device xyz_pseq: "
386                 "File does not exist");
387         }
388     }
389 }
390 
TEST_F(PMBusDriverDeviceTests,GetStatusVout)391 TEST_F(PMBusDriverDeviceTests, GetStatusVout)
392 {
393     // Test where works
394     {
395         MockServices services;
396 
397         std::string name{"xyz_pseq"};
398         uint8_t bus{3};
399         uint16_t address{0x72};
400         std::string powerControlGPIOName{"power-chassis-control"};
401         std::string powerGoodGPIOName{"power-chassis-good"};
402         std::vector<std::unique_ptr<Rail>> rails;
403         PMBusDriverDevice device{
404             name,
405             bus,
406             address,
407             powerControlGPIOName,
408             powerGoodGPIOName,
409             std::move(rails),
410             services};
411 
412         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
413         EXPECT_CALL(pmbus, read("status13_vout", Type::Debug, true))
414             .Times(1)
415             .WillOnce(Return(0xde));
416 
417         uint8_t page{13};
418         EXPECT_EQ(device.getStatusVout(page), 0xde);
419     }
420 
421     // Test where fails with exception
422     {
423         MockServices services;
424 
425         std::string name{"xyz_pseq"};
426         uint8_t bus{3};
427         uint16_t address{0x72};
428         std::string powerControlGPIOName{"power-chassis-control"};
429         std::string powerGoodGPIOName{"power-chassis-good"};
430         std::vector<std::unique_ptr<Rail>> rails;
431         PMBusDriverDevice device{
432             name,
433             bus,
434             address,
435             powerControlGPIOName,
436             powerGoodGPIOName,
437             std::move(rails),
438             services};
439 
440         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
441         EXPECT_CALL(pmbus, read("status0_vout", Type::Debug, true))
442             .Times(1)
443             .WillOnce(Throw(std::runtime_error{"File does not exist"}));
444 
445         try
446         {
447             uint8_t page{0};
448             device.getStatusVout(page);
449             ADD_FAILURE() << "Should not have reached this line.";
450         }
451         catch (const std::exception& e)
452         {
453             EXPECT_STREQ(
454                 e.what(),
455                 "Unable to read STATUS_VOUT for PAGE 0 of device xyz_pseq: "
456                 "File does not exist");
457         }
458     }
459 }
460 
TEST_F(PMBusDriverDeviceTests,GetReadVout)461 TEST_F(PMBusDriverDeviceTests, GetReadVout)
462 {
463     // Test where works
464     {
465         // Create simulated hwmon voltage label file
466         createFile("in13_label"); // PAGE 9 -> file number 13
467 
468         MockServices services;
469 
470         std::string name{"xyz_pseq"};
471         uint8_t bus{3};
472         uint16_t address{0x72};
473         std::string powerControlGPIOName{"power-chassis-control"};
474         std::string powerGoodGPIOName{"power-chassis-good"};
475         std::vector<std::unique_ptr<Rail>> rails;
476         PMBusDriverDevice device{
477             name,
478             bus,
479             address,
480             powerControlGPIOName,
481             powerGoodGPIOName,
482             std::move(rails),
483             services};
484 
485         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
486         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
487             .Times(1)
488             .WillOnce(Return(tempDirPath));
489         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
490             .Times(1)
491             .WillOnce(Return("vout10")); // PAGE number 9 + 1
492         EXPECT_CALL(pmbus, readString("in13_input", Type::Hwmon))
493             .Times(1)
494             .WillOnce(Return("851"));
495 
496         uint8_t page{9};
497         EXPECT_EQ(device.getReadVout(page), 0.851);
498     }
499 
500     // Test where fails
501     {
502         // Create simulated hwmon voltage label file
503         createFile("in13_label"); // PAGE 8 -> file number 13
504 
505         MockServices services;
506 
507         std::string name{"xyz_pseq"};
508         uint8_t bus{3};
509         uint16_t address{0x72};
510         std::string powerControlGPIOName{"power-chassis-control"};
511         std::string powerGoodGPIOName{"power-chassis-good"};
512         std::vector<std::unique_ptr<Rail>> rails;
513         PMBusDriverDevice device{
514             name,
515             bus,
516             address,
517             powerControlGPIOName,
518             powerGoodGPIOName,
519             std::move(rails),
520             services};
521 
522         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
523         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
524             .Times(1)
525             .WillOnce(Return(tempDirPath));
526         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
527             .Times(1)
528             .WillOnce(Return("vout9")); // PAGE number 8 + 1
529 
530         try
531         {
532             uint8_t page{9};
533             device.getReadVout(page);
534             ADD_FAILURE() << "Should not have reached this line.";
535         }
536         catch (const std::exception& e)
537         {
538             EXPECT_STREQ(
539                 e.what(),
540                 "Unable to read READ_VOUT for PAGE 9 of device xyz_pseq: "
541                 "Unable to find hwmon file number for PAGE 9 of device xyz_pseq");
542         }
543     }
544 }
545 
TEST_F(PMBusDriverDeviceTests,GetVoutUVFaultLimit)546 TEST_F(PMBusDriverDeviceTests, GetVoutUVFaultLimit)
547 {
548     // Test where works
549     {
550         // Create simulated hwmon voltage label file
551         createFile("in1_label"); // PAGE 6 -> file number 1
552 
553         MockServices services;
554 
555         std::string name{"xyz_pseq"};
556         uint8_t bus{3};
557         uint16_t address{0x72};
558         std::string powerControlGPIOName{"power-chassis-control"};
559         std::string powerGoodGPIOName{"power-chassis-good"};
560         std::vector<std::unique_ptr<Rail>> rails;
561         PMBusDriverDevice device{
562             name,
563             bus,
564             address,
565             powerControlGPIOName,
566             powerGoodGPIOName,
567             std::move(rails),
568             services};
569 
570         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
571         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
572             .Times(1)
573             .WillOnce(Return(tempDirPath));
574         EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
575             .Times(1)
576             .WillOnce(Return("vout7")); // PAGE number 6 + 1
577         EXPECT_CALL(pmbus, readString("in1_lcrit", Type::Hwmon))
578             .Times(1)
579             .WillOnce(Return("1329"));
580 
581         uint8_t page{6};
582         EXPECT_EQ(device.getVoutUVFaultLimit(page), 1.329);
583     }
584 
585     // Test where fails
586     {
587         // Create simulated hwmon voltage label file
588         createFile("in1_label"); // PAGE 7 -> file number 1
589 
590         MockServices services;
591 
592         std::string name{"xyz_pseq"};
593         uint8_t bus{3};
594         uint16_t address{0x72};
595         std::string powerControlGPIOName{"power-chassis-control"};
596         std::string powerGoodGPIOName{"power-chassis-good"};
597         std::vector<std::unique_ptr<Rail>> rails;
598         PMBusDriverDevice device{
599             name,
600             bus,
601             address,
602             powerControlGPIOName,
603             powerGoodGPIOName,
604             std::move(rails),
605             services};
606 
607         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
608         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
609             .Times(1)
610             .WillOnce(Return(tempDirPath));
611         EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
612             .Times(1)
613             .WillOnce(Return("vout8")); // PAGE number 7 + 1
614 
615         try
616         {
617             uint8_t page{6};
618             device.getVoutUVFaultLimit(page);
619             ADD_FAILURE() << "Should not have reached this line.";
620         }
621         catch (const std::exception& e)
622         {
623             EXPECT_STREQ(
624                 e.what(),
625                 "Unable to read VOUT_UV_FAULT_LIMIT for PAGE 6 of device xyz_pseq: "
626                 "Unable to find hwmon file number for PAGE 6 of device xyz_pseq");
627         }
628     }
629 }
630 
TEST_F(PMBusDriverDeviceTests,GetPageToFileNumberMap)631 TEST_F(PMBusDriverDeviceTests, GetPageToFileNumberMap)
632 {
633     // Test where works: No voltage label files/mappings found
634     {
635         // Create simulated hwmon files.  None are valid voltage label files.
636         createFile("in1_input");   // Not a label file
637         createFile("in9_lcrit");   // Not a label file
638         createFile("in_label");    // Invalid voltage label file name
639         createFile("in9a_label");  // Invalid voltage label file name
640         createFile("fan3_label");  // Not a voltage label file
641         createFile("temp8_label"); // Not a voltage label file
642 
643         MockServices services;
644 
645         std::string name{"xyz_pseq"};
646         uint8_t bus{3};
647         uint16_t address{0x72};
648         std::string powerControlGPIOName{"power-chassis-control"};
649         std::string powerGoodGPIOName{"power-chassis-good"};
650         std::vector<std::unique_ptr<Rail>> rails;
651         PMBusDriverDevice device{
652             name,
653             bus,
654             address,
655             powerControlGPIOName,
656             powerGoodGPIOName,
657             std::move(rails),
658             services};
659 
660         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
661         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
662             .Times(1)
663             .WillOnce(Return(tempDirPath));
664         EXPECT_CALL(pmbus, readString).Times(0);
665 
666         const std::map<uint8_t, unsigned int>& map =
667             device.getPageToFileNumberMap();
668         EXPECT_TRUE(map.empty());
669     }
670 
671     // Test where works: Multiple voltage label files/mappings found
672     {
673         // Create simulated hwmon files
674         createFile("in9_label");   // PAGE 3 -> file number 9
675         createFile("in13_label");  // PAGE 7 -> file number 13
676         createFile("in0_label");   // PAGE 12 -> file number 0
677         createFile("in11_label");  // No mapping; invalid contents
678         createFile("in12_label");  // No mapping; invalid contents
679         createFile("in1_input");   // Not a label file
680         createFile("in7_lcrit");   // Not a label file
681         createFile("fan3_label");  // Not a voltage label file
682         createFile("temp8_label"); // Not a voltage label file
683 
684         MockServices services;
685 
686         std::string name{"xyz_pseq"};
687         uint8_t bus{3};
688         uint16_t address{0x72};
689         std::string powerControlGPIOName{"power-chassis-control"};
690         std::string powerGoodGPIOName{"power-chassis-good"};
691         std::vector<std::unique_ptr<Rail>> rails;
692         PMBusDriverDevice device{
693             name,
694             bus,
695             address,
696             powerControlGPIOName,
697             powerGoodGPIOName,
698             std::move(rails),
699             services};
700 
701         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
702         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
703             .Times(1)
704             .WillOnce(Return(tempDirPath));
705         EXPECT_CALL(pmbus, readString("in9_label", Type::Hwmon))
706             .Times(1)
707             .WillOnce(Return("vout4")); // PAGE number 3 + 1
708         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
709             .Times(1)
710             .WillOnce(Return("vout8")); // PAGE number 7 + 1
711         EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
712             .Times(1)
713             .WillOnce(Return("vout13")); // PAGE number 12 + 1
714         EXPECT_CALL(pmbus, readString("in11_label", Type::Hwmon))
715             .Times(1)
716             .WillOnce(Return("vout")); // Invalid format
717         EXPECT_CALL(pmbus, readString("in12_label", Type::Hwmon))
718             .Times(1)
719             .WillOnce(Return("vout13a")); // Invalid format
720 
721         const std::map<uint8_t, unsigned int>& map =
722             device.getPageToFileNumberMap();
723         EXPECT_EQ(map.size(), 3);
724         EXPECT_EQ(map.at(uint8_t{3}), 9);
725         EXPECT_EQ(map.at(uint8_t{7}), 13);
726         EXPECT_EQ(map.at(uint8_t{12}), 0);
727     }
728 
729     // Test where fails: hwmon directory path is actually a file
730     {
731         // Create file that will be returned as the hwmon directory path
732         createFile("in9_label");
733 
734         MockServices services;
735 
736         std::string name{"xyz_pseq"};
737         uint8_t bus{3};
738         uint16_t address{0x72};
739         std::string powerControlGPIOName{"power-chassis-control"};
740         std::string powerGoodGPIOName{"power-chassis-good"};
741         std::vector<std::unique_ptr<Rail>> rails;
742         PMBusDriverDevice device{
743             name,
744             bus,
745             address,
746             powerControlGPIOName,
747             powerGoodGPIOName,
748             std::move(rails),
749             services};
750 
751         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
752         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
753             .Times(1)
754             .WillOnce(Return(tempDirPath / "in9_label"));
755         EXPECT_CALL(pmbus, readString).Times(0);
756 
757         const std::map<uint8_t, unsigned int>& map =
758             device.getPageToFileNumberMap();
759         EXPECT_TRUE(map.empty());
760     }
761 
762     // Test where fails: hwmon directory path does not exist
763     {
764         MockServices services;
765 
766         std::string name{"xyz_pseq"};
767         uint8_t bus{3};
768         uint16_t address{0x72};
769         std::string powerControlGPIOName{"power-chassis-control"};
770         std::string powerGoodGPIOName{"power-chassis-good"};
771         std::vector<std::unique_ptr<Rail>> rails;
772         PMBusDriverDevice device{
773             name,
774             bus,
775             address,
776             powerControlGPIOName,
777             powerGoodGPIOName,
778             std::move(rails),
779             services};
780 
781         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
782         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
783             .Times(1)
784             .WillOnce(Return(tempDirPath / "does_not_exist"));
785         EXPECT_CALL(pmbus, readString).Times(0);
786 
787         const std::map<uint8_t, unsigned int>& map =
788             device.getPageToFileNumberMap();
789         EXPECT_TRUE(map.empty());
790     }
791 
792     // Test where fails: hwmon directory path is not readable
793     {
794         // Create simulated hwmon files
795         createFile("in9_label");
796         createFile("in13_label");
797         createFile("in0_label");
798 
799         // Change temporary directory to be unreadable
800         fs::permissions(tempDirPath, fs::perms::none);
801 
802         MockServices services;
803 
804         std::string name{"xyz_pseq"};
805         uint8_t bus{3};
806         uint16_t address{0x72};
807         std::string powerControlGPIOName{"power-chassis-control"};
808         std::string powerGoodGPIOName{"power-chassis-good"};
809         std::vector<std::unique_ptr<Rail>> rails;
810         PMBusDriverDevice device{
811             name,
812             bus,
813             address,
814             powerControlGPIOName,
815             powerGoodGPIOName,
816             std::move(rails),
817             services};
818 
819         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
820         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
821             .Times(1)
822             .WillOnce(Return(tempDirPath));
823         EXPECT_CALL(pmbus, readString).Times(0);
824 
825         try
826         {
827             device.getPageToFileNumberMap();
828             ADD_FAILURE() << "Should not have reached this line.";
829         }
830         catch (const std::exception& e)
831         {
832             // Error message varies
833         }
834 
835         // Change temporary directory to be readable/writable
836         fs::permissions(tempDirPath, fs::perms::owner_all);
837     }
838 }
839 
TEST_F(PMBusDriverDeviceTests,GetFileNumber)840 TEST_F(PMBusDriverDeviceTests, GetFileNumber)
841 {
842     // Test where works
843     {
844         // Create simulated hwmon voltage label files
845         createFile("in0_label");  // PAGE 6 -> file number 0
846         createFile("in13_label"); // PAGE 9 -> file number 13
847 
848         MockServices services;
849 
850         std::string name{"xyz_pseq"};
851         uint8_t bus{3};
852         uint16_t address{0x72};
853         std::string powerControlGPIOName{"power-chassis-control"};
854         std::string powerGoodGPIOName{"power-chassis-good"};
855         std::vector<std::unique_ptr<Rail>> rails;
856         PMBusDriverDevice device{
857             name,
858             bus,
859             address,
860             powerControlGPIOName,
861             powerGoodGPIOName,
862             std::move(rails),
863             services};
864 
865         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
866         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
867             .Times(1)
868             .WillOnce(Return(tempDirPath));
869         EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
870             .Times(1)
871             .WillOnce(Return("vout7")); // PAGE number 6 + 1
872         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
873             .Times(1)
874             .WillOnce(Return("vout10")); // PAGE number 9 + 1
875 
876         // Map was empty and needs to be built
877         uint8_t page{6};
878         EXPECT_EQ(device.getFileNumber(page), 0);
879 
880         // Map had already been built
881         page = 9;
882         EXPECT_EQ(device.getFileNumber(page), 13);
883     }
884 
885     // Test where fails: No mapping for specified PMBus PAGE
886     {
887         // Create simulated hwmon voltage label files
888         createFile("in0_label");  // PAGE 6 -> file number 0
889         createFile("in13_label"); // PAGE 9 -> file number 13
890 
891         MockServices services;
892 
893         std::string name{"xyz_pseq"};
894         uint8_t bus{3};
895         uint16_t address{0x72};
896         std::string powerControlGPIOName{"power-chassis-control"};
897         std::string powerGoodGPIOName{"power-chassis-good"};
898         std::vector<std::unique_ptr<Rail>> rails;
899         PMBusDriverDevice device{
900             name,
901             bus,
902             address,
903             powerControlGPIOName,
904             powerGoodGPIOName,
905             std::move(rails),
906             services};
907 
908         MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
909         EXPECT_CALL(pmbus, getPath(Type::Hwmon))
910             .Times(1)
911             .WillOnce(Return(tempDirPath));
912         EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
913             .Times(1)
914             .WillOnce(Return("vout7")); // PAGE number 6 + 1
915         EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
916             .Times(1)
917             .WillOnce(Return("vout10")); // PAGE number 9 + 1
918 
919         try
920         {
921             uint8_t page{13};
922             device.getFileNumber(page);
923             ADD_FAILURE() << "Should not have reached this line.";
924         }
925         catch (const std::exception& e)
926         {
927             EXPECT_STREQ(
928                 e.what(),
929                 "Unable to find hwmon file number for PAGE 13 of device xyz_pseq");
930         }
931     }
932 }
933 
TEST_F(PMBusDriverDeviceTests,PrepareForPgoodFaultDetection)934 TEST_F(PMBusDriverDeviceTests, PrepareForPgoodFaultDetection)
935 {
936     // This is a protected method and cannot be called directly from a gtest.
937     // Call findPgoodFault() which calls prepareForPgoodFaultDetection().
938 
939     // Create simulated hwmon voltage label file
940     createFile("in1_label"); // PAGE 6 -> file number 1
941 
942     MockServices services;
943     std::vector<int> gpioValues{1, 1, 1};
944     EXPECT_CALL(services, getGPIOValues("xyz_pseq"))
945         .Times(1)
946         .WillOnce(Return(gpioValues));
947 
948     std::string name{"xyz_pseq"};
949     uint8_t bus{3};
950     uint16_t address{0x72};
951     std::string powerControlGPIOName{"power-chassis-control"};
952     std::string powerGoodGPIOName{"power-chassis-good"};
953     std::vector<std::unique_ptr<Rail>> rails;
954     PMBusDriverDevice device{
955         name,
956         bus,
957         address,
958         powerControlGPIOName,
959         powerGoodGPIOName,
960         std::move(rails),
961         services};
962 
963     // Methods that get hwmon file info should be called twice
964     MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
965     EXPECT_CALL(pmbus, getPath(Type::Hwmon))
966         .Times(2)
967         .WillRepeatedly(Return(tempDirPath));
968     EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
969         .Times(2)
970         .WillRepeatedly(Return("vout7")); // PAGE number 6 + 1
971 
972     // Map was empty and needs to be built
973     uint8_t page{6};
974     EXPECT_EQ(device.getFileNumber(page), 1);
975 
976     // Call findPgoodFault() which calls prepareForPgoodFaultDetection() which
977     // rebuilds the map.
978     std::string powerSupplyError{};
979     std::map<std::string, std::string> additionalData{};
980     std::string error =
981         device.findPgoodFault(services, powerSupplyError, additionalData);
982     EXPECT_TRUE(error.empty());
983     EXPECT_EQ(additionalData.size(), 0);
984 
985     EXPECT_EQ(device.getFileNumber(page), 1);
986 }
987