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 "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 37 using namespace phosphor::power::sequencer; 38 using namespace phosphor::power::sequencer::config_file_parser; 39 using namespace phosphor::power::sequencer::config_file_parser::internal; 40 using json = nlohmann::json; 41 using TemporaryFile = phosphor::power::util::TemporaryFile; 42 43 void writeConfigFile(const std::filesystem::path& pathName, 44 const std::string& contents) 45 { 46 std::ofstream file{pathName}; 47 file << contents; 48 } 49 50 void writeConfigFile(const std::filesystem::path& pathName, 51 const json& contents) 52 { 53 std::ofstream file{pathName}; 54 file << contents; 55 } 56 57 TEST(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 138 TEST(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 160 TEST(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 189 TEST(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 296 TEST(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 524 TEST(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 584 TEST(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 677 TEST(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 718 TEST(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 771 TEST(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 805 TEST(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 826 TEST(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 847 TEST(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 } 878