xref: /openbmc/phosphor-power/phosphor-power-sequencer/test/config_file_parser_tests.cpp (revision 7b7a5632c594d60f4620ca14379a766a56faf846)
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 #include "chassis.hpp"
17 #include "config_file_parser.hpp"
18 #include "config_file_parser_error.hpp"
19 #include "mock_services.hpp"
20 #include "power_sequencer_device.hpp"
21 #include "rail.hpp"
22 #include "temporary_file.hpp"
23 #include "temporary_subdirectory.hpp"
24 
25 #include <sys/stat.h> // for chmod()
26 
27 #include <nlohmann/json.hpp>
28 
29 #include <cstdint>
30 #include <exception>
31 #include <filesystem>
32 #include <fstream>
33 #include <map>
34 #include <memory>
35 #include <optional>
36 #include <stdexcept>
37 #include <string>
38 #include <tuple>
39 #include <vector>
40 
41 #include <gtest/gtest.h>
42 
43 using namespace phosphor::power::sequencer;
44 using namespace phosphor::power::sequencer::config_file_parser;
45 using namespace phosphor::power::sequencer::config_file_parser::internal;
46 using namespace phosphor::power::util;
47 using json = nlohmann::json;
48 namespace fs = std::filesystem;
49 
writeConfigFile(const fs::path & pathName,const std::string & contents)50 void writeConfigFile(const fs::path& pathName, const std::string& contents)
51 {
52     std::ofstream file{pathName};
53     file << contents;
54 }
55 
writeConfigFile(const fs::path & pathName,const json & contents)56 void writeConfigFile(const fs::path& pathName, const json& contents)
57 {
58     std::ofstream file{pathName};
59     file << contents;
60 }
61 
TEST(ConfigFileParserTests,Find)62 TEST(ConfigFileParserTests, Find)
63 {
64     std::vector<std::string> compatibleSystemTypes{
65         "com.acme.Hardware.Chassis.Model.MegaServer4CPU",
66         "com.acme.Hardware.Chassis.Model.MegaServer",
67         "com.acme.Hardware.Chassis.Model.Server"};
68 
69     // Test where works: Fully qualified system type: First in list
70     {
71         TemporarySubDirectory configFileDir;
72         fs::path configFileDirPath = configFileDir.getPath();
73 
74         fs::path configFilePath = configFileDirPath;
75         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
76         writeConfigFile(configFilePath, std::string{""});
77 
78         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
79         EXPECT_EQ(pathFound, configFilePath);
80     }
81 
82     // Test where works: Fully qualified system type: Second in list
83     {
84         TemporarySubDirectory configFileDir;
85         fs::path configFileDirPath = configFileDir.getPath();
86 
87         fs::path configFilePath = configFileDirPath;
88         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer.json";
89         writeConfigFile(configFilePath, std::string{""});
90 
91         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
92         EXPECT_EQ(pathFound, configFilePath);
93     }
94 
95     // Test where works: Last node in system type: Second in list
96     {
97         TemporarySubDirectory configFileDir;
98         fs::path configFileDirPath = configFileDir.getPath();
99 
100         fs::path configFilePath = configFileDirPath;
101         configFilePath /= "MegaServer.json";
102         writeConfigFile(configFilePath, std::string{""});
103 
104         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
105         EXPECT_EQ(pathFound, configFilePath);
106     }
107 
108     // Test where works: Last node in system type: Last in list
109     {
110         TemporarySubDirectory configFileDir;
111         fs::path configFileDirPath = configFileDir.getPath();
112 
113         fs::path configFilePath = configFileDirPath;
114         configFilePath /= "Server.json";
115         writeConfigFile(configFilePath, std::string{""});
116 
117         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
118         EXPECT_EQ(pathFound, configFilePath);
119     }
120 
121     // Test where works: System type has no '.'
122     {
123         TemporarySubDirectory configFileDir;
124         fs::path configFileDirPath = configFileDir.getPath();
125 
126         fs::path configFilePath = configFileDirPath;
127         configFilePath /= "Server.json";
128         writeConfigFile(configFilePath, std::string{""});
129 
130         std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
131                                                   "MegaServer", "Server"};
132         fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
133         EXPECT_EQ(pathFound, configFilePath);
134     }
135 
136     // Test where fails: System type list is empty
137     {
138         TemporarySubDirectory configFileDir;
139         fs::path configFileDirPath = configFileDir.getPath();
140 
141         fs::path configFilePath = configFileDirPath;
142         configFilePath /= "Server.json";
143         writeConfigFile(configFilePath, std::string{""});
144 
145         std::vector<std::string> emptySystemTypes{};
146         fs::path pathFound = find(emptySystemTypes, configFileDirPath);
147         EXPECT_TRUE(pathFound.empty());
148     }
149 
150     // Test where fails: Configuration file directory is empty
151     {
152         TemporarySubDirectory configFileDir;
153         fs::path configFileDirPath = configFileDir.getPath();
154 
155         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
156         EXPECT_TRUE(pathFound.empty());
157     }
158 
159     // Test where fails: Configuration file directory does not exist
160     {
161         fs::path configFileDirPath{"/tmp/does_not_exist_XYZ"};
162 
163         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
164         EXPECT_TRUE(pathFound.empty());
165     }
166 
167     // Test where fails: Configuration file directory is not readable
168     {
169         TemporarySubDirectory configFileDir;
170         fs::path configFileDirPath = configFileDir.getPath();
171         fs::permissions(configFileDirPath, fs::perms::none);
172 
173         EXPECT_THROW(find(compatibleSystemTypes, configFileDirPath),
174                      std::exception);
175 
176         fs::permissions(configFileDirPath, fs::perms::owner_all);
177     }
178 
179     // Test where fails: No matching file name found
180     {
181         TemporarySubDirectory configFileDir;
182         fs::path configFileDirPath = configFileDir.getPath();
183 
184         fs::path configFilePath = configFileDirPath;
185         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer";
186         writeConfigFile(configFilePath, std::string{""});
187 
188         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
189         EXPECT_TRUE(pathFound.empty());
190     }
191 
192     // Test where fails: Matching file name is a directory: Fully qualified
193     {
194         TemporarySubDirectory configFileDir;
195         fs::path configFileDirPath = configFileDir.getPath();
196 
197         fs::path configFilePath = configFileDirPath;
198         configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
199         fs::create_directory(configFilePath);
200 
201         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
202         EXPECT_TRUE(pathFound.empty());
203     }
204 
205     // Test where fails: Matching file name is a directory: Last node
206     {
207         TemporarySubDirectory configFileDir;
208         fs::path configFileDirPath = configFileDir.getPath();
209 
210         fs::path configFilePath = configFileDirPath;
211         configFilePath /= "MegaServer.json";
212         fs::create_directory(configFilePath);
213 
214         fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
215         EXPECT_TRUE(pathFound.empty());
216     }
217 
218     // Test where fails: System type has no '.'
219     {
220         TemporarySubDirectory configFileDir;
221         fs::path configFileDirPath = configFileDir.getPath();
222 
223         fs::path configFilePath = configFileDirPath;
224         configFilePath /= "MegaServer2CPU.json";
225         writeConfigFile(configFilePath, std::string{""});
226 
227         std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
228                                                   "MegaServer", "Server", ""};
229         fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
230         EXPECT_TRUE(pathFound.empty());
231     }
232 
233     // Test where fails: System type ends with '.'
234     {
235         TemporarySubDirectory configFileDir;
236         fs::path configFileDirPath = configFileDir.getPath();
237 
238         fs::path configFilePath = configFileDirPath;
239         configFilePath /= "MegaServer4CPU.json";
240         writeConfigFile(configFilePath, std::string{""});
241 
242         std::vector<std::string> dotAtEndSystemTypes{
243             "com.acme.Hardware.Chassis.Model.MegaServer4CPU.", "a.", "."};
244         fs::path pathFound = find(dotAtEndSystemTypes, configFileDirPath);
245         EXPECT_TRUE(pathFound.empty());
246     }
247 }
248 
TEST(ConfigFileParserTests,Parse)249 TEST(ConfigFileParserTests, Parse)
250 {
251     // Test where works
252     {
253         const json configFileContents = R"(
254             {
255               "chassis": [
256                 {
257                   "number": 1,
258                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
259                   "power_sequencers": []
260                 },
261                 {
262                   "number": 2,
263                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
264                   "power_sequencers": []
265                 }
266               ]
267             }
268         )"_json;
269 
270         TemporaryFile configFile;
271         fs::path pathName{configFile.getPath()};
272         writeConfigFile(pathName, configFileContents);
273 
274         MockServices services{};
275         auto chassis = parse(pathName, services);
276 
277         EXPECT_EQ(chassis.size(), 2);
278         EXPECT_EQ(chassis[0]->getNumber(), 1);
279         EXPECT_EQ(chassis[0]->getInventoryPath(),
280                   "/xyz/openbmc_project/inventory/system/chassis1");
281         EXPECT_EQ(chassis[1]->getNumber(), 2);
282         EXPECT_EQ(chassis[1]->getInventoryPath(),
283                   "/xyz/openbmc_project/inventory/system/chassis2");
284     }
285 
286     // Test where fails: File does not exist
287     {
288         fs::path pathName{"/tmp/non_existent_file"};
289         MockServices services{};
290         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
291     }
292 
293     // Test where fails: File is not readable
294     {
295         const json configFileContents = R"(
296             {
297               "chassis": [
298                 {
299                   "number": 1,
300                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
301                   "power_sequencers": []
302                 }
303               ]
304             }
305         )"_json;
306 
307         TemporaryFile configFile;
308         fs::path pathName{configFile.getPath()};
309         writeConfigFile(pathName, configFileContents);
310 
311         chmod(pathName.c_str(), 0222);
312         MockServices services{};
313         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
314     }
315 
316     // Test where fails: File is not valid JSON
317     {
318         const std::string configFileContents = "] foo [";
319 
320         TemporaryFile configFile;
321         fs::path pathName{configFile.getPath()};
322         writeConfigFile(pathName, configFileContents);
323 
324         MockServices services{};
325         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
326     }
327 
328     // Test where fails: JSON does not conform to config file format
329     {
330         const json configFileContents = R"( [ "foo", "bar" ] )"_json;
331 
332         TemporaryFile configFile;
333         fs::path pathName{configFile.getPath()};
334         writeConfigFile(pathName, configFileContents);
335 
336         MockServices services{};
337         EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
338     }
339 }
340 
TEST(ConfigFileParserTests,ParseChassis)341 TEST(ConfigFileParserTests, ParseChassis)
342 {
343     // Constants used by multiple tests
344     const json templateElement = R"(
345         {
346           "id": "foo_chassis",
347           "number": "${chassis_number}",
348           "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
349           "power_sequencers": [
350             {
351               "type": "UCD90320",
352               "i2c_interface": { "bus": "${bus}", "address": "${address}" },
353               "power_control_gpio_name": "power-chassis${chassis_number}-control",
354               "power_good_gpio_name": "power-chassis${chassis_number}-good",
355               "rails": []
356             }
357           ]
358         }
359     )"_json;
360     const std::map<std::string, JSONRefWrapper> chassisTemplates{
361         {"foo_chassis", templateElement}};
362 
363     // Test where works: Does not use a template
364     {
365         const json element = R"(
366             {
367               "number": 1,
368               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
369               "power_sequencers": [
370                 {
371                   "type": "UCD90320",
372                   "i2c_interface": { "bus": 3, "address": "0x11" },
373                   "power_control_gpio_name": "power-chassis-control",
374                   "power_good_gpio_name": "power-chassis-good",
375                   "rails": []
376                 }
377               ]
378             }
379         )"_json;
380         MockServices services{};
381         auto chassis = parseChassis(element, chassisTemplates, services);
382         EXPECT_EQ(chassis->getNumber(), 1);
383         EXPECT_EQ(chassis->getInventoryPath(),
384                   "/xyz/openbmc_project/inventory/system/chassis");
385         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
386         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
387         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 3);
388         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x11);
389     }
390 
391     // Test where works: Uses template: No comments specified
392     {
393         const json element = R"(
394             {
395               "template_id": "foo_chassis",
396               "template_variable_values": {
397                 "chassis_number": "2",
398                 "bus": "13",
399                 "address": "0x70"
400               }
401             }
402         )"_json;
403         MockServices services{};
404         auto chassis = parseChassis(element, chassisTemplates, services);
405         EXPECT_EQ(chassis->getNumber(), 2);
406         EXPECT_EQ(chassis->getInventoryPath(),
407                   "/xyz/openbmc_project/inventory/system/chassis2");
408         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
409         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
410         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 13);
411         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x70);
412     }
413 
414     // Test where works: Uses template: Comments specified
415     {
416         const json element = R"(
417             {
418               "comments": ["Chassis 3: Standard hardware layout"],
419               "template_id": "foo_chassis",
420               "template_variable_values": {
421                 "chassis_number": "3",
422                 "bus": "23",
423                 "address": "0x54"
424               }
425             }
426         )"_json;
427         MockServices services{};
428         auto chassis = parseChassis(element, chassisTemplates, services);
429         EXPECT_EQ(chassis->getNumber(), 3);
430         EXPECT_EQ(chassis->getInventoryPath(),
431                   "/xyz/openbmc_project/inventory/system/chassis3");
432         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
433         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
434         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 23);
435         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x54);
436     }
437 
438     // Test where fails: Element is not an object
439     try
440     {
441         const json element = R"( [ "vdda", "vddb" ] )"_json;
442         MockServices services{};
443         parseChassis(element, chassisTemplates, services);
444         ADD_FAILURE() << "Should not have reached this line.";
445     }
446     catch (const std::invalid_argument& e)
447     {
448         EXPECT_STREQ(e.what(), "Element is not an object");
449     }
450 
451     // Test where fails: Does not use a template: Cannot parse properties
452     try
453     {
454         const json element = R"(
455             {
456               "number": "one",
457               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
458               "power_sequencers": []
459             }
460         )"_json;
461         MockServices services{};
462         parseChassis(element, chassisTemplates, services);
463         ADD_FAILURE() << "Should not have reached this line.";
464     }
465     catch (const std::invalid_argument& e)
466     {
467         EXPECT_STREQ(e.what(), "Element is not an integer");
468     }
469 
470     // Test where fails: Uses template: Required template_variable_values
471     // property not specified
472     try
473     {
474         const json element = R"(
475             {
476               "template_id": "foo_chassis"
477             }
478         )"_json;
479         MockServices services{};
480         parseChassis(element, chassisTemplates, services);
481         ADD_FAILURE() << "Should not have reached this line.";
482     }
483     catch (const std::invalid_argument& e)
484     {
485         EXPECT_STREQ(e.what(),
486                      "Required property missing: template_variable_values");
487     }
488 
489     // Test where fails: Uses template: template_id value is invalid: Not a
490     // string
491     try
492     {
493         const json element = R"(
494             {
495               "template_id": 23,
496               "template_variable_values": { "chassis_number": "2" }
497             }
498         )"_json;
499         MockServices services{};
500         parseChassis(element, chassisTemplates, services);
501         ADD_FAILURE() << "Should not have reached this line.";
502     }
503     catch (const std::invalid_argument& e)
504     {
505         EXPECT_STREQ(e.what(), "Element is not a string");
506     }
507 
508     // Test where fails: Uses template: template_id value is invalid: No
509     // matching template
510     try
511     {
512         const json element = R"(
513             {
514               "template_id": "does_not_exist",
515               "template_variable_values": { "chassis_number": "2" }
516             }
517         )"_json;
518         MockServices services{};
519         parseChassis(element, chassisTemplates, services);
520         ADD_FAILURE() << "Should not have reached this line.";
521     }
522     catch (const std::invalid_argument& e)
523     {
524         EXPECT_STREQ(e.what(), "Invalid chassis template id: does_not_exist");
525     }
526 
527     // Test where fails: Uses template: template_variable_values value is
528     // invalid
529     try
530     {
531         const json element = R"(
532             {
533               "template_id": "foo_chassis",
534               "template_variable_values": { "chassis_number": 2 }
535             }
536         )"_json;
537         MockServices services{};
538         parseChassis(element, chassisTemplates, services);
539         ADD_FAILURE() << "Should not have reached this line.";
540     }
541     catch (const std::invalid_argument& e)
542     {
543         EXPECT_STREQ(e.what(), "Element is not a string");
544     }
545 
546     // Test where fails: Uses template: Invalid property specified
547     try
548     {
549         const json element = R"(
550             {
551               "template_id": "foo_chassis",
552               "template_variable_values": { "chassis_number": "2" },
553               "foo": "bar"
554             }
555         )"_json;
556         MockServices services{};
557         parseChassis(element, chassisTemplates, services);
558         ADD_FAILURE() << "Should not have reached this line.";
559     }
560     catch (const std::invalid_argument& e)
561     {
562         EXPECT_STREQ(e.what(), "Element contains an invalid property");
563     }
564 
565     // Test where fails: Uses template: Cannot parse properties in template
566     try
567     {
568         const json element = R"(
569             {
570               "template_id": "foo_chassis",
571               "template_variable_values": { "chassis_number": "0" }
572             }
573         )"_json;
574         MockServices services{};
575         parseChassis(element, chassisTemplates, services);
576         ADD_FAILURE() << "Should not have reached this line.";
577     }
578     catch (const std::invalid_argument& e)
579     {
580         EXPECT_STREQ(e.what(), "Invalid chassis number: Must be > 0");
581     }
582 }
583 
TEST(ConfigFileParserTests,ParseChassisArray)584 TEST(ConfigFileParserTests, ParseChassisArray)
585 {
586     // Constants used by multiple tests
587     const json fooTemplateElement = R"(
588         {
589           "id": "foo_chassis",
590           "number": "${chassis_number}",
591           "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
592           "power_sequencers": []
593         }
594     )"_json;
595     const json barTemplateElement = R"(
596         {
597           "id": "bar_chassis",
598           "number": "${chassis_number}",
599           "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${chassis_number}",
600           "power_sequencers": []
601         }
602     )"_json;
603     const std::map<std::string, JSONRefWrapper> chassisTemplates{
604         {"foo_chassis", fooTemplateElement},
605         {"bar_chassis", barTemplateElement}};
606 
607     // Test where works: Array is empty
608     {
609         const json element = R"(
610             [
611             ]
612         )"_json;
613         MockServices services{};
614         auto chassis = parseChassisArray(element, chassisTemplates, services);
615         EXPECT_EQ(chassis.size(), 0);
616     }
617 
618     // Test where works: Template not used
619     {
620         const json element = R"(
621             [
622               {
623                 "number": 1,
624                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
625                 "power_sequencers": []
626               },
627               {
628                 "number": 2,
629                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
630                 "power_sequencers": []
631               }
632             ]
633         )"_json;
634         MockServices services{};
635         auto chassis = parseChassisArray(element, chassisTemplates, services);
636         EXPECT_EQ(chassis.size(), 2);
637         EXPECT_EQ(chassis[0]->getNumber(), 1);
638         EXPECT_EQ(chassis[0]->getInventoryPath(),
639                   "/xyz/openbmc_project/inventory/system/chassis1");
640         EXPECT_EQ(chassis[1]->getNumber(), 2);
641         EXPECT_EQ(chassis[1]->getInventoryPath(),
642                   "/xyz/openbmc_project/inventory/system/chassis2");
643     }
644 
645     // Test where works: Template used
646     {
647         const json element = R"(
648             [
649               {
650                 "template_id": "foo_chassis",
651                 "template_variable_values": { "chassis_number": "2" }
652               },
653               {
654                 "template_id": "bar_chassis",
655                 "template_variable_values": { "chassis_number": "3" }
656               }
657             ]
658         )"_json;
659         MockServices services{};
660         auto chassis = parseChassisArray(element, chassisTemplates, services);
661         EXPECT_EQ(chassis.size(), 2);
662         EXPECT_EQ(chassis[0]->getNumber(), 2);
663         EXPECT_EQ(chassis[0]->getInventoryPath(),
664                   "/xyz/openbmc_project/inventory/system/chassis2");
665         EXPECT_EQ(chassis[1]->getNumber(), 3);
666         EXPECT_EQ(chassis[1]->getInventoryPath(),
667                   "/xyz/openbmc_project/inventory/system/bar_chassis3");
668     }
669 
670     // Test where fails: Element is not an array
671     try
672     {
673         const json element = R"(
674             {
675                 "foo": "bar"
676             }
677         )"_json;
678         MockServices services{};
679         parseChassisArray(element, chassisTemplates, services);
680         ADD_FAILURE() << "Should not have reached this line.";
681     }
682     catch (const std::invalid_argument& e)
683     {
684         EXPECT_STREQ(e.what(), "Element is not an array");
685     }
686 
687     // Test where fails: Element within array is invalid
688     try
689     {
690         const json element = R"(
691             [
692               {
693                 "number": true,
694                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
695                 "power_sequencers": []
696               }
697             ]
698         )"_json;
699         MockServices services{};
700         parseChassisArray(element, chassisTemplates, services);
701         ADD_FAILURE() << "Should not have reached this line.";
702     }
703     catch (const std::invalid_argument& e)
704     {
705         EXPECT_STREQ(e.what(), "Element is not an integer");
706     }
707 
708     // Test where fails: Invalid variable value specified
709     try
710     {
711         const json element = R"(
712             [
713                 {
714                   "template_id": "foo_chassis",
715                   "template_variable_values": { "chassis_number": "two" }
716                 }
717             ]
718         )"_json;
719         MockServices services{};
720         parseChassisArray(element, chassisTemplates, services);
721         ADD_FAILURE() << "Should not have reached this line.";
722     }
723     catch (const std::invalid_argument& e)
724     {
725         EXPECT_STREQ(e.what(), "Element is not an integer");
726     }
727 }
728 
TEST(ConfigFileParserTests,ParseChassisProperties)729 TEST(ConfigFileParserTests, ParseChassisProperties)
730 {
731     // Test where works: Parse chassis object without template/variables: Has
732     // comments property
733     {
734         const json element = R"(
735             {
736               "comments": [ "Chassis 1: Has all CPUs, fans, and PSUs" ],
737               "number": 1,
738               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
739               "power_sequencers": [
740                 {
741                   "type": "UCD90160",
742                   "i2c_interface": { "bus": 3, "address": "0x11" },
743                   "power_control_gpio_name": "power-chassis-control",
744                   "power_good_gpio_name": "power-chassis-good",
745                   "rails": [ { "name": "VDD_CPU0" }, { "name": "VCS_CPU1" } ]
746                 }
747               ]
748             }
749         )"_json;
750         bool isChassisTemplate{false};
751         std::map<std::string, std::string> variables{};
752         MockServices services{};
753         auto chassis = parseChassisProperties(element, isChassisTemplate,
754                                               variables, services);
755         EXPECT_EQ(chassis->getNumber(), 1);
756         EXPECT_EQ(chassis->getInventoryPath(),
757                   "/xyz/openbmc_project/inventory/system/chassis");
758         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
759         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90160");
760         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 3);
761         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x11);
762         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerControlGPIOName(),
763                   "power-chassis-control");
764         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerGoodGPIOName(),
765                   "power-chassis-good");
766         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails().size(), 2);
767         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails()[0]->getName(),
768                   "VDD_CPU0");
769         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails()[1]->getName(),
770                   "VCS_CPU1");
771     }
772 
773     // Test where works: Parse chassis_template object with variables: No
774     // comments property
775     {
776         const json element = R"(
777             {
778               "id": "foo_chassis",
779               "number": "${chassis_number}",
780               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
781               "power_sequencers": [
782                 {
783                   "type": "UCD90320",
784                   "i2c_interface": { "bus": "${bus}", "address": "${address}" },
785                   "power_control_gpio_name": "power-chassis${chassis_number}-control",
786                   "power_good_gpio_name": "power-chassis${chassis_number}-good",
787                   "rails": [ { "name": "vio${chassis_number}" } ]
788                 }
789               ]
790             }
791         )"_json;
792         bool isChassisTemplate{true};
793         std::map<std::string, std::string> variables{
794             {"chassis_number", "2"}, {"bus", "12"}, {"address", "0x71"}};
795         MockServices services{};
796         auto chassis = parseChassisProperties(element, isChassisTemplate,
797                                               variables, services);
798         EXPECT_EQ(chassis->getNumber(), 2);
799         EXPECT_EQ(chassis->getInventoryPath(),
800                   "/xyz/openbmc_project/inventory/system/chassis2");
801         EXPECT_EQ(chassis->getPowerSequencers().size(), 1);
802         EXPECT_EQ(chassis->getPowerSequencers()[0]->getName(), "UCD90320");
803         EXPECT_EQ(chassis->getPowerSequencers()[0]->getBus(), 12);
804         EXPECT_EQ(chassis->getPowerSequencers()[0]->getAddress(), 0x71);
805         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerControlGPIOName(),
806                   "power-chassis2-control");
807         EXPECT_EQ(chassis->getPowerSequencers()[0]->getPowerGoodGPIOName(),
808                   "power-chassis2-good");
809         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails().size(), 1);
810         EXPECT_EQ(chassis->getPowerSequencers()[0]->getRails()[0]->getName(),
811                   "vio2");
812     }
813 
814     // Test where fails: Element is not an object
815     try
816     {
817         const json element = R"( true )"_json;
818         bool isChassisTemplate{false};
819         std::map<std::string, std::string> variables{};
820         MockServices services{};
821         parseChassisProperties(element, isChassisTemplate, variables, services);
822         ADD_FAILURE() << "Should not have reached this line.";
823     }
824     catch (const std::invalid_argument& e)
825     {
826         EXPECT_STREQ(e.what(), "Element is not an object");
827     }
828 
829     // Test where fails: Required id property not specified in chassis template
830     try
831     {
832         const json element = R"(
833             {
834               "number": "${chassis_number}",
835               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
836               "power_sequencers": []
837             }
838         )"_json;
839         bool isChassisTemplate{true};
840         std::map<std::string, std::string> variables{{"chassis_number", "2"}};
841         MockServices services{};
842         parseChassisProperties(element, isChassisTemplate, variables, services);
843         ADD_FAILURE() << "Should not have reached this line.";
844     }
845     catch (const std::invalid_argument& e)
846     {
847         EXPECT_STREQ(e.what(), "Required property missing: id");
848     }
849 
850     // Test where fails: Required number property not specified
851     try
852     {
853         const json element = R"(
854             {
855               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
856               "power_sequencers": []
857             }
858         )"_json;
859         bool isChassisTemplate{false};
860         std::map<std::string, std::string> variables{};
861         MockServices services{};
862         parseChassisProperties(element, isChassisTemplate, variables, services);
863         ADD_FAILURE() << "Should not have reached this line.";
864     }
865     catch (const std::invalid_argument& e)
866     {
867         EXPECT_STREQ(e.what(), "Required property missing: number");
868     }
869 
870     // Test where fails: Required inventory_path property not specified
871     try
872     {
873         const json element = R"(
874             {
875               "id": "foo_chassis",
876               "number": "${chassis_number}",
877               "power_sequencers": []
878             }
879         )"_json;
880         bool isChassisTemplate{true};
881         std::map<std::string, std::string> variables{{"chassis_number", "2"}};
882         MockServices services{};
883         parseChassisProperties(element, isChassisTemplate, variables, services);
884         ADD_FAILURE() << "Should not have reached this line.";
885     }
886     catch (const std::invalid_argument& e)
887     {
888         EXPECT_STREQ(e.what(), "Required property missing: inventory_path");
889     }
890 
891     // Test where fails: Required power_sequencers property not specified
892     try
893     {
894         const json element = R"(
895             {
896               "number": 1,
897               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis"
898             }
899         )"_json;
900         bool isChassisTemplate{false};
901         std::map<std::string, std::string> variables{};
902         MockServices services{};
903         parseChassisProperties(element, isChassisTemplate, variables, services);
904         ADD_FAILURE() << "Should not have reached this line.";
905     }
906     catch (const std::invalid_argument& e)
907     {
908         EXPECT_STREQ(e.what(), "Required property missing: power_sequencers");
909     }
910 
911     // Test where fails: number value is invalid: Not an integer
912     try
913     {
914         const json element = R"(
915             {
916               "number": "two",
917               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
918               "power_sequencers": []
919             }
920         )"_json;
921         bool isChassisTemplate{false};
922         std::map<std::string, std::string> variables{};
923         MockServices services{};
924         parseChassisProperties(element, isChassisTemplate, variables, services);
925         ADD_FAILURE() << "Should not have reached this line.";
926     }
927     catch (const std::invalid_argument& e)
928     {
929         EXPECT_STREQ(e.what(), "Element is not an integer");
930     }
931 
932     // Test where fails: number value is invalid: Equal to 0
933     try
934     {
935         const json element = R"(
936             {
937               "number": 0,
938               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
939               "power_sequencers": []
940             }
941         )"_json;
942         bool isChassisTemplate{false};
943         std::map<std::string, std::string> variables{};
944         MockServices services{};
945         parseChassisProperties(element, isChassisTemplate, variables, services);
946         ADD_FAILURE() << "Should not have reached this line.";
947     }
948     catch (const std::invalid_argument& e)
949     {
950         EXPECT_STREQ(e.what(), "Invalid chassis number: Must be > 0");
951     }
952 
953     // Test where fails: inventory_path value is invalid
954     try
955     {
956         const json element = R"(
957             {
958               "number": 1,
959               "inventory_path": "",
960               "power_sequencers": []
961             }
962         )"_json;
963         bool isChassisTemplate{false};
964         std::map<std::string, std::string> variables{};
965         MockServices services{};
966         parseChassisProperties(element, isChassisTemplate, variables, services);
967         ADD_FAILURE() << "Should not have reached this line.";
968     }
969     catch (const std::invalid_argument& e)
970     {
971         EXPECT_STREQ(e.what(), "Element contains an empty string");
972     }
973 
974     // Test where fails: power_sequencers value is invalid
975     try
976     {
977         const json element = R"(
978             {
979               "number": 1,
980               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
981               "power_sequencers": { "name": "foo" }
982             }
983         )"_json;
984         bool isChassisTemplate{false};
985         std::map<std::string, std::string> variables{};
986         MockServices services{};
987         parseChassisProperties(element, isChassisTemplate, variables, services);
988         ADD_FAILURE() << "Should not have reached this line.";
989     }
990     catch (const std::invalid_argument& e)
991     {
992         EXPECT_STREQ(e.what(), "Element is not an array");
993     }
994 
995     // Test where fails: Invalid property specified
996     try
997     {
998         const json element = R"(
999             {
1000               "foo": "bar",
1001               "number": 1,
1002               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis",
1003               "power_sequencers": []
1004             }
1005         )"_json;
1006         bool isChassisTemplate{false};
1007         std::map<std::string, std::string> variables{};
1008         MockServices services{};
1009         parseChassisProperties(element, isChassisTemplate, variables, services);
1010         ADD_FAILURE() << "Should not have reached this line.";
1011     }
1012     catch (const std::invalid_argument& e)
1013     {
1014         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1015     }
1016 
1017     // Test where fails: Invalid variable value specified
1018     try
1019     {
1020         const json element = R"(
1021             {
1022               "id": "foo_chassis",
1023               "number": "${chassis_number}",
1024               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1025               "power_sequencers": []
1026             }
1027         )"_json;
1028         bool isChassisTemplate{true};
1029         std::map<std::string, std::string> variables{{"chassis_number", "two"}};
1030         MockServices services{};
1031         parseChassisProperties(element, isChassisTemplate, variables, services);
1032         ADD_FAILURE() << "Should not have reached this line.";
1033     }
1034     catch (const std::invalid_argument& e)
1035     {
1036         EXPECT_STREQ(e.what(), "Element is not an integer");
1037     }
1038 }
1039 
TEST(ConfigFileParserTests,ParseChassisTemplate)1040 TEST(ConfigFileParserTests, ParseChassisTemplate)
1041 {
1042     // Test where works: comments specified
1043     {
1044         const json element = R"(
1045             {
1046               "comments": [ "This is a template for the foo chassis type",
1047                             "Chassis contains a UCD90320 power sequencer" ],
1048               "id": "foo_chassis",
1049               "number": "${chassis_number}",
1050               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1051               "power_sequencers": [
1052                 {
1053                   "type": "UCD90320",
1054                   "i2c_interface": { "bus": "${bus}", "address": "0x11" },
1055                   "power_control_gpio_name": "power-chassis${chassis_number}-control",
1056                   "power_good_gpio_name": "power-chassis${chassis_number}-good",
1057                   "rails": [ { "name": "VDD_CPU0" }, { "name": "VCS_CPU1" } ]
1058                 }
1059               ]
1060             }
1061         )"_json;
1062         auto [id, jsonRef] = parseChassisTemplate(element);
1063         EXPECT_EQ(id, "foo_chassis");
1064         EXPECT_EQ(jsonRef.get()["number"], "${chassis_number}");
1065         EXPECT_EQ(jsonRef.get()["power_sequencers"].size(), 1);
1066         EXPECT_EQ(jsonRef.get()["power_sequencers"][0]["type"], "UCD90320");
1067     }
1068 
1069     // Test where works: comments not specified
1070     {
1071         const json element = R"(
1072             {
1073               "id": "foo_chassis",
1074               "number": "${chassis_number}",
1075               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1076               "power_sequencers": []
1077             }
1078         )"_json;
1079         auto [id, jsonRef] = parseChassisTemplate(element);
1080         EXPECT_EQ(id, "foo_chassis");
1081         EXPECT_EQ(jsonRef.get()["number"], "${chassis_number}");
1082         EXPECT_EQ(
1083             jsonRef.get()["inventory_path"],
1084             "/xyz/openbmc_project/inventory/system/chassis${chassis_number}");
1085         EXPECT_EQ(jsonRef.get()["power_sequencers"].size(), 0);
1086     }
1087 
1088     // Test where fails: Element is not an object
1089     try
1090     {
1091         const json element = R"( [ "vdda", "vddb" ] )"_json;
1092         parseChassisTemplate(element);
1093         ADD_FAILURE() << "Should not have reached this line.";
1094     }
1095     catch (const std::invalid_argument& e)
1096     {
1097         EXPECT_STREQ(e.what(), "Element is not an object");
1098     }
1099 
1100     // Test where fails: Required id property not specified
1101     try
1102     {
1103         const json element = R"(
1104             {
1105               "number": "${chassis_number}",
1106               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1107               "power_sequencers": []
1108             }
1109         )"_json;
1110         parseChassisTemplate(element);
1111         ADD_FAILURE() << "Should not have reached this line.";
1112     }
1113     catch (const std::invalid_argument& e)
1114     {
1115         EXPECT_STREQ(e.what(), "Required property missing: id");
1116     }
1117 
1118     // Test where fails: Required number property not specified
1119     try
1120     {
1121         const json element = R"(
1122             {
1123               "id": "foo_chassis",
1124               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1125               "power_sequencers": []
1126             }
1127         )"_json;
1128         parseChassisTemplate(element);
1129         ADD_FAILURE() << "Should not have reached this line.";
1130     }
1131     catch (const std::invalid_argument& e)
1132     {
1133         EXPECT_STREQ(e.what(), "Required property missing: number");
1134     }
1135 
1136     // Test where fails: Required inventory_path property not specified
1137     try
1138     {
1139         const json element = R"(
1140             {
1141               "id": "foo_chassis",
1142               "number": "${chassis_number}",
1143               "power_sequencers": []
1144             }
1145         )"_json;
1146         parseChassisTemplate(element);
1147         ADD_FAILURE() << "Should not have reached this line.";
1148     }
1149     catch (const std::invalid_argument& e)
1150     {
1151         EXPECT_STREQ(e.what(), "Required property missing: inventory_path");
1152     }
1153 
1154     // Test where fails: Required power_sequencers property not specified
1155     try
1156     {
1157         const json element = R"(
1158             {
1159               "id": "foo_chassis",
1160               "number": "${chassis_number}",
1161               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}"
1162             }
1163         )"_json;
1164         parseChassisTemplate(element);
1165         ADD_FAILURE() << "Should not have reached this line.";
1166     }
1167     catch (const std::invalid_argument& e)
1168     {
1169         EXPECT_STREQ(e.what(), "Required property missing: power_sequencers");
1170     }
1171 
1172     // Test where fails: id value is invalid
1173     try
1174     {
1175         const json element = R"(
1176             {
1177               "id": 13,
1178               "number": "${chassis_number}",
1179               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1180               "power_sequencers": []
1181             }
1182         )"_json;
1183         parseChassisTemplate(element);
1184         ADD_FAILURE() << "Should not have reached this line.";
1185     }
1186     catch (const std::invalid_argument& e)
1187     {
1188         EXPECT_STREQ(e.what(), "Element is not a string");
1189     }
1190 
1191     // Test where fails: Invalid property specified
1192     try
1193     {
1194         const json element = R"(
1195             {
1196               "id": "foo_chassis",
1197               "number": "${chassis_number}",
1198               "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1199               "power_sequencers": [],
1200               "foo": "bar"
1201             }
1202         )"_json;
1203         parseChassisTemplate(element);
1204         ADD_FAILURE() << "Should not have reached this line.";
1205     }
1206     catch (const std::invalid_argument& e)
1207     {
1208         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1209     }
1210 }
1211 
TEST(ConfigFileParserTests,ParseChassisTemplateArray)1212 TEST(ConfigFileParserTests, ParseChassisTemplateArray)
1213 {
1214     // Test where works: Array is empty
1215     {
1216         const json element = R"(
1217             [
1218             ]
1219         )"_json;
1220         auto chassisTemplates = parseChassisTemplateArray(element);
1221         EXPECT_EQ(chassisTemplates.size(), 0);
1222     }
1223 
1224     // Test where works: Array is not empty
1225     {
1226         const json element = R"(
1227             [
1228               {
1229                 "id": "foo_chassis",
1230                 "number": "${chassis_number}",
1231                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1232                 "power_sequencers": []
1233               },
1234               {
1235                 "id": "bar_chassis",
1236                 "number": "${number}",
1237                 "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${number}",
1238                 "power_sequencers": []
1239               }
1240             ]
1241         )"_json;
1242         auto chassisTemplates = parseChassisTemplateArray(element);
1243         EXPECT_EQ(chassisTemplates.size(), 2);
1244         EXPECT_EQ(chassisTemplates.at("foo_chassis").get()["number"],
1245                   "${chassis_number}");
1246         EXPECT_EQ(
1247             chassisTemplates.at("foo_chassis").get()["inventory_path"],
1248             "/xyz/openbmc_project/inventory/system/chassis${chassis_number}");
1249         EXPECT_EQ(chassisTemplates.at("bar_chassis").get()["number"],
1250                   "${number}");
1251         EXPECT_EQ(chassisTemplates.at("bar_chassis").get()["inventory_path"],
1252                   "/xyz/openbmc_project/inventory/system/bar_chassis${number}");
1253     }
1254 
1255     // Test where fails: Element is not an array
1256     try
1257     {
1258         const json element = R"(
1259             {
1260                 "foo": "bar"
1261             }
1262         )"_json;
1263         parseChassisTemplateArray(element);
1264         ADD_FAILURE() << "Should not have reached this line.";
1265     }
1266     catch (const std::invalid_argument& e)
1267     {
1268         EXPECT_STREQ(e.what(), "Element is not an array");
1269     }
1270 
1271     // Test where fails: Element within array is invalid
1272     try
1273     {
1274         const json element = R"(
1275             [
1276               {
1277                 "id": "foo_chassis",
1278                 "number": "${chassis_number}",
1279                 "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
1280                 "power_sequencers": []
1281               },
1282               {
1283                 "id": "bar_chassis"
1284               }
1285             ]
1286         )"_json;
1287         parseChassisTemplateArray(element);
1288         ADD_FAILURE() << "Should not have reached this line.";
1289     }
1290     catch (const std::invalid_argument& e)
1291     {
1292         EXPECT_STREQ(e.what(), "Required property missing: number");
1293     }
1294 }
1295 
TEST(ConfigFileParserTests,ParseGPIO)1296 TEST(ConfigFileParserTests, ParseGPIO)
1297 {
1298     // Test where works: Only required properties specified
1299     {
1300         const json element = R"(
1301             {
1302                 "line": 60
1303             }
1304         )"_json;
1305         std::map<std::string, std::string> variables{};
1306         auto gpio = parseGPIO(element, variables);
1307         EXPECT_EQ(gpio.line, 60);
1308         EXPECT_FALSE(gpio.activeLow);
1309     }
1310 
1311     // Test where works: All properties specified
1312     {
1313         const json element = R"(
1314             {
1315                 "line": 131,
1316                 "active_low": true
1317             }
1318         )"_json;
1319         std::map<std::string, std::string> variables{};
1320         auto gpio = parseGPIO(element, variables);
1321         EXPECT_EQ(gpio.line, 131);
1322         EXPECT_TRUE(gpio.activeLow);
1323     }
1324 
1325     // Test where works: Variables specified
1326     {
1327         const json element = R"(
1328             {
1329                 "line": "${line}",
1330                 "active_low": "${active_low}"
1331             }
1332         )"_json;
1333         std::map<std::string, std::string> variables{{"line", "54"},
1334                                                      {"active_low", "false"}};
1335         auto gpio = parseGPIO(element, variables);
1336         EXPECT_EQ(gpio.line, 54);
1337         EXPECT_FALSE(gpio.activeLow);
1338     }
1339 
1340     // Test where fails: Element is not an object
1341     try
1342     {
1343         const json element = R"( [ "vdda", "vddb" ] )"_json;
1344         std::map<std::string, std::string> variables{};
1345         parseGPIO(element, variables);
1346         ADD_FAILURE() << "Should not have reached this line.";
1347     }
1348     catch (const std::invalid_argument& e)
1349     {
1350         EXPECT_STREQ(e.what(), "Element is not an object");
1351     }
1352 
1353     // Test where fails: Required line property not specified
1354     try
1355     {
1356         const json element = R"(
1357             {
1358                 "active_low": true
1359             }
1360         )"_json;
1361         std::map<std::string, std::string> variables{};
1362         parseGPIO(element, variables);
1363         ADD_FAILURE() << "Should not have reached this line.";
1364     }
1365     catch (const std::invalid_argument& e)
1366     {
1367         EXPECT_STREQ(e.what(), "Required property missing: line");
1368     }
1369 
1370     // Test where fails: line value is invalid
1371     try
1372     {
1373         const json element = R"(
1374             {
1375                 "line": -131,
1376                 "active_low": true
1377             }
1378         )"_json;
1379         std::map<std::string, std::string> variables{};
1380         parseGPIO(element, variables);
1381         ADD_FAILURE() << "Should not have reached this line.";
1382     }
1383     catch (const std::invalid_argument& e)
1384     {
1385         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
1386     }
1387 
1388     // Test where fails: active_low value is invalid
1389     try
1390     {
1391         const json element = R"(
1392             {
1393                 "line": 131,
1394                 "active_low": "true"
1395             }
1396         )"_json;
1397         std::map<std::string, std::string> variables{};
1398         parseGPIO(element, variables);
1399         ADD_FAILURE() << "Should not have reached this line.";
1400     }
1401     catch (const std::invalid_argument& e)
1402     {
1403         EXPECT_STREQ(e.what(), "Element is not a boolean");
1404     }
1405 
1406     // Test where fails: Invalid property specified
1407     try
1408     {
1409         const json element = R"(
1410             {
1411                 "line": 131,
1412                 "foo": "bar"
1413             }
1414         )"_json;
1415         std::map<std::string, std::string> variables{};
1416         parseGPIO(element, variables);
1417         ADD_FAILURE() << "Should not have reached this line.";
1418     }
1419     catch (const std::invalid_argument& e)
1420     {
1421         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1422     }
1423 
1424     // Test where fails: Invalid variable value specified
1425     try
1426     {
1427         const json element = R"(
1428             {
1429                 "line": "${line}",
1430                 "active_low": "${active_low}"
1431             }
1432         )"_json;
1433         std::map<std::string, std::string> variables{{"line", "-1"},
1434                                                      {"active_low", "false"}};
1435         parseGPIO(element, variables);
1436         ADD_FAILURE() << "Should not have reached this line.";
1437     }
1438     catch (const std::invalid_argument& e)
1439     {
1440         EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
1441     }
1442 }
1443 
TEST(ConfigFileParserTests,ParseI2CInterface)1444 TEST(ConfigFileParserTests, ParseI2CInterface)
1445 {
1446     // Test where works: No variables
1447     {
1448         const json element = R"(
1449             {
1450                 "bus": 2,
1451                 "address": "0x70"
1452             }
1453         )"_json;
1454         std::map<std::string, std::string> variables{};
1455         auto [bus, address] = parseI2CInterface(element, variables);
1456         EXPECT_EQ(bus, 2);
1457         EXPECT_EQ(address, 0x70);
1458     }
1459 
1460     // Test where works: Variables specified
1461     {
1462         const json element = R"(
1463             {
1464                 "bus": "${bus}",
1465                 "address": "${address}"
1466             }
1467         )"_json;
1468         std::map<std::string, std::string> variables{{"bus", "3"},
1469                                                      {"address", "0x23"}};
1470         auto [bus, address] = parseI2CInterface(element, variables);
1471         EXPECT_EQ(bus, 3);
1472         EXPECT_EQ(address, 0x23);
1473     }
1474 
1475     // Test where fails: Element is not an object
1476     try
1477     {
1478         const json element = R"( [ 1, "0x70" ] )"_json;
1479         std::map<std::string, std::string> variables{};
1480         parseI2CInterface(element, variables);
1481         ADD_FAILURE() << "Should not have reached this line.";
1482     }
1483     catch (const std::invalid_argument& e)
1484     {
1485         EXPECT_STREQ(e.what(), "Element is not an object");
1486     }
1487 
1488     // Test where fails: Required bus property not specified
1489     try
1490     {
1491         const json element = R"(
1492             {
1493                 "address": "0x70"
1494             }
1495         )"_json;
1496         std::map<std::string, std::string> variables{};
1497         parseI2CInterface(element, variables);
1498         ADD_FAILURE() << "Should not have reached this line.";
1499     }
1500     catch (const std::invalid_argument& e)
1501     {
1502         EXPECT_STREQ(e.what(), "Required property missing: bus");
1503     }
1504 
1505     // Test where fails: Required address property not specified
1506     try
1507     {
1508         const json element = R"(
1509             {
1510                 "bus": 2
1511             }
1512         )"_json;
1513         std::map<std::string, std::string> variables{};
1514         parseI2CInterface(element, variables);
1515         ADD_FAILURE() << "Should not have reached this line.";
1516     }
1517     catch (const std::invalid_argument& e)
1518     {
1519         EXPECT_STREQ(e.what(), "Required property missing: address");
1520     }
1521 
1522     // Test where fails: bus value is invalid
1523     try
1524     {
1525         const json element = R"(
1526             {
1527                 "bus": 1.1,
1528                 "address": "0x70"
1529             }
1530         )"_json;
1531         std::map<std::string, std::string> variables{};
1532         parseI2CInterface(element, variables);
1533         ADD_FAILURE() << "Should not have reached this line.";
1534     }
1535     catch (const std::invalid_argument& e)
1536     {
1537         EXPECT_STREQ(e.what(), "Element is not an integer");
1538     }
1539 
1540     // Test where fails: address value is invalid
1541     try
1542     {
1543         const json element = R"(
1544             {
1545                 "bus": 2,
1546                 "address": 70
1547             }
1548         )"_json;
1549         std::map<std::string, std::string> variables{};
1550         parseI2CInterface(element, variables);
1551         ADD_FAILURE() << "Should not have reached this line.";
1552     }
1553     catch (const std::invalid_argument& e)
1554     {
1555         EXPECT_STREQ(e.what(), "Element is not a string");
1556     }
1557 
1558     // Test where fails: Invalid property specified
1559     try
1560     {
1561         const json element = R"(
1562             {
1563                 "bus": 2,
1564                 "address": "0x70",
1565                 "foo": "bar"
1566             }
1567         )"_json;
1568         std::map<std::string, std::string> variables{};
1569         parseI2CInterface(element, variables);
1570         ADD_FAILURE() << "Should not have reached this line.";
1571     }
1572     catch (const std::invalid_argument& e)
1573     {
1574         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1575     }
1576 
1577     // Test where fails: Invalid variable value specified
1578     try
1579     {
1580         const json element = R"(
1581             {
1582                 "bus": "${bus}",
1583                 "address": "${address}"
1584             }
1585         )"_json;
1586         std::map<std::string, std::string> variables{{"bus", "foo"},
1587                                                      {"address", "0x23"}};
1588         parseI2CInterface(element, variables);
1589         ADD_FAILURE() << "Should not have reached this line.";
1590     }
1591     catch (const std::invalid_argument& e)
1592     {
1593         EXPECT_STREQ(e.what(), "Element is not an integer");
1594     }
1595 }
1596 
TEST(ConfigFileParserTests,ParsePowerSequencer)1597 TEST(ConfigFileParserTests, ParsePowerSequencer)
1598 {
1599     // Test where works: Has comments property: Type is "UCD90160"
1600     {
1601         const json element = R"(
1602             {
1603               "comments": [ "Power sequencer in chassis 1",
1604                             "Controls VDD rails" ],
1605               "type": "UCD90160",
1606               "i2c_interface": { "bus": 3, "address": "0x11" },
1607               "power_control_gpio_name": "power-chassis-control",
1608               "power_good_gpio_name": "power-chassis-good",
1609               "rails": [ { "name": "VDD_CPU0" }, { "name": "VCS_CPU1" } ]
1610             }
1611         )"_json;
1612         std::map<std::string, std::string> variables{};
1613         MockServices services{};
1614         auto powerSequencer = parsePowerSequencer(element, variables, services);
1615         EXPECT_EQ(powerSequencer->getName(), "UCD90160");
1616         EXPECT_EQ(powerSequencer->getBus(), 3);
1617         EXPECT_EQ(powerSequencer->getAddress(), 0x11);
1618         EXPECT_EQ(powerSequencer->getPowerControlGPIOName(),
1619                   "power-chassis-control");
1620         EXPECT_EQ(powerSequencer->getPowerGoodGPIOName(), "power-chassis-good");
1621         EXPECT_EQ(powerSequencer->getRails().size(), 2);
1622         EXPECT_EQ(powerSequencer->getRails()[0]->getName(), "VDD_CPU0");
1623         EXPECT_EQ(powerSequencer->getRails()[1]->getName(), "VCS_CPU1");
1624     }
1625 
1626     // Test where works: No comments property: Variables specified: Type is
1627     // "UCD90320"
1628     {
1629         const json element = R"(
1630             {
1631               "type": "${type}",
1632               "i2c_interface": { "bus": "${bus}", "address": "${address}" },
1633               "power_control_gpio_name": "${power_control_gpio_name}",
1634               "power_good_gpio_name": "${power_good_gpio_name}",
1635               "rails": [ { "name": "${rail1}" }, { "name": "${rail2}" } ]
1636             }
1637         )"_json;
1638         std::map<std::string, std::string> variables{
1639             {"type", "UCD90320"},
1640             {"bus", "4"},
1641             {"address", "0x24"},
1642             {"power_control_gpio_name", "power_on"},
1643             {"power_good_gpio_name", "pgood"},
1644             {"rail1", "cpu1"},
1645             {"rail2", "cpu2"}};
1646         MockServices services{};
1647         auto powerSequencer = parsePowerSequencer(element, variables, services);
1648         EXPECT_EQ(powerSequencer->getName(), "UCD90320");
1649         EXPECT_EQ(powerSequencer->getBus(), 4);
1650         EXPECT_EQ(powerSequencer->getAddress(), 0x24);
1651         EXPECT_EQ(powerSequencer->getPowerControlGPIOName(), "power_on");
1652         EXPECT_EQ(powerSequencer->getPowerGoodGPIOName(), "pgood");
1653         EXPECT_EQ(powerSequencer->getRails().size(), 2);
1654         EXPECT_EQ(powerSequencer->getRails()[0]->getName(), "cpu1");
1655         EXPECT_EQ(powerSequencer->getRails()[1]->getName(), "cpu2");
1656     }
1657 
1658     // Test where works: Type is "gpios_only_device"
1659     {
1660         const json element = R"(
1661             {
1662               "type": "gpios_only_device",
1663               "power_control_gpio_name": "power-chassis-control",
1664               "power_good_gpio_name": "power-chassis-good"
1665             }
1666         )"_json;
1667         std::map<std::string, std::string> variables{};
1668         MockServices services{};
1669         auto powerSequencer = parsePowerSequencer(element, variables, services);
1670         EXPECT_EQ(powerSequencer->getName(), "gpios_only_device");
1671         EXPECT_EQ(powerSequencer->getBus(), 0);
1672         EXPECT_EQ(powerSequencer->getAddress(), 0);
1673         EXPECT_EQ(powerSequencer->getPowerControlGPIOName(),
1674                   "power-chassis-control");
1675         EXPECT_EQ(powerSequencer->getPowerGoodGPIOName(), "power-chassis-good");
1676         EXPECT_EQ(powerSequencer->getRails().size(), 0);
1677     }
1678 
1679     // Test where fails: Element is not an object
1680     try
1681     {
1682         const json element = R"( [ "vdda", "vddb" ] )"_json;
1683         std::map<std::string, std::string> variables{};
1684         MockServices services{};
1685         parsePowerSequencer(element, variables, services);
1686         ADD_FAILURE() << "Should not have reached this line.";
1687     }
1688     catch (const std::invalid_argument& e)
1689     {
1690         EXPECT_STREQ(e.what(), "Element is not an object");
1691     }
1692 
1693     // Test where fails: Required type property not specified
1694     try
1695     {
1696         const json element = R"(
1697             {
1698               "i2c_interface": { "bus": 3, "address": "0x11" },
1699               "power_control_gpio_name": "power-chassis-control",
1700               "power_good_gpio_name": "power-chassis-good",
1701               "rails": []
1702             }
1703         )"_json;
1704         std::map<std::string, std::string> variables{};
1705         MockServices services{};
1706         parsePowerSequencer(element, variables, services);
1707         ADD_FAILURE() << "Should not have reached this line.";
1708     }
1709     catch (const std::invalid_argument& e)
1710     {
1711         EXPECT_STREQ(e.what(), "Required property missing: type");
1712     }
1713 
1714     // Test where fails: Required i2c_interface property not specified
1715     try
1716     {
1717         const json element = R"(
1718             {
1719               "type": "UCD90320",
1720               "power_control_gpio_name": "power-chassis-control",
1721               "power_good_gpio_name": "power-chassis-good",
1722               "rails": []
1723             }
1724         )"_json;
1725         std::map<std::string, std::string> variables{};
1726         MockServices services{};
1727         parsePowerSequencer(element, variables, services);
1728         ADD_FAILURE() << "Should not have reached this line.";
1729     }
1730     catch (const std::invalid_argument& e)
1731     {
1732         EXPECT_STREQ(e.what(), "Required property missing: i2c_interface");
1733     }
1734 
1735     // Test where fails: Required power_control_gpio_name property not specified
1736     try
1737     {
1738         const json element = R"(
1739             {
1740               "type": "UCD90320",
1741               "i2c_interface": { "bus": 3, "address": "0x11" },
1742               "power_good_gpio_name": "power-chassis-good",
1743               "rails": []
1744             }
1745         )"_json;
1746         std::map<std::string, std::string> variables{};
1747         MockServices services{};
1748         parsePowerSequencer(element, variables, services);
1749         ADD_FAILURE() << "Should not have reached this line.";
1750     }
1751     catch (const std::invalid_argument& e)
1752     {
1753         EXPECT_STREQ(e.what(),
1754                      "Required property missing: power_control_gpio_name");
1755     }
1756 
1757     // Test where fails: Required power_good_gpio_name property not specified
1758     try
1759     {
1760         const json element = R"(
1761             {
1762               "type": "UCD90320",
1763               "i2c_interface": { "bus": 3, "address": "0x11" },
1764               "power_control_gpio_name": "power-chassis-control",
1765               "rails": []
1766             }
1767         )"_json;
1768         std::map<std::string, std::string> variables{};
1769         MockServices services{};
1770         parsePowerSequencer(element, variables, services);
1771         ADD_FAILURE() << "Should not have reached this line.";
1772     }
1773     catch (const std::invalid_argument& e)
1774     {
1775         EXPECT_STREQ(e.what(),
1776                      "Required property missing: power_good_gpio_name");
1777     }
1778 
1779     // Test where fails: Required rails property not specified
1780     try
1781     {
1782         const json element = R"(
1783             {
1784               "type": "UCD90320",
1785               "i2c_interface": { "bus": 3, "address": "0x11" },
1786               "power_control_gpio_name": "power-chassis-control",
1787               "power_good_gpio_name": "power-chassis-good"
1788             }
1789         )"_json;
1790         std::map<std::string, std::string> variables{};
1791         MockServices services{};
1792         parsePowerSequencer(element, variables, services);
1793         ADD_FAILURE() << "Should not have reached this line.";
1794     }
1795     catch (const std::invalid_argument& e)
1796     {
1797         EXPECT_STREQ(e.what(), "Required property missing: rails");
1798     }
1799 
1800     // Test where fails: type value is invalid: Not a string
1801     try
1802     {
1803         const json element = R"(
1804             {
1805               "type": true,
1806               "i2c_interface": { "bus": 3, "address": "0x11" },
1807               "power_control_gpio_name": "power-chassis-control",
1808               "power_good_gpio_name": "power-chassis-good",
1809               "rails": []
1810             }
1811         )"_json;
1812         std::map<std::string, std::string> variables{};
1813         MockServices services{};
1814         parsePowerSequencer(element, variables, services);
1815         ADD_FAILURE() << "Should not have reached this line.";
1816     }
1817     catch (const std::invalid_argument& e)
1818     {
1819         EXPECT_STREQ(e.what(), "Element is not a string");
1820     }
1821 
1822     // Test where fails: type value is invalid: Not a supported type
1823     try
1824     {
1825         const json element = R"(
1826             {
1827               "type": "foo_bar",
1828               "i2c_interface": { "bus": 3, "address": "0x11" },
1829               "power_control_gpio_name": "power-chassis-control",
1830               "power_good_gpio_name": "power-chassis-good",
1831               "rails": []
1832             }
1833         )"_json;
1834         std::map<std::string, std::string> variables{};
1835         MockServices services{};
1836         parsePowerSequencer(element, variables, services);
1837         ADD_FAILURE() << "Should not have reached this line.";
1838     }
1839     catch (const std::invalid_argument& e)
1840     {
1841         EXPECT_STREQ(e.what(), "Invalid power sequencer type: foo_bar");
1842     }
1843 
1844     // Test where fails: i2c_interface value is invalid
1845     try
1846     {
1847         const json element = R"(
1848             {
1849               "type": "UCD90320",
1850               "i2c_interface": 3,
1851               "power_control_gpio_name": "power-chassis-control",
1852               "power_good_gpio_name": "power-chassis-good",
1853               "rails": []
1854             }
1855         )"_json;
1856         std::map<std::string, std::string> variables{};
1857         MockServices services{};
1858         parsePowerSequencer(element, variables, services);
1859         ADD_FAILURE() << "Should not have reached this line.";
1860     }
1861     catch (const std::invalid_argument& e)
1862     {
1863         EXPECT_STREQ(e.what(), "Element is not an object");
1864     }
1865 
1866     // Test where fails: power_control_gpio_name value is invalid
1867     try
1868     {
1869         const json element = R"(
1870             {
1871               "type": "UCD90320",
1872               "i2c_interface": { "bus": 3, "address": "0x11" },
1873               "power_control_gpio_name": [],
1874               "power_good_gpio_name": "power-chassis-good",
1875               "rails": []
1876             }
1877         )"_json;
1878         std::map<std::string, std::string> variables{};
1879         MockServices services{};
1880         parsePowerSequencer(element, variables, services);
1881         ADD_FAILURE() << "Should not have reached this line.";
1882     }
1883     catch (const std::invalid_argument& e)
1884     {
1885         EXPECT_STREQ(e.what(), "Element is not a string");
1886     }
1887 
1888     // Test where fails: power_good_gpio_name value is invalid
1889     try
1890     {
1891         const json element = R"(
1892             {
1893               "type": "UCD90320",
1894               "i2c_interface": { "bus": 3, "address": "0x11" },
1895               "power_control_gpio_name": "power-chassis-control",
1896               "power_good_gpio_name": 12,
1897               "rails": []
1898             }
1899         )"_json;
1900         std::map<std::string, std::string> variables{};
1901         MockServices services{};
1902         parsePowerSequencer(element, variables, services);
1903         ADD_FAILURE() << "Should not have reached this line.";
1904     }
1905     catch (const std::invalid_argument& e)
1906     {
1907         EXPECT_STREQ(e.what(), "Element is not a string");
1908     }
1909 
1910     // Test where fails: rails value is invalid
1911     try
1912     {
1913         const json element = R"(
1914             {
1915               "type": "UCD90320",
1916               "i2c_interface": { "bus": 3, "address": "0x11" },
1917               "power_control_gpio_name": "power-chassis-control",
1918               "power_good_gpio_name": "power-chassis-good",
1919               "rails": [ { "name": 33 } ]
1920             }
1921         )"_json;
1922         std::map<std::string, std::string> variables{};
1923         MockServices services{};
1924         parsePowerSequencer(element, variables, services);
1925         ADD_FAILURE() << "Should not have reached this line.";
1926     }
1927     catch (const std::invalid_argument& e)
1928     {
1929         EXPECT_STREQ(e.what(), "Element is not a string");
1930     }
1931 
1932     // Test where fails: Invalid property specified
1933     try
1934     {
1935         const json element = R"(
1936             {
1937               "type": "UCD90320",
1938               "i2c_interface": { "bus": 3, "address": "0x11" },
1939               "power_control_gpio_name": "power-chassis-control",
1940               "power_good_gpio_name": "power-chassis-good",
1941               "driver_name": "foo",
1942               "rails": []
1943             }
1944         )"_json;
1945         std::map<std::string, std::string> variables{};
1946         MockServices services{};
1947         parsePowerSequencer(element, variables, services);
1948         ADD_FAILURE() << "Should not have reached this line.";
1949     }
1950     catch (const std::invalid_argument& e)
1951     {
1952         EXPECT_STREQ(e.what(), "Element contains an invalid property");
1953     }
1954 
1955     // Test where fails: Invalid variable value specified
1956     try
1957     {
1958         const json element = R"(
1959             {
1960               "type": "UCD90320",
1961               "i2c_interface": { "bus": "${bus}", "address": "0x11" },
1962               "power_control_gpio_name": "power-chassis-control",
1963               "power_good_gpio_name": "power-chassis-good",
1964               "rails": []
1965             }
1966         )"_json;
1967         std::map<std::string, std::string> variables{{"bus", "two"}};
1968         MockServices services{};
1969         parsePowerSequencer(element, variables, services);
1970         ADD_FAILURE() << "Should not have reached this line.";
1971     }
1972     catch (const std::invalid_argument& e)
1973     {
1974         EXPECT_STREQ(e.what(), "Element is not an integer");
1975     }
1976 }
1977 
TEST(ConfigFileParserTests,ParsePowerSequencerArray)1978 TEST(ConfigFileParserTests, ParsePowerSequencerArray)
1979 {
1980     // Test where works: Array is empty
1981     {
1982         const json element = R"(
1983             [
1984             ]
1985         )"_json;
1986         std::map<std::string, std::string> variables{};
1987         MockServices services{};
1988         auto powerSequencers =
1989             parsePowerSequencerArray(element, variables, services);
1990         EXPECT_EQ(powerSequencers.size(), 0);
1991     }
1992 
1993     // Test where works: Array is not empty
1994     {
1995         const json element = R"(
1996             [
1997               {
1998                 "type": "UCD90160",
1999                 "i2c_interface": { "bus": 3, "address": "0x11" },
2000                 "power_control_gpio_name": "power-chassis-control1",
2001                 "power_good_gpio_name": "power-chassis-good1",
2002                 "rails": []
2003               },
2004               {
2005                 "type": "UCD90320",
2006                 "i2c_interface": { "bus": 4, "address": "0x70" },
2007                 "power_control_gpio_name": "power-chassis-control2",
2008                 "power_good_gpio_name": "power-chassis-good2",
2009                 "rails": []
2010               }
2011             ]
2012         )"_json;
2013         std::map<std::string, std::string> variables{};
2014         MockServices services{};
2015         auto powerSequencers =
2016             parsePowerSequencerArray(element, variables, services);
2017         EXPECT_EQ(powerSequencers.size(), 2);
2018         EXPECT_EQ(powerSequencers[0]->getName(), "UCD90160");
2019         EXPECT_EQ(powerSequencers[0]->getBus(), 3);
2020         EXPECT_EQ(powerSequencers[0]->getAddress(), 0x11);
2021         EXPECT_EQ(powerSequencers[1]->getName(), "UCD90320");
2022         EXPECT_EQ(powerSequencers[1]->getBus(), 4);
2023         EXPECT_EQ(powerSequencers[1]->getAddress(), 0x70);
2024     }
2025 
2026     // Test where works: Variables specified
2027     {
2028         const json element = R"(
2029             [
2030               {
2031                 "type": "UCD90160",
2032                 "i2c_interface": { "bus": "${bus1}", "address": "${address1}" },
2033                 "power_control_gpio_name": "power-chassis-control1",
2034                 "power_good_gpio_name": "power-chassis-good1",
2035                 "rails": []
2036               },
2037               {
2038                 "type": "UCD90320",
2039                 "i2c_interface": { "bus": "${bus2}", "address": "${address2}" },
2040                 "power_control_gpio_name": "power-chassis-control2",
2041                 "power_good_gpio_name": "power-chassis-good2",
2042                 "rails": []
2043               }
2044             ]
2045         )"_json;
2046         std::map<std::string, std::string> variables{
2047             {"bus1", "5"},
2048             {"address1", "0x22"},
2049             {"bus2", "7"},
2050             {"address2", "0x49"}};
2051         MockServices services{};
2052         auto powerSequencers =
2053             parsePowerSequencerArray(element, variables, services);
2054         EXPECT_EQ(powerSequencers.size(), 2);
2055         EXPECT_EQ(powerSequencers[0]->getName(), "UCD90160");
2056         EXPECT_EQ(powerSequencers[0]->getBus(), 5);
2057         EXPECT_EQ(powerSequencers[0]->getAddress(), 0x22);
2058         EXPECT_EQ(powerSequencers[1]->getName(), "UCD90320");
2059         EXPECT_EQ(powerSequencers[1]->getBus(), 7);
2060         EXPECT_EQ(powerSequencers[1]->getAddress(), 0x49);
2061     }
2062 
2063     // Test where fails: Element is not an array
2064     try
2065     {
2066         const json element = R"(
2067             {
2068                 "foo": "bar"
2069             }
2070         )"_json;
2071         std::map<std::string, std::string> variables{};
2072         MockServices services{};
2073         parsePowerSequencerArray(element, variables, services);
2074         ADD_FAILURE() << "Should not have reached this line.";
2075     }
2076     catch (const std::invalid_argument& e)
2077     {
2078         EXPECT_STREQ(e.what(), "Element is not an array");
2079     }
2080 
2081     // Test where fails: Element within array is invalid
2082     try
2083     {
2084         const json element = R"(
2085             [
2086               {
2087                 "type": "UCD90160",
2088                 "i2c_interface": { "bus": 3, "address": "0x11" },
2089                 "power_control_gpio_name": "power-chassis-control1",
2090                 "power_good_gpio_name": "power-chassis-good1",
2091                 "rails": []
2092               },
2093               true
2094             ]
2095         )"_json;
2096         std::map<std::string, std::string> variables{};
2097         MockServices services{};
2098         parsePowerSequencerArray(element, variables, services);
2099         ADD_FAILURE() << "Should not have reached this line.";
2100     }
2101     catch (const std::invalid_argument& e)
2102     {
2103         EXPECT_STREQ(e.what(), "Element is not an object");
2104     }
2105 
2106     // Test where fails: Invalid variable value specified
2107     try
2108     {
2109         const json element = R"(
2110             [
2111               {
2112                 "type": "UCD90320",
2113                 "i2c_interface": { "bus": "${bus}", "address": "${address}" },
2114                 "power_control_gpio_name": "power-chassis-control",
2115                 "power_good_gpio_name": "power-chassis-good",
2116                 "rails": []
2117               }
2118             ]
2119         )"_json;
2120         std::map<std::string, std::string> variables{{"bus", "7"},
2121                                                      {"address", "70"}};
2122         MockServices services{};
2123         parsePowerSequencerArray(element, variables, services);
2124         ADD_FAILURE() << "Should not have reached this line.";
2125     }
2126     catch (const std::invalid_argument& e)
2127     {
2128         EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
2129     }
2130 }
2131 
TEST(ConfigFileParserTests,ParseRail)2132 TEST(ConfigFileParserTests, ParseRail)
2133 {
2134     // Test where works: Only required properties specified
2135     {
2136         const json element = R"(
2137             {
2138                 "name": "VDD_CPU0"
2139             }
2140         )"_json;
2141         std::map<std::string, std::string> variables{};
2142         auto rail = parseRail(element, variables);
2143         EXPECT_EQ(rail->getName(), "VDD_CPU0");
2144         EXPECT_FALSE(rail->getPresence().has_value());
2145         EXPECT_FALSE(rail->getPage().has_value());
2146         EXPECT_FALSE(rail->isPowerSupplyRail());
2147         EXPECT_FALSE(rail->getCheckStatusVout());
2148         EXPECT_FALSE(rail->getCompareVoltageToLimit());
2149         EXPECT_FALSE(rail->getGPIO().has_value());
2150     }
2151 
2152     // Test where works: All properties specified
2153     {
2154         const json element = R"(
2155             {
2156                 "name": "12.0VB",
2157                 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply1",
2158                 "page": 11,
2159                 "is_power_supply_rail": true,
2160                 "check_status_vout": true,
2161                 "compare_voltage_to_limit": true,
2162                 "gpio": { "line": 60, "active_low": true }
2163             }
2164         )"_json;
2165         std::map<std::string, std::string> variables{};
2166         auto rail = parseRail(element, variables);
2167         EXPECT_EQ(rail->getName(), "12.0VB");
2168         EXPECT_TRUE(rail->getPresence().has_value());
2169         EXPECT_EQ(rail->getPresence().value(),
2170                   "/xyz/openbmc_project/inventory/system/chassis/powersupply1");
2171         EXPECT_TRUE(rail->getPage().has_value());
2172         EXPECT_EQ(rail->getPage().value(), 11);
2173         EXPECT_TRUE(rail->isPowerSupplyRail());
2174         EXPECT_TRUE(rail->getCheckStatusVout());
2175         EXPECT_TRUE(rail->getCompareVoltageToLimit());
2176         EXPECT_TRUE(rail->getGPIO().has_value());
2177         EXPECT_EQ(rail->getGPIO().value().line, 60);
2178         EXPECT_TRUE(rail->getGPIO().value().activeLow);
2179     }
2180 
2181     // Test where works: Variables specified
2182     {
2183         const json element = R"(
2184             {
2185                 "name": "${name}",
2186                 "presence": "${presence}",
2187                 "page": "${page}",
2188                 "is_power_supply_rail": "${is_power_supply_rail}",
2189                 "check_status_vout": "${check_status_vout}",
2190                 "compare_voltage_to_limit": "${compare_voltage_to_limit}",
2191                 "gpio": { "line": "${line}", "active_low": true }
2192             }
2193         )"_json;
2194         std::map<std::string, std::string> variables{
2195             {"name", "vdd"},
2196             {"presence",
2197              "/xyz/openbmc_project/inventory/system/chassis/powersupply2"},
2198             {"page", "9"},
2199             {"is_power_supply_rail", "true"},
2200             {"check_status_vout", "false"},
2201             {"compare_voltage_to_limit", "true"},
2202             {"line", "72"}};
2203         auto rail = parseRail(element, variables);
2204         EXPECT_EQ(rail->getName(), "vdd");
2205         EXPECT_TRUE(rail->getPresence().has_value());
2206         EXPECT_EQ(rail->getPresence().value(),
2207                   "/xyz/openbmc_project/inventory/system/chassis/powersupply2");
2208         EXPECT_TRUE(rail->getPage().has_value());
2209         EXPECT_EQ(rail->getPage().value(), 9);
2210         EXPECT_TRUE(rail->isPowerSupplyRail());
2211         EXPECT_FALSE(rail->getCheckStatusVout());
2212         EXPECT_TRUE(rail->getCompareVoltageToLimit());
2213         EXPECT_TRUE(rail->getGPIO().has_value());
2214         EXPECT_EQ(rail->getGPIO().value().line, 72);
2215         EXPECT_TRUE(rail->getGPIO().value().activeLow);
2216     }
2217 
2218     // Test where fails: Element is not an object
2219     try
2220     {
2221         const json element = R"( [ "vdda", "vddb" ] )"_json;
2222         std::map<std::string, std::string> variables{};
2223         parseRail(element, variables);
2224         ADD_FAILURE() << "Should not have reached this line.";
2225     }
2226     catch (const std::invalid_argument& e)
2227     {
2228         EXPECT_STREQ(e.what(), "Element is not an object");
2229     }
2230 
2231     // Test where fails: Required name property not specified
2232     try
2233     {
2234         const json element = R"(
2235             {
2236                 "page": 11
2237             }
2238         )"_json;
2239         std::map<std::string, std::string> variables{};
2240         parseRail(element, variables);
2241         ADD_FAILURE() << "Should not have reached this line.";
2242     }
2243     catch (const std::invalid_argument& e)
2244     {
2245         EXPECT_STREQ(e.what(), "Required property missing: name");
2246     }
2247 
2248     // Test where fails: name value is invalid
2249     try
2250     {
2251         const json element = R"(
2252             {
2253                 "name": 31,
2254                 "page": 11
2255             }
2256         )"_json;
2257         std::map<std::string, std::string> variables{};
2258         parseRail(element, variables);
2259         ADD_FAILURE() << "Should not have reached this line.";
2260     }
2261     catch (const std::invalid_argument& e)
2262     {
2263         EXPECT_STREQ(e.what(), "Element is not a string");
2264     }
2265 
2266     // Test where fails: presence value is invalid
2267     try
2268     {
2269         const json element = R"(
2270             {
2271                 "name": "VCS_CPU1",
2272                 "presence": false
2273             }
2274         )"_json;
2275         std::map<std::string, std::string> variables{};
2276         parseRail(element, variables);
2277         ADD_FAILURE() << "Should not have reached this line.";
2278     }
2279     catch (const std::invalid_argument& e)
2280     {
2281         EXPECT_STREQ(e.what(), "Element is not a string");
2282     }
2283 
2284     // Test where fails: page value is invalid
2285     try
2286     {
2287         const json element = R"(
2288             {
2289                 "name": "VCS_CPU1",
2290                 "page": 256
2291             }
2292         )"_json;
2293         std::map<std::string, std::string> variables{};
2294         parseRail(element, variables);
2295         ADD_FAILURE() << "Should not have reached this line.";
2296     }
2297     catch (const std::invalid_argument& e)
2298     {
2299         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
2300     }
2301 
2302     // Test where fails: is_power_supply_rail value is invalid
2303     try
2304     {
2305         const json element = R"(
2306             {
2307                 "name": "12.0VA",
2308                 "is_power_supply_rail": "true"
2309             }
2310         )"_json;
2311         std::map<std::string, std::string> variables{};
2312         parseRail(element, variables);
2313         ADD_FAILURE() << "Should not have reached this line.";
2314     }
2315     catch (const std::invalid_argument& e)
2316     {
2317         EXPECT_STREQ(e.what(), "Element is not a boolean");
2318     }
2319 
2320     // Test where fails: check_status_vout value is invalid
2321     try
2322     {
2323         const json element = R"(
2324             {
2325                 "name": "VCS_CPU1",
2326                 "check_status_vout": "false"
2327             }
2328         )"_json;
2329         std::map<std::string, std::string> variables{};
2330         parseRail(element, variables);
2331         ADD_FAILURE() << "Should not have reached this line.";
2332     }
2333     catch (const std::invalid_argument& e)
2334     {
2335         EXPECT_STREQ(e.what(), "Element is not a boolean");
2336     }
2337 
2338     // Test where fails: compare_voltage_to_limit value is invalid
2339     try
2340     {
2341         const json element = R"(
2342             {
2343                 "name": "VCS_CPU1",
2344                 "compare_voltage_to_limit": 23
2345             }
2346         )"_json;
2347         std::map<std::string, std::string> variables{};
2348         parseRail(element, variables);
2349         ADD_FAILURE() << "Should not have reached this line.";
2350     }
2351     catch (const std::invalid_argument& e)
2352     {
2353         EXPECT_STREQ(e.what(), "Element is not a boolean");
2354     }
2355 
2356     // Test where fails: gpio value is invalid
2357     try
2358     {
2359         const json element = R"(
2360             {
2361                 "name": "VCS_CPU1",
2362                 "gpio": 131
2363             }
2364         )"_json;
2365         std::map<std::string, std::string> variables{};
2366         parseRail(element, variables);
2367         ADD_FAILURE() << "Should not have reached this line.";
2368     }
2369     catch (const std::invalid_argument& e)
2370     {
2371         EXPECT_STREQ(e.what(), "Element is not an object");
2372     }
2373 
2374     // Test where fails: check_status_vout is true and page not specified
2375     try
2376     {
2377         const json element = R"(
2378             {
2379                 "name": "VCS_CPU1",
2380                 "check_status_vout": true
2381             }
2382         )"_json;
2383         std::map<std::string, std::string> variables{};
2384         parseRail(element, variables);
2385         ADD_FAILURE() << "Should not have reached this line.";
2386     }
2387     catch (const std::invalid_argument& e)
2388     {
2389         EXPECT_STREQ(e.what(), "Required property missing: page");
2390     }
2391 
2392     // Test where fails: compare_voltage_to_limit is true and page not
2393     // specified
2394     try
2395     {
2396         const json element = R"(
2397             {
2398                 "name": "VCS_CPU1",
2399                 "compare_voltage_to_limit": true
2400             }
2401         )"_json;
2402         std::map<std::string, std::string> variables{};
2403         parseRail(element, variables);
2404         ADD_FAILURE() << "Should not have reached this line.";
2405     }
2406     catch (const std::invalid_argument& e)
2407     {
2408         EXPECT_STREQ(e.what(), "Required property missing: page");
2409     }
2410 
2411     // Test where fails: Invalid property specified
2412     try
2413     {
2414         const json element = R"(
2415             {
2416                 "name": "VCS_CPU1",
2417                 "foo": "bar"
2418             }
2419         )"_json;
2420         std::map<std::string, std::string> variables{};
2421         parseRail(element, variables);
2422         ADD_FAILURE() << "Should not have reached this line.";
2423     }
2424     catch (const std::invalid_argument& e)
2425     {
2426         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2427     }
2428 
2429     // Test where fails: Undefined variable specified
2430     try
2431     {
2432         const json element = R"(
2433             {
2434                 "name": "12.0VB",
2435                 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply${chassis}"
2436             }
2437         )"_json;
2438         std::map<std::string, std::string> variables{{"foo", "bar"}};
2439         parseRail(element, variables);
2440         ADD_FAILURE() << "Should not have reached this line.";
2441     }
2442     catch (const std::invalid_argument& e)
2443     {
2444         EXPECT_STREQ(e.what(), "Undefined variable: chassis");
2445     }
2446 }
2447 
TEST(ConfigFileParserTests,ParseRailArray)2448 TEST(ConfigFileParserTests, ParseRailArray)
2449 {
2450     // Test where works: Array is empty
2451     {
2452         const json element = R"(
2453             [
2454             ]
2455         )"_json;
2456         std::map<std::string, std::string> variables{};
2457         auto rails = parseRailArray(element, variables);
2458         EXPECT_EQ(rails.size(), 0);
2459     }
2460 
2461     // Test where works: Array is not empty
2462     {
2463         const json element = R"(
2464             [
2465                 { "name": "VDD_CPU0" },
2466                 { "name": "VCS_CPU1" }
2467             ]
2468         )"_json;
2469         std::map<std::string, std::string> variables{};
2470         auto rails = parseRailArray(element, variables);
2471         EXPECT_EQ(rails.size(), 2);
2472         EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
2473         EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
2474     }
2475 
2476     // Test where works: Variables specified
2477     {
2478         const json element = R"(
2479             [
2480                 { "name": "${rail1}" },
2481                 { "name": "${rail2}" },
2482                 { "name": "${rail3}" }
2483             ]
2484         )"_json;
2485         std::map<std::string, std::string> variables{
2486             {"rail1", "foo"}, {"rail2", "bar"}, {"rail3", "baz"}};
2487         auto rails = parseRailArray(element, variables);
2488         EXPECT_EQ(rails.size(), 3);
2489         EXPECT_EQ(rails[0]->getName(), "foo");
2490         EXPECT_EQ(rails[1]->getName(), "bar");
2491         EXPECT_EQ(rails[2]->getName(), "baz");
2492     }
2493 
2494     // Test where fails: Element is not an array
2495     try
2496     {
2497         const json element = R"(
2498             {
2499                 "foo": "bar"
2500             }
2501         )"_json;
2502         std::map<std::string, std::string> variables{};
2503         parseRailArray(element, variables);
2504         ADD_FAILURE() << "Should not have reached this line.";
2505     }
2506     catch (const std::invalid_argument& e)
2507     {
2508         EXPECT_STREQ(e.what(), "Element is not an array");
2509     }
2510 
2511     // Test where fails: Element within array is invalid
2512     try
2513     {
2514         const json element = R"(
2515             [
2516                 { "name": "VDD_CPU0" },
2517                 23
2518             ]
2519         )"_json;
2520         std::map<std::string, std::string> variables{};
2521         parseRailArray(element, variables);
2522         ADD_FAILURE() << "Should not have reached this line.";
2523     }
2524     catch (const std::invalid_argument& e)
2525     {
2526         EXPECT_STREQ(e.what(), "Element is not an object");
2527     }
2528 
2529     // Test where fails: Invalid variable value specified
2530     try
2531     {
2532         const json element = R"(
2533             [
2534                 { "name": "VDD_CPU0", "page": "${page1}" },
2535                 { "name": "VCS_CPU1", "page": "${page2}" }
2536             ]
2537         )"_json;
2538         std::map<std::string, std::string> variables{{"page1", "11"},
2539                                                      {"page2", "-1"}};
2540         parseRailArray(element, variables);
2541         ADD_FAILURE() << "Should not have reached this line.";
2542     }
2543     catch (const std::invalid_argument& e)
2544     {
2545         EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
2546     }
2547 }
2548 
TEST(ConfigFileParserTests,ParseRoot)2549 TEST(ConfigFileParserTests, ParseRoot)
2550 {
2551     // Test where works: Only required properties specified
2552     {
2553         const json element = R"(
2554             {
2555               "chassis": [
2556                 {
2557                   "number": 1,
2558                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2559                   "power_sequencers": []
2560                 },
2561                 {
2562                   "number": 2,
2563                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
2564                   "power_sequencers": []
2565                 }
2566               ]
2567             }
2568         )"_json;
2569         MockServices services{};
2570         auto chassis = parseRoot(element, services);
2571         EXPECT_EQ(chassis.size(), 2);
2572         EXPECT_EQ(chassis[0]->getNumber(), 1);
2573         EXPECT_EQ(chassis[0]->getInventoryPath(),
2574                   "/xyz/openbmc_project/inventory/system/chassis1");
2575         EXPECT_EQ(chassis[1]->getNumber(), 2);
2576         EXPECT_EQ(chassis[1]->getInventoryPath(),
2577                   "/xyz/openbmc_project/inventory/system/chassis2");
2578     }
2579 
2580     // Test where works: All properties specified
2581     {
2582         const json element = R"(
2583             {
2584               "comments": [ "Config file for a FooBar one-chassis system" ],
2585               "chassis_templates": [
2586                 {
2587                   "id": "foo_chassis",
2588                   "number": "${chassis_number}",
2589                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
2590                   "power_sequencers": []
2591                 },
2592                 {
2593                   "id": "bar_chassis",
2594                   "number": "${chassis_number}",
2595                   "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${chassis_number}",
2596                   "power_sequencers": []
2597                 }
2598               ],
2599               "chassis": [
2600                 {
2601                   "template_id": "foo_chassis",
2602                   "template_variable_values": { "chassis_number": "2" }
2603                 },
2604                 {
2605                   "template_id": "bar_chassis",
2606                   "template_variable_values": { "chassis_number": "3" }
2607                 }
2608               ]
2609             }
2610         )"_json;
2611         MockServices services{};
2612         auto chassis = parseRoot(element, services);
2613         EXPECT_EQ(chassis.size(), 2);
2614         EXPECT_EQ(chassis[0]->getNumber(), 2);
2615         EXPECT_EQ(chassis[0]->getInventoryPath(),
2616                   "/xyz/openbmc_project/inventory/system/chassis2");
2617         EXPECT_EQ(chassis[1]->getNumber(), 3);
2618         EXPECT_EQ(chassis[1]->getInventoryPath(),
2619                   "/xyz/openbmc_project/inventory/system/bar_chassis3");
2620     }
2621 
2622     // Test where fails: Element is not an object
2623     try
2624     {
2625         const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json;
2626         MockServices services{};
2627         parseRoot(element, services);
2628         ADD_FAILURE() << "Should not have reached this line.";
2629     }
2630     catch (const std::invalid_argument& e)
2631     {
2632         EXPECT_STREQ(e.what(), "Element is not an object");
2633     }
2634 
2635     // Test where fails: Required chassis property not specified
2636     try
2637     {
2638         const json element = R"(
2639             {
2640               "comments": [ "Config file for a FooBar one-chassis system" ],
2641               "chassis_templates": [
2642                 {
2643                   "id": "foo_chassis",
2644                   "number": "${chassis_number}",
2645                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
2646                   "power_sequencers": []
2647                 }
2648               ]
2649             }
2650         )"_json;
2651         MockServices services{};
2652         parseRoot(element, services);
2653         ADD_FAILURE() << "Should not have reached this line.";
2654     }
2655     catch (const std::invalid_argument& e)
2656     {
2657         EXPECT_STREQ(e.what(), "Required property missing: chassis");
2658     }
2659 
2660     // Test where fails: chassis_templates value is invalid
2661     try
2662     {
2663         const json element = R"(
2664             {
2665               "chassis_templates": true,
2666               "chassis": [
2667                 {
2668                   "number": 1,
2669                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2670                   "power_sequencers": []
2671                 }
2672               ]
2673             }
2674         )"_json;
2675         MockServices services{};
2676         parseRoot(element, services);
2677         ADD_FAILURE() << "Should not have reached this line.";
2678     }
2679     catch (const std::invalid_argument& e)
2680     {
2681         EXPECT_STREQ(e.what(), "Element is not an array");
2682     }
2683 
2684     // Test where fails: chassis value is invalid
2685     try
2686     {
2687         const json element = R"(
2688             {
2689               "chassis": [
2690                 {
2691                   "number": "one",
2692                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2693                   "power_sequencers": []
2694                 }
2695               ]
2696             }
2697         )"_json;
2698         MockServices services{};
2699         parseRoot(element, services);
2700         ADD_FAILURE() << "Should not have reached this line.";
2701     }
2702     catch (const std::invalid_argument& e)
2703     {
2704         EXPECT_STREQ(e.what(), "Element is not an integer");
2705     }
2706 
2707     // Test where fails: Invalid property specified
2708     try
2709     {
2710         const json element = R"(
2711             {
2712               "chassis": [
2713                 {
2714                   "number": 1,
2715                   "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
2716                   "power_sequencers": []
2717                 }
2718               ],
2719               "foo": true
2720             }
2721         )"_json;
2722         MockServices services{};
2723         parseRoot(element, services);
2724         ADD_FAILURE() << "Should not have reached this line.";
2725     }
2726     catch (const std::invalid_argument& e)
2727     {
2728         EXPECT_STREQ(e.what(), "Element contains an invalid property");
2729     }
2730 }
2731 
TEST(ConfigFileParserTests,ParseVariables)2732 TEST(ConfigFileParserTests, ParseVariables)
2733 {
2734     // Test where works: No variables specified
2735     {
2736         const json element = R"(
2737             {
2738             }
2739         )"_json;
2740         auto variables = parseVariables(element);
2741         EXPECT_EQ(variables.size(), 0);
2742     }
2743 
2744     // Test where works: Variables specified
2745     {
2746         const json element = R"(
2747             {
2748               "chassis_number": "2",
2749               "bus_number": "13"
2750             }
2751         )"_json;
2752         auto variables = parseVariables(element);
2753         EXPECT_EQ(variables.size(), 2);
2754         EXPECT_EQ(variables["chassis_number"], "2");
2755         EXPECT_EQ(variables["bus_number"], "13");
2756     }
2757 
2758     // Test where fails: Element is not an object
2759     try
2760     {
2761         const json element = R"(
2762             [
2763               "chassis_number", "2",
2764               "bus_number", "13"
2765             ]
2766         )"_json;
2767         parseVariables(element);
2768         ADD_FAILURE() << "Should not have reached this line.";
2769     }
2770     catch (const std::invalid_argument& e)
2771     {
2772         EXPECT_STREQ(e.what(), "Element is not an object");
2773     }
2774 
2775     // Test where fails: Key is not a string
2776     try
2777     {
2778         const json element = R"(
2779             {
2780               chassis_number: "2",
2781               "bus_number": "13"
2782             }
2783         )"_json;
2784         parseVariables(element);
2785         ADD_FAILURE() << "Should not have reached this line.";
2786     }
2787     catch (const json::parse_error& e)
2788     {}
2789 
2790     // Test where fails: Value is not a string
2791     try
2792     {
2793         const json element = R"(
2794             {
2795               "chassis_number": "2",
2796               "bus_number": 13
2797             }
2798         )"_json;
2799         parseVariables(element);
2800         ADD_FAILURE() << "Should not have reached this line.";
2801     }
2802     catch (const std::invalid_argument& e)
2803     {
2804         EXPECT_STREQ(e.what(), "Element is not a string");
2805     }
2806 
2807     // Test where fails: Key is an empty string
2808     try
2809     {
2810         const json element = R"(
2811             {
2812               "chassis_number": "2",
2813               "": "13"
2814             }
2815         )"_json;
2816         parseVariables(element);
2817         ADD_FAILURE() << "Should not have reached this line.";
2818     }
2819     catch (const std::invalid_argument& e)
2820     {
2821         EXPECT_STREQ(e.what(), "Element contains an empty string");
2822     }
2823 
2824     // Test where fails: Value is an empty string
2825     try
2826     {
2827         const json element = R"(
2828             {
2829               "chassis_number": "",
2830               "bus_number": "13"
2831             }
2832         )"_json;
2833         parseVariables(element);
2834         ADD_FAILURE() << "Should not have reached this line.";
2835     }
2836     catch (const std::invalid_argument& e)
2837     {
2838         EXPECT_STREQ(e.what(), "Element contains an empty string");
2839     }
2840 }
2841