blob: bfc96808f5df00b3c8e3883e3301810c06593054 [file] [log] [blame]
Shawn McCarney6a957f62024-01-10 16:15:19 -06001/**
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 "config_file_parser.hpp"
17#include "config_file_parser_error.hpp"
18#include "rail.hpp"
19#include "temporary_file.hpp"
20
21#include <sys/stat.h> // for chmod()
22
23#include <nlohmann/json.hpp>
24
25#include <cstdint>
26#include <exception>
27#include <filesystem>
28#include <fstream>
29#include <memory>
30#include <optional>
31#include <stdexcept>
32#include <string>
33#include <vector>
34
35#include <gtest/gtest.h>
36
37using namespace phosphor::power::sequencer;
38using namespace phosphor::power::sequencer::config_file_parser;
39using namespace phosphor::power::sequencer::config_file_parser::internal;
40using json = nlohmann::json;
41using TemporaryFile = phosphor::power::util::TemporaryFile;
42
43void writeConfigFile(const std::filesystem::path& pathName,
44 const std::string& contents)
45{
46 std::ofstream file{pathName};
47 file << contents;
48}
49
50void writeConfigFile(const std::filesystem::path& pathName,
51 const json& contents)
52{
53 std::ofstream file{pathName};
54 file << contents;
55}
56
57TEST(ConfigFileParserTests, Parse)
58{
59 // Test where works
60 {
61 const json configFileContents = R"(
62 {
63 "rails": [
64 {
65 "name": "VDD_CPU0",
66 "page": 11,
67 "check_status_vout": true
68 },
69 {
70 "name": "VCS_CPU1",
71 "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
72 "gpio": { "line": 60 }
73 }
74 ]
75 }
76 )"_json;
77
78 TemporaryFile configFile;
79 std::filesystem::path pathName{configFile.getPath()};
80 writeConfigFile(pathName, configFileContents);
81
82 std::vector<std::unique_ptr<Rail>> rails = parse(pathName);
83
84 EXPECT_EQ(rails.size(), 2);
85 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
86 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
87 }
88
89 // Test where fails: File does not exist
90 {
91 std::filesystem::path pathName{"/tmp/non_existent_file"};
92 EXPECT_THROW(parse(pathName), ConfigFileParserError);
93 }
94
95 // Test where fails: File is not readable
96 {
97 const json configFileContents = R"(
98 {
99 "rails": [
100 {
101 "name": "VDD_CPU0"
102 }
103 ]
104 }
105 )"_json;
106
107 TemporaryFile configFile;
108 std::filesystem::path pathName{configFile.getPath()};
109 writeConfigFile(pathName, configFileContents);
110
111 chmod(pathName.c_str(), 0222);
112 EXPECT_THROW(parse(pathName), ConfigFileParserError);
113 }
114
115 // Test where fails: File is not valid JSON
116 {
117 const std::string configFileContents = "] foo [";
118
119 TemporaryFile configFile;
120 std::filesystem::path pathName{configFile.getPath()};
121 writeConfigFile(pathName, configFileContents);
122
123 EXPECT_THROW(parse(pathName), ConfigFileParserError);
124 }
125
126 // Test where fails: JSON does not conform to config file format
127 {
128 const json configFileContents = R"( [ "foo", "bar" ] )"_json;
129
130 TemporaryFile configFile;
131 std::filesystem::path pathName{configFile.getPath()};
132 writeConfigFile(pathName, configFileContents);
133
134 EXPECT_THROW(parse(pathName), ConfigFileParserError);
135 }
136}
137
138TEST(ConfigFileParserTests, GetRequiredProperty)
139{
140 // Test where property exists
141 {
142 const json element = R"( { "name": "VDD_CPU0" } )"_json;
143 const json& propertyElement = getRequiredProperty(element, "name");
144 EXPECT_EQ(propertyElement.get<std::string>(), "VDD_CPU0");
145 }
146
147 // Test where property does not exist
148 try
149 {
150 const json element = R"( { "foo": 23 } )"_json;
151 getRequiredProperty(element, "name");
152 ADD_FAILURE() << "Should not have reached this line.";
153 }
154 catch (const std::invalid_argument& e)
155 {
156 EXPECT_STREQ(e.what(), "Required property missing: name");
157 }
158}
159
160TEST(ConfigFileParserTests, ParseBoolean)
161{
162 // Test where works: true
163 {
164 const json element = R"( true )"_json;
165 bool value = parseBoolean(element);
166 EXPECT_EQ(value, true);
167 }
168
169 // Test where works: false
170 {
171 const json element = R"( false )"_json;
172 bool value = parseBoolean(element);
173 EXPECT_EQ(value, false);
174 }
175
176 // Test where fails: Element is not a boolean
177 try
178 {
179 const json element = R"( 1 )"_json;
180 parseBoolean(element);
181 ADD_FAILURE() << "Should not have reached this line.";
182 }
183 catch (const std::invalid_argument& e)
184 {
185 EXPECT_STREQ(e.what(), "Element is not a boolean");
186 }
187}
188
189TEST(ConfigFileParserTests, ParseGPIO)
190{
191 // Test where works: Only required properties specified
192 {
193 const json element = R"(
194 {
195 "line": 60
196 }
197 )"_json;
198 GPIO gpio = parseGPIO(element);
199 EXPECT_EQ(gpio.line, 60);
200 EXPECT_FALSE(gpio.activeLow);
201 }
202
203 // Test where works: All properties specified
204 {
205 const json element = R"(
206 {
207 "line": 131,
208 "active_low": true
209 }
210 )"_json;
211 GPIO gpio = parseGPIO(element);
212 EXPECT_EQ(gpio.line, 131);
213 EXPECT_TRUE(gpio.activeLow);
214 }
215
216 // Test where fails: Element is not an object
217 try
218 {
219 const json element = R"( [ "vdda", "vddb" ] )"_json;
220 parseGPIO(element);
221 ADD_FAILURE() << "Should not have reached this line.";
222 }
223 catch (const std::invalid_argument& e)
224 {
225 EXPECT_STREQ(e.what(), "Element is not an object");
226 }
227
228 // Test where fails: Required line property not specified
229 try
230 {
231 const json element = R"(
232 {
233 "active_low": true
234 }
235 )"_json;
236 parseGPIO(element);
237 ADD_FAILURE() << "Should not have reached this line.";
238 }
239 catch (const std::invalid_argument& e)
240 {
241 EXPECT_STREQ(e.what(), "Required property missing: line");
242 }
243
244 // Test where fails: line value is invalid
245 try
246 {
247 const json element = R"(
248 {
249 "line": -131,
250 "active_low": true
251 }
252 )"_json;
253 parseGPIO(element);
254 ADD_FAILURE() << "Should not have reached this line.";
255 }
256 catch (const std::invalid_argument& e)
257 {
258 EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
259 }
260
261 // Test where fails: active_low value is invalid
262 try
263 {
264 const json element = R"(
265 {
266 "line": 131,
267 "active_low": "true"
268 }
269 )"_json;
270 parseGPIO(element);
271 ADD_FAILURE() << "Should not have reached this line.";
272 }
273 catch (const std::invalid_argument& e)
274 {
275 EXPECT_STREQ(e.what(), "Element is not a boolean");
276 }
277
278 // Test where fails: Invalid property specified
279 try
280 {
281 const json element = R"(
282 {
283 "line": 131,
284 "foo": "bar"
285 }
286 )"_json;
287 parseGPIO(element);
288 ADD_FAILURE() << "Should not have reached this line.";
289 }
290 catch (const std::invalid_argument& e)
291 {
292 EXPECT_STREQ(e.what(), "Element contains an invalid property");
293 }
294}
295
296TEST(ConfigFileParserTests, ParseRail)
297{
298 // Test where works: Only required properties specified
299 {
300 const json element = R"(
301 {
302 "name": "VDD_CPU0"
303 }
304 )"_json;
305 std::unique_ptr<Rail> rail = parseRail(element);
306 EXPECT_EQ(rail->getName(), "VDD_CPU0");
307 EXPECT_FALSE(rail->getPresence().has_value());
308 EXPECT_FALSE(rail->getPage().has_value());
309 EXPECT_FALSE(rail->getCheckStatusVout());
310 EXPECT_FALSE(rail->getCompareVoltageToLimits());
311 EXPECT_FALSE(rail->getGPIO().has_value());
312 }
313
314 // Test where works: All properties specified
315 {
316 const json element = R"(
317 {
318 "name": "VCS_CPU1",
319 "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
320 "page": 11,
321 "check_status_vout": true,
322 "compare_voltage_to_limits": true,
323 "gpio": { "line": 60, "active_low": true }
324 }
325 )"_json;
326 std::unique_ptr<Rail> rail = parseRail(element);
327 EXPECT_EQ(rail->getName(), "VCS_CPU1");
328 EXPECT_TRUE(rail->getPresence().has_value());
329 EXPECT_EQ(
330 rail->getPresence().value(),
331 "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1");
332 EXPECT_TRUE(rail->getPage().has_value());
333 EXPECT_EQ(rail->getPage().value(), 11);
334 EXPECT_TRUE(rail->getCheckStatusVout());
335 EXPECT_TRUE(rail->getCompareVoltageToLimits());
336 EXPECT_TRUE(rail->getGPIO().has_value());
337 EXPECT_EQ(rail->getGPIO().value().line, 60);
338 EXPECT_TRUE(rail->getGPIO().value().activeLow);
339 }
340
341 // Test where fails: Element is not an object
342 try
343 {
344 const json element = R"( [ "vdda", "vddb" ] )"_json;
345 parseRail(element);
346 ADD_FAILURE() << "Should not have reached this line.";
347 }
348 catch (const std::invalid_argument& e)
349 {
350 EXPECT_STREQ(e.what(), "Element is not an object");
351 }
352
353 // Test where fails: Required name property not specified
354 try
355 {
356 const json element = R"(
357 {
358 "page": 11
359 }
360 )"_json;
361 parseRail(element);
362 ADD_FAILURE() << "Should not have reached this line.";
363 }
364 catch (const std::invalid_argument& e)
365 {
366 EXPECT_STREQ(e.what(), "Required property missing: name");
367 }
368
369 // Test where fails: name value is invalid
370 try
371 {
372 const json element = R"(
373 {
374 "name": 31,
375 "page": 11
376 }
377 )"_json;
378 parseRail(element);
379 ADD_FAILURE() << "Should not have reached this line.";
380 }
381 catch (const std::invalid_argument& e)
382 {
383 EXPECT_STREQ(e.what(), "Element is not a string");
384 }
385
386 // Test where fails: presence value is invalid
387 try
388 {
389 const json element = R"(
390 {
391 "name": "VCS_CPU1",
392 "presence": false
393 }
394 )"_json;
395 parseRail(element);
396 ADD_FAILURE() << "Should not have reached this line.";
397 }
398 catch (const std::invalid_argument& e)
399 {
400 EXPECT_STREQ(e.what(), "Element is not a string");
401 }
402
403 // Test where fails: page value is invalid
404 try
405 {
406 const json element = R"(
407 {
408 "name": "VCS_CPU1",
409 "page": 256
410 }
411 )"_json;
412 parseRail(element);
413 ADD_FAILURE() << "Should not have reached this line.";
414 }
415 catch (const std::invalid_argument& e)
416 {
417 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
418 }
419
420 // Test where fails: check_status_vout value is invalid
421 try
422 {
423 const json element = R"(
424 {
425 "name": "VCS_CPU1",
426 "check_status_vout": "false"
427 }
428 )"_json;
429 parseRail(element);
430 ADD_FAILURE() << "Should not have reached this line.";
431 }
432 catch (const std::invalid_argument& e)
433 {
434 EXPECT_STREQ(e.what(), "Element is not a boolean");
435 }
436
437 // Test where fails: compare_voltage_to_limits value is invalid
438 try
439 {
440 const json element = R"(
441 {
442 "name": "VCS_CPU1",
443 "compare_voltage_to_limits": 23
444 }
445 )"_json;
446 parseRail(element);
447 ADD_FAILURE() << "Should not have reached this line.";
448 }
449 catch (const std::invalid_argument& e)
450 {
451 EXPECT_STREQ(e.what(), "Element is not a boolean");
452 }
453
454 // Test where fails: gpio value is invalid
455 try
456 {
457 const json element = R"(
458 {
459 "name": "VCS_CPU1",
460 "gpio": 131
461 }
462 )"_json;
463 parseRail(element);
464 ADD_FAILURE() << "Should not have reached this line.";
465 }
466 catch (const std::invalid_argument& e)
467 {
468 EXPECT_STREQ(e.what(), "Element is not an object");
469 }
470
471 // Test where fails: check_status_vout is true and page not specified
472 try
473 {
474 const json element = R"(
475 {
476 "name": "VCS_CPU1",
477 "check_status_vout": true
478 }
479 )"_json;
480 parseRail(element);
481 ADD_FAILURE() << "Should not have reached this line.";
482 }
483 catch (const std::invalid_argument& e)
484 {
485 EXPECT_STREQ(e.what(), "Required property missing: page");
486 }
487
488 // Test where fails: compare_voltage_to_limits is true and page not
489 // specified
490 try
491 {
492 const json element = R"(
493 {
494 "name": "VCS_CPU1",
495 "compare_voltage_to_limits": true
496 }
497 )"_json;
498 parseRail(element);
499 ADD_FAILURE() << "Should not have reached this line.";
500 }
501 catch (const std::invalid_argument& e)
502 {
503 EXPECT_STREQ(e.what(), "Required property missing: page");
504 }
505
506 // Test where fails: Invalid property specified
507 try
508 {
509 const json element = R"(
510 {
511 "name": "VCS_CPU1",
512 "foo": "bar"
513 }
514 )"_json;
515 parseRail(element);
516 ADD_FAILURE() << "Should not have reached this line.";
517 }
518 catch (const std::invalid_argument& e)
519 {
520 EXPECT_STREQ(e.what(), "Element contains an invalid property");
521 }
522}
523
524TEST(ConfigFileParserTests, ParseRailArray)
525{
526 // Test where works: Array is empty
527 {
528 const json element = R"(
529 [
530 ]
531 )"_json;
532 std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
533 EXPECT_EQ(rails.size(), 0);
534 }
535
536 // Test where works: Array is not empty
537 {
538 const json element = R"(
539 [
540 { "name": "VDD_CPU0" },
541 { "name": "VCS_CPU1" }
542 ]
543 )"_json;
544 std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
545 EXPECT_EQ(rails.size(), 2);
546 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
547 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
548 }
549
550 // Test where fails: Element is not an array
551 try
552 {
553 const json element = R"(
554 {
555 "foo": "bar"
556 }
557 )"_json;
558 parseRailArray(element);
559 ADD_FAILURE() << "Should not have reached this line.";
560 }
561 catch (const std::invalid_argument& e)
562 {
563 EXPECT_STREQ(e.what(), "Element is not an array");
564 }
565
566 // Test where fails: Element within array is invalid
567 try
568 {
569 const json element = R"(
570 [
571 { "name": "VDD_CPU0" },
572 23
573 ]
574 )"_json;
575 parseRailArray(element);
576 ADD_FAILURE() << "Should not have reached this line.";
577 }
578 catch (const std::invalid_argument& e)
579 {
580 EXPECT_STREQ(e.what(), "Element is not an object");
581 }
582}
583
584TEST(ConfigFileParserTests, ParseRoot)
585{
586 // Test where works
587 {
588 const json element = R"(
589 {
590 "rails": [
591 {
592 "name": "VDD_CPU0",
593 "page": 11,
594 "check_status_vout": true
595 },
596 {
597 "name": "VCS_CPU1",
598 "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
599 "gpio": { "line": 60 }
600 }
601 ]
602 }
603 )"_json;
604 std::vector<std::unique_ptr<Rail>> rails = parseRoot(element);
605 EXPECT_EQ(rails.size(), 2);
606 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
607 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
608 }
609
610 // Test where fails: Element is not an object
611 try
612 {
613 const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json;
614 parseRoot(element);
615 ADD_FAILURE() << "Should not have reached this line.";
616 }
617 catch (const std::invalid_argument& e)
618 {
619 EXPECT_STREQ(e.what(), "Element is not an object");
620 }
621
622 // Test where fails: Required rails property not specified
623 try
624 {
625 const json element = R"(
626 {
627 }
628 )"_json;
629 parseRoot(element);
630 ADD_FAILURE() << "Should not have reached this line.";
631 }
632 catch (const std::invalid_argument& e)
633 {
634 EXPECT_STREQ(e.what(), "Required property missing: rails");
635 }
636
637 // Test where fails: rails value is invalid
638 try
639 {
640 const json element = R"(
641 {
642 "rails": 31
643 }
644 )"_json;
645 parseRoot(element);
646 ADD_FAILURE() << "Should not have reached this line.";
647 }
648 catch (const std::invalid_argument& e)
649 {
650 EXPECT_STREQ(e.what(), "Element is not an array");
651 }
652
653 // Test where fails: Invalid property specified
654 try
655 {
656 const json element = R"(
657 {
658 "rails": [
659 {
660 "name": "VDD_CPU0",
661 "page": 11,
662 "check_status_vout": true
663 }
664 ],
665 "foo": true
666 }
667 )"_json;
668 parseRoot(element);
669 ADD_FAILURE() << "Should not have reached this line.";
670 }
671 catch (const std::invalid_argument& e)
672 {
673 EXPECT_STREQ(e.what(), "Element contains an invalid property");
674 }
675}
676
677TEST(ConfigFileParserTests, ParseString)
678{
679 // Test where works: Empty string
680 {
681 const json element = "";
682 std::string value = parseString(element, true);
683 EXPECT_EQ(value, "");
684 }
685
686 // Test where works: Non-empty string
687 {
688 const json element = "vdd_cpu1";
689 std::string value = parseString(element, false);
690 EXPECT_EQ(value, "vdd_cpu1");
691 }
692
693 // Test where fails: Element is not a string
694 try
695 {
696 const json element = R"( { "foo": "bar" } )"_json;
697 parseString(element);
698 ADD_FAILURE() << "Should not have reached this line.";
699 }
700 catch (const std::invalid_argument& e)
701 {
702 EXPECT_STREQ(e.what(), "Element is not a string");
703 }
704
705 // Test where fails: Empty string
706 try
707 {
708 const json element = "";
709 parseString(element);
710 ADD_FAILURE() << "Should not have reached this line.";
711 }
712 catch (const std::invalid_argument& e)
713 {
714 EXPECT_STREQ(e.what(), "Element contains an empty string");
715 }
716}
717
718TEST(ConfigFileParserTests, ParseUint8)
719{
720 // Test where works: 0
721 {
722 const json element = R"( 0 )"_json;
723 uint8_t value = parseUint8(element);
724 EXPECT_EQ(value, 0);
725 }
726
727 // Test where works: UINT8_MAX
728 {
729 const json element = R"( 255 )"_json;
730 uint8_t value = parseUint8(element);
731 EXPECT_EQ(value, 255);
732 }
733
734 // Test where fails: Element is not an integer
735 try
736 {
737 const json element = R"( 1.03 )"_json;
738 parseUint8(element);
739 ADD_FAILURE() << "Should not have reached this line.";
740 }
741 catch (const std::invalid_argument& e)
742 {
743 EXPECT_STREQ(e.what(), "Element is not an integer");
744 }
745
746 // Test where fails: Value < 0
747 try
748 {
749 const json element = R"( -1 )"_json;
750 parseUint8(element);
751 ADD_FAILURE() << "Should not have reached this line.";
752 }
753 catch (const std::invalid_argument& e)
754 {
755 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
756 }
757
758 // Test where fails: Value > UINT8_MAX
759 try
760 {
761 const json element = R"( 256 )"_json;
762 parseUint8(element);
763 ADD_FAILURE() << "Should not have reached this line.";
764 }
765 catch (const std::invalid_argument& e)
766 {
767 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
768 }
769}
770
771TEST(ConfigFileParserTests, ParseUnsignedInteger)
772{
773 // Test where works: 1
774 {
775 const json element = R"( 1 )"_json;
776 unsigned int value = parseUnsignedInteger(element);
777 EXPECT_EQ(value, 1);
778 }
779
780 // Test where fails: Element is not an integer
781 try
782 {
783 const json element = R"( 1.5 )"_json;
784 parseUnsignedInteger(element);
785 ADD_FAILURE() << "Should not have reached this line.";
786 }
787 catch (const std::invalid_argument& e)
788 {
789 EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
790 }
791
792 // Test where fails: Value < 0
793 try
794 {
795 const json element = R"( -1 )"_json;
796 parseUnsignedInteger(element);
797 ADD_FAILURE() << "Should not have reached this line.";
798 }
799 catch (const std::invalid_argument& e)
800 {
801 EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
802 }
803}
804
805TEST(ConfigFileParserTests, VerifyIsArray)
806{
807 // Test where element is an array
808 {
809 const json element = R"( [ "foo", "bar" ] )"_json;
810 verifyIsArray(element);
811 }
812
813 // Test where element is not an array
814 try
815 {
816 const json element = R"( { "foo": "bar" } )"_json;
817 verifyIsArray(element);
818 ADD_FAILURE() << "Should not have reached this line.";
819 }
820 catch (const std::invalid_argument& e)
821 {
822 EXPECT_STREQ(e.what(), "Element is not an array");
823 }
824}
825
826TEST(ConfigFileParserTests, VerifyIsObject)
827{
828 // Test where element is an object
829 {
830 const json element = R"( { "foo": "bar" } )"_json;
831 verifyIsObject(element);
832 }
833
834 // Test where element is not an object
835 try
836 {
837 const json element = R"( [ "foo", "bar" ] )"_json;
838 verifyIsObject(element);
839 ADD_FAILURE() << "Should not have reached this line.";
840 }
841 catch (const std::invalid_argument& e)
842 {
843 EXPECT_STREQ(e.what(), "Element is not an object");
844 }
845}
846
847TEST(ConfigFileParserTests, VerifyPropertyCount)
848{
849 // Test where element has expected number of properties
850 {
851 const json element = R"(
852 {
853 "line": 131,
854 "active_low": true
855 }
856 )"_json;
857 verifyPropertyCount(element, 2);
858 }
859
860 // Test where element has unexpected number of properties
861 try
862 {
863 const json element = R"(
864 {
865 "line": 131,
866 "active_low": true,
867 "foo": 1.3
868 }
869 )"_json;
870 verifyPropertyCount(element, 2);
871 ADD_FAILURE() << "Should not have reached this line.";
872 }
873 catch (const std::invalid_argument& e)
874 {
875 EXPECT_STREQ(e.what(), "Element contains an invalid property");
876 }
877}