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 #include "temporary_subdirectory.hpp" 21 22 #include <sys/stat.h> // for chmod() 23 24 #include <nlohmann/json.hpp> 25 26 #include <cstdint> 27 #include <exception> 28 #include <filesystem> 29 #include <fstream> 30 #include <memory> 31 #include <optional> 32 #include <stdexcept> 33 #include <string> 34 #include <vector> 35 36 #include <gtest/gtest.h> 37 38 using namespace phosphor::power::sequencer; 39 using namespace phosphor::power::sequencer::config_file_parser; 40 using namespace phosphor::power::sequencer::config_file_parser::internal; 41 using namespace phosphor::power::util; 42 using json = nlohmann::json; 43 namespace fs = std::filesystem; 44 45 void writeConfigFile(const fs::path& pathName, const std::string& contents) 46 { 47 std::ofstream file{pathName}; 48 file << contents; 49 } 50 51 void writeConfigFile(const fs::path& pathName, const json& contents) 52 { 53 std::ofstream file{pathName}; 54 file << contents; 55 } 56 57 TEST(ConfigFileParserTests, Find) 58 { 59 std::vector<std::string> compatibleSystemTypes{ 60 "com.acme.Hardware.Chassis.Model.MegaServer4CPU", 61 "com.acme.Hardware.Chassis.Model.MegaServer", 62 "com.acme.Hardware.Chassis.Model.Server"}; 63 64 // Test where works: Fully qualified system type: First in list 65 { 66 TemporarySubDirectory configFileDir; 67 fs::path configFileDirPath = configFileDir.getPath(); 68 69 fs::path configFilePath = configFileDirPath; 70 configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json"; 71 writeConfigFile(configFilePath, std::string{""}); 72 73 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 74 EXPECT_EQ(pathFound, configFilePath); 75 } 76 77 // Test where works: Fully qualified system type: Second in list 78 { 79 TemporarySubDirectory configFileDir; 80 fs::path configFileDirPath = configFileDir.getPath(); 81 82 fs::path configFilePath = configFileDirPath; 83 configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer.json"; 84 writeConfigFile(configFilePath, std::string{""}); 85 86 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 87 EXPECT_EQ(pathFound, configFilePath); 88 } 89 90 // Test where works: Last node in system type: Second in list 91 { 92 TemporarySubDirectory configFileDir; 93 fs::path configFileDirPath = configFileDir.getPath(); 94 95 fs::path configFilePath = configFileDirPath; 96 configFilePath /= "MegaServer.json"; 97 writeConfigFile(configFilePath, std::string{""}); 98 99 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 100 EXPECT_EQ(pathFound, configFilePath); 101 } 102 103 // Test where works: Last node in system type: Last in list 104 { 105 TemporarySubDirectory configFileDir; 106 fs::path configFileDirPath = configFileDir.getPath(); 107 108 fs::path configFilePath = configFileDirPath; 109 configFilePath /= "Server.json"; 110 writeConfigFile(configFilePath, std::string{""}); 111 112 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 113 EXPECT_EQ(pathFound, configFilePath); 114 } 115 116 // Test where works: System type has no '.' 117 { 118 TemporarySubDirectory configFileDir; 119 fs::path configFileDirPath = configFileDir.getPath(); 120 121 fs::path configFilePath = configFileDirPath; 122 configFilePath /= "Server.json"; 123 writeConfigFile(configFilePath, std::string{""}); 124 125 std::vector<std::string> noDotSystemTypes{"MegaServer4CPU", 126 "MegaServer", "Server"}; 127 fs::path pathFound = find(noDotSystemTypes, configFileDirPath); 128 EXPECT_EQ(pathFound, configFilePath); 129 } 130 131 // Test where fails: System type list is empty 132 { 133 TemporarySubDirectory configFileDir; 134 fs::path configFileDirPath = configFileDir.getPath(); 135 136 fs::path configFilePath = configFileDirPath; 137 configFilePath /= "Server.json"; 138 writeConfigFile(configFilePath, std::string{""}); 139 140 std::vector<std::string> emptySystemTypes{}; 141 fs::path pathFound = find(emptySystemTypes, configFileDirPath); 142 EXPECT_TRUE(pathFound.empty()); 143 } 144 145 // Test where fails: Configuration file directory is empty 146 { 147 TemporarySubDirectory configFileDir; 148 fs::path configFileDirPath = configFileDir.getPath(); 149 150 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 151 EXPECT_TRUE(pathFound.empty()); 152 } 153 154 // Test where fails: Configuration file directory does not exist 155 { 156 fs::path configFileDirPath{"/tmp/does_not_exist_XYZ"}; 157 158 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 159 EXPECT_TRUE(pathFound.empty()); 160 } 161 162 // Test where fails: Configuration file directory is not readable 163 { 164 TemporarySubDirectory configFileDir; 165 fs::path configFileDirPath = configFileDir.getPath(); 166 fs::permissions(configFileDirPath, fs::perms::none); 167 168 EXPECT_THROW(find(compatibleSystemTypes, configFileDirPath), 169 std::exception); 170 171 fs::permissions(configFileDirPath, fs::perms::owner_all); 172 } 173 174 // Test where fails: No matching file name found 175 { 176 TemporarySubDirectory configFileDir; 177 fs::path configFileDirPath = configFileDir.getPath(); 178 179 fs::path configFilePath = configFileDirPath; 180 configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer"; 181 writeConfigFile(configFilePath, std::string{""}); 182 183 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 184 EXPECT_TRUE(pathFound.empty()); 185 } 186 187 // Test where fails: Matching file name is a directory: Fully qualified 188 { 189 TemporarySubDirectory configFileDir; 190 fs::path configFileDirPath = configFileDir.getPath(); 191 192 fs::path configFilePath = configFileDirPath; 193 configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json"; 194 fs::create_directory(configFilePath); 195 196 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 197 EXPECT_TRUE(pathFound.empty()); 198 } 199 200 // Test where fails: Matching file name is a directory: Last node 201 { 202 TemporarySubDirectory configFileDir; 203 fs::path configFileDirPath = configFileDir.getPath(); 204 205 fs::path configFilePath = configFileDirPath; 206 configFilePath /= "MegaServer.json"; 207 fs::create_directory(configFilePath); 208 209 fs::path pathFound = find(compatibleSystemTypes, configFileDirPath); 210 EXPECT_TRUE(pathFound.empty()); 211 } 212 213 // Test where fails: System type has no '.' 214 { 215 TemporarySubDirectory configFileDir; 216 fs::path configFileDirPath = configFileDir.getPath(); 217 218 fs::path configFilePath = configFileDirPath; 219 configFilePath /= "MegaServer2CPU.json"; 220 writeConfigFile(configFilePath, std::string{""}); 221 222 std::vector<std::string> noDotSystemTypes{"MegaServer4CPU", 223 "MegaServer", "Server", ""}; 224 fs::path pathFound = find(noDotSystemTypes, configFileDirPath); 225 EXPECT_TRUE(pathFound.empty()); 226 } 227 228 // Test where fails: System type ends with '.' 229 { 230 TemporarySubDirectory configFileDir; 231 fs::path configFileDirPath = configFileDir.getPath(); 232 233 fs::path configFilePath = configFileDirPath; 234 configFilePath /= "MegaServer4CPU.json"; 235 writeConfigFile(configFilePath, std::string{""}); 236 237 std::vector<std::string> dotAtEndSystemTypes{ 238 "com.acme.Hardware.Chassis.Model.MegaServer4CPU.", "a.", "."}; 239 fs::path pathFound = find(dotAtEndSystemTypes, configFileDirPath); 240 EXPECT_TRUE(pathFound.empty()); 241 } 242 } 243 244 TEST(ConfigFileParserTests, Parse) 245 { 246 // Test where works 247 { 248 const json configFileContents = R"( 249 { 250 "rails": [ 251 { 252 "name": "VDD_CPU0", 253 "page": 11, 254 "check_status_vout": true 255 }, 256 { 257 "name": "VCS_CPU1", 258 "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1", 259 "gpio": { "line": 60 } 260 } 261 ] 262 } 263 )"_json; 264 265 TemporaryFile configFile; 266 fs::path pathName{configFile.getPath()}; 267 writeConfigFile(pathName, configFileContents); 268 269 std::vector<std::unique_ptr<Rail>> rails = parse(pathName); 270 271 EXPECT_EQ(rails.size(), 2); 272 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0"); 273 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1"); 274 } 275 276 // Test where fails: File does not exist 277 { 278 fs::path pathName{"/tmp/non_existent_file"}; 279 EXPECT_THROW(parse(pathName), ConfigFileParserError); 280 } 281 282 // Test where fails: File is not readable 283 { 284 const json configFileContents = R"( 285 { 286 "rails": [ 287 { 288 "name": "VDD_CPU0" 289 } 290 ] 291 } 292 )"_json; 293 294 TemporaryFile configFile; 295 fs::path pathName{configFile.getPath()}; 296 writeConfigFile(pathName, configFileContents); 297 298 chmod(pathName.c_str(), 0222); 299 EXPECT_THROW(parse(pathName), ConfigFileParserError); 300 } 301 302 // Test where fails: File is not valid JSON 303 { 304 const std::string configFileContents = "] foo ["; 305 306 TemporaryFile configFile; 307 fs::path pathName{configFile.getPath()}; 308 writeConfigFile(pathName, configFileContents); 309 310 EXPECT_THROW(parse(pathName), ConfigFileParserError); 311 } 312 313 // Test where fails: JSON does not conform to config file format 314 { 315 const json configFileContents = R"( [ "foo", "bar" ] )"_json; 316 317 TemporaryFile configFile; 318 fs::path pathName{configFile.getPath()}; 319 writeConfigFile(pathName, configFileContents); 320 321 EXPECT_THROW(parse(pathName), ConfigFileParserError); 322 } 323 } 324 325 TEST(ConfigFileParserTests, GetRequiredProperty) 326 { 327 // Test where property exists 328 { 329 const json element = R"( { "name": "VDD_CPU0" } )"_json; 330 const json& propertyElement = getRequiredProperty(element, "name"); 331 EXPECT_EQ(propertyElement.get<std::string>(), "VDD_CPU0"); 332 } 333 334 // Test where property does not exist 335 try 336 { 337 const json element = R"( { "foo": 23 } )"_json; 338 getRequiredProperty(element, "name"); 339 ADD_FAILURE() << "Should not have reached this line."; 340 } 341 catch (const std::invalid_argument& e) 342 { 343 EXPECT_STREQ(e.what(), "Required property missing: name"); 344 } 345 } 346 347 TEST(ConfigFileParserTests, ParseBoolean) 348 { 349 // Test where works: true 350 { 351 const json element = R"( true )"_json; 352 bool value = parseBoolean(element); 353 EXPECT_EQ(value, true); 354 } 355 356 // Test where works: false 357 { 358 const json element = R"( false )"_json; 359 bool value = parseBoolean(element); 360 EXPECT_EQ(value, false); 361 } 362 363 // Test where fails: Element is not a boolean 364 try 365 { 366 const json element = R"( 1 )"_json; 367 parseBoolean(element); 368 ADD_FAILURE() << "Should not have reached this line."; 369 } 370 catch (const std::invalid_argument& e) 371 { 372 EXPECT_STREQ(e.what(), "Element is not a boolean"); 373 } 374 } 375 376 TEST(ConfigFileParserTests, ParseGPIO) 377 { 378 // Test where works: Only required properties specified 379 { 380 const json element = R"( 381 { 382 "line": 60 383 } 384 )"_json; 385 GPIO gpio = parseGPIO(element); 386 EXPECT_EQ(gpio.line, 60); 387 EXPECT_FALSE(gpio.activeLow); 388 } 389 390 // Test where works: All properties specified 391 { 392 const json element = R"( 393 { 394 "line": 131, 395 "active_low": true 396 } 397 )"_json; 398 GPIO gpio = parseGPIO(element); 399 EXPECT_EQ(gpio.line, 131); 400 EXPECT_TRUE(gpio.activeLow); 401 } 402 403 // Test where fails: Element is not an object 404 try 405 { 406 const json element = R"( [ "vdda", "vddb" ] )"_json; 407 parseGPIO(element); 408 ADD_FAILURE() << "Should not have reached this line."; 409 } 410 catch (const std::invalid_argument& e) 411 { 412 EXPECT_STREQ(e.what(), "Element is not an object"); 413 } 414 415 // Test where fails: Required line property not specified 416 try 417 { 418 const json element = R"( 419 { 420 "active_low": true 421 } 422 )"_json; 423 parseGPIO(element); 424 ADD_FAILURE() << "Should not have reached this line."; 425 } 426 catch (const std::invalid_argument& e) 427 { 428 EXPECT_STREQ(e.what(), "Required property missing: line"); 429 } 430 431 // Test where fails: line value is invalid 432 try 433 { 434 const json element = R"( 435 { 436 "line": -131, 437 "active_low": true 438 } 439 )"_json; 440 parseGPIO(element); 441 ADD_FAILURE() << "Should not have reached this line."; 442 } 443 catch (const std::invalid_argument& e) 444 { 445 EXPECT_STREQ(e.what(), "Element is not an unsigned integer"); 446 } 447 448 // Test where fails: active_low value is invalid 449 try 450 { 451 const json element = R"( 452 { 453 "line": 131, 454 "active_low": "true" 455 } 456 )"_json; 457 parseGPIO(element); 458 ADD_FAILURE() << "Should not have reached this line."; 459 } 460 catch (const std::invalid_argument& e) 461 { 462 EXPECT_STREQ(e.what(), "Element is not a boolean"); 463 } 464 465 // Test where fails: Invalid property specified 466 try 467 { 468 const json element = R"( 469 { 470 "line": 131, 471 "foo": "bar" 472 } 473 )"_json; 474 parseGPIO(element); 475 ADD_FAILURE() << "Should not have reached this line."; 476 } 477 catch (const std::invalid_argument& e) 478 { 479 EXPECT_STREQ(e.what(), "Element contains an invalid property"); 480 } 481 } 482 483 TEST(ConfigFileParserTests, ParseRail) 484 { 485 // Test where works: Only required properties specified 486 { 487 const json element = R"( 488 { 489 "name": "VDD_CPU0" 490 } 491 )"_json; 492 std::unique_ptr<Rail> rail = parseRail(element); 493 EXPECT_EQ(rail->getName(), "VDD_CPU0"); 494 EXPECT_FALSE(rail->getPresence().has_value()); 495 EXPECT_FALSE(rail->getPage().has_value()); 496 EXPECT_FALSE(rail->isPowerSupplyRail()); 497 EXPECT_FALSE(rail->getCheckStatusVout()); 498 EXPECT_FALSE(rail->getCompareVoltageToLimit()); 499 EXPECT_FALSE(rail->getGPIO().has_value()); 500 } 501 502 // Test where works: All properties specified 503 { 504 const json element = R"( 505 { 506 "name": "12.0VB", 507 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply1", 508 "page": 11, 509 "is_power_supply_rail": true, 510 "check_status_vout": true, 511 "compare_voltage_to_limit": true, 512 "gpio": { "line": 60, "active_low": true } 513 } 514 )"_json; 515 std::unique_ptr<Rail> rail = parseRail(element); 516 EXPECT_EQ(rail->getName(), "12.0VB"); 517 EXPECT_TRUE(rail->getPresence().has_value()); 518 EXPECT_EQ(rail->getPresence().value(), 519 "/xyz/openbmc_project/inventory/system/chassis/powersupply1"); 520 EXPECT_TRUE(rail->getPage().has_value()); 521 EXPECT_EQ(rail->getPage().value(), 11); 522 EXPECT_TRUE(rail->isPowerSupplyRail()); 523 EXPECT_TRUE(rail->getCheckStatusVout()); 524 EXPECT_TRUE(rail->getCompareVoltageToLimit()); 525 EXPECT_TRUE(rail->getGPIO().has_value()); 526 EXPECT_EQ(rail->getGPIO().value().line, 60); 527 EXPECT_TRUE(rail->getGPIO().value().activeLow); 528 } 529 530 // Test where fails: Element is not an object 531 try 532 { 533 const json element = R"( [ "vdda", "vddb" ] )"_json; 534 parseRail(element); 535 ADD_FAILURE() << "Should not have reached this line."; 536 } 537 catch (const std::invalid_argument& e) 538 { 539 EXPECT_STREQ(e.what(), "Element is not an object"); 540 } 541 542 // Test where fails: Required name property not specified 543 try 544 { 545 const json element = R"( 546 { 547 "page": 11 548 } 549 )"_json; 550 parseRail(element); 551 ADD_FAILURE() << "Should not have reached this line."; 552 } 553 catch (const std::invalid_argument& e) 554 { 555 EXPECT_STREQ(e.what(), "Required property missing: name"); 556 } 557 558 // Test where fails: name value is invalid 559 try 560 { 561 const json element = R"( 562 { 563 "name": 31, 564 "page": 11 565 } 566 )"_json; 567 parseRail(element); 568 ADD_FAILURE() << "Should not have reached this line."; 569 } 570 catch (const std::invalid_argument& e) 571 { 572 EXPECT_STREQ(e.what(), "Element is not a string"); 573 } 574 575 // Test where fails: presence value is invalid 576 try 577 { 578 const json element = R"( 579 { 580 "name": "VCS_CPU1", 581 "presence": false 582 } 583 )"_json; 584 parseRail(element); 585 ADD_FAILURE() << "Should not have reached this line."; 586 } 587 catch (const std::invalid_argument& e) 588 { 589 EXPECT_STREQ(e.what(), "Element is not a string"); 590 } 591 592 // Test where fails: page value is invalid 593 try 594 { 595 const json element = R"( 596 { 597 "name": "VCS_CPU1", 598 "page": 256 599 } 600 )"_json; 601 parseRail(element); 602 ADD_FAILURE() << "Should not have reached this line."; 603 } 604 catch (const std::invalid_argument& e) 605 { 606 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer"); 607 } 608 609 // Test where fails: is_power_supply_rail value is invalid 610 try 611 { 612 const json element = R"( 613 { 614 "name": "12.0VA", 615 "is_power_supply_rail": "true" 616 } 617 )"_json; 618 parseRail(element); 619 ADD_FAILURE() << "Should not have reached this line."; 620 } 621 catch (const std::invalid_argument& e) 622 { 623 EXPECT_STREQ(e.what(), "Element is not a boolean"); 624 } 625 626 // Test where fails: check_status_vout value is invalid 627 try 628 { 629 const json element = R"( 630 { 631 "name": "VCS_CPU1", 632 "check_status_vout": "false" 633 } 634 )"_json; 635 parseRail(element); 636 ADD_FAILURE() << "Should not have reached this line."; 637 } 638 catch (const std::invalid_argument& e) 639 { 640 EXPECT_STREQ(e.what(), "Element is not a boolean"); 641 } 642 643 // Test where fails: compare_voltage_to_limit value is invalid 644 try 645 { 646 const json element = R"( 647 { 648 "name": "VCS_CPU1", 649 "compare_voltage_to_limit": 23 650 } 651 )"_json; 652 parseRail(element); 653 ADD_FAILURE() << "Should not have reached this line."; 654 } 655 catch (const std::invalid_argument& e) 656 { 657 EXPECT_STREQ(e.what(), "Element is not a boolean"); 658 } 659 660 // Test where fails: gpio value is invalid 661 try 662 { 663 const json element = R"( 664 { 665 "name": "VCS_CPU1", 666 "gpio": 131 667 } 668 )"_json; 669 parseRail(element); 670 ADD_FAILURE() << "Should not have reached this line."; 671 } 672 catch (const std::invalid_argument& e) 673 { 674 EXPECT_STREQ(e.what(), "Element is not an object"); 675 } 676 677 // Test where fails: check_status_vout is true and page not specified 678 try 679 { 680 const json element = R"( 681 { 682 "name": "VCS_CPU1", 683 "check_status_vout": true 684 } 685 )"_json; 686 parseRail(element); 687 ADD_FAILURE() << "Should not have reached this line."; 688 } 689 catch (const std::invalid_argument& e) 690 { 691 EXPECT_STREQ(e.what(), "Required property missing: page"); 692 } 693 694 // Test where fails: compare_voltage_to_limit is true and page not 695 // specified 696 try 697 { 698 const json element = R"( 699 { 700 "name": "VCS_CPU1", 701 "compare_voltage_to_limit": true 702 } 703 )"_json; 704 parseRail(element); 705 ADD_FAILURE() << "Should not have reached this line."; 706 } 707 catch (const std::invalid_argument& e) 708 { 709 EXPECT_STREQ(e.what(), "Required property missing: page"); 710 } 711 712 // Test where fails: Invalid property specified 713 try 714 { 715 const json element = R"( 716 { 717 "name": "VCS_CPU1", 718 "foo": "bar" 719 } 720 )"_json; 721 parseRail(element); 722 ADD_FAILURE() << "Should not have reached this line."; 723 } 724 catch (const std::invalid_argument& e) 725 { 726 EXPECT_STREQ(e.what(), "Element contains an invalid property"); 727 } 728 } 729 730 TEST(ConfigFileParserTests, ParseRailArray) 731 { 732 // Test where works: Array is empty 733 { 734 const json element = R"( 735 [ 736 ] 737 )"_json; 738 std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element); 739 EXPECT_EQ(rails.size(), 0); 740 } 741 742 // Test where works: Array is not empty 743 { 744 const json element = R"( 745 [ 746 { "name": "VDD_CPU0" }, 747 { "name": "VCS_CPU1" } 748 ] 749 )"_json; 750 std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element); 751 EXPECT_EQ(rails.size(), 2); 752 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0"); 753 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1"); 754 } 755 756 // Test where fails: Element is not an array 757 try 758 { 759 const json element = R"( 760 { 761 "foo": "bar" 762 } 763 )"_json; 764 parseRailArray(element); 765 ADD_FAILURE() << "Should not have reached this line."; 766 } 767 catch (const std::invalid_argument& e) 768 { 769 EXPECT_STREQ(e.what(), "Element is not an array"); 770 } 771 772 // Test where fails: Element within array is invalid 773 try 774 { 775 const json element = R"( 776 [ 777 { "name": "VDD_CPU0" }, 778 23 779 ] 780 )"_json; 781 parseRailArray(element); 782 ADD_FAILURE() << "Should not have reached this line."; 783 } 784 catch (const std::invalid_argument& e) 785 { 786 EXPECT_STREQ(e.what(), "Element is not an object"); 787 } 788 } 789 790 TEST(ConfigFileParserTests, ParseRoot) 791 { 792 // Test where works 793 { 794 const json element = R"( 795 { 796 "rails": [ 797 { 798 "name": "VDD_CPU0", 799 "page": 11, 800 "check_status_vout": true 801 }, 802 { 803 "name": "VCS_CPU1", 804 "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1", 805 "gpio": { "line": 60 } 806 } 807 ] 808 } 809 )"_json; 810 std::vector<std::unique_ptr<Rail>> rails = parseRoot(element); 811 EXPECT_EQ(rails.size(), 2); 812 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0"); 813 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1"); 814 } 815 816 // Test where fails: Element is not an object 817 try 818 { 819 const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json; 820 parseRoot(element); 821 ADD_FAILURE() << "Should not have reached this line."; 822 } 823 catch (const std::invalid_argument& e) 824 { 825 EXPECT_STREQ(e.what(), "Element is not an object"); 826 } 827 828 // Test where fails: Required rails property not specified 829 try 830 { 831 const json element = R"( 832 { 833 } 834 )"_json; 835 parseRoot(element); 836 ADD_FAILURE() << "Should not have reached this line."; 837 } 838 catch (const std::invalid_argument& e) 839 { 840 EXPECT_STREQ(e.what(), "Required property missing: rails"); 841 } 842 843 // Test where fails: rails value is invalid 844 try 845 { 846 const json element = R"( 847 { 848 "rails": 31 849 } 850 )"_json; 851 parseRoot(element); 852 ADD_FAILURE() << "Should not have reached this line."; 853 } 854 catch (const std::invalid_argument& e) 855 { 856 EXPECT_STREQ(e.what(), "Element is not an array"); 857 } 858 859 // Test where fails: Invalid property specified 860 try 861 { 862 const json element = R"( 863 { 864 "rails": [ 865 { 866 "name": "VDD_CPU0", 867 "page": 11, 868 "check_status_vout": true 869 } 870 ], 871 "foo": true 872 } 873 )"_json; 874 parseRoot(element); 875 ADD_FAILURE() << "Should not have reached this line."; 876 } 877 catch (const std::invalid_argument& e) 878 { 879 EXPECT_STREQ(e.what(), "Element contains an invalid property"); 880 } 881 } 882 883 TEST(ConfigFileParserTests, ParseString) 884 { 885 // Test where works: Empty string 886 { 887 const json element = ""; 888 std::string value = parseString(element, true); 889 EXPECT_EQ(value, ""); 890 } 891 892 // Test where works: Non-empty string 893 { 894 const json element = "vdd_cpu1"; 895 std::string value = parseString(element, false); 896 EXPECT_EQ(value, "vdd_cpu1"); 897 } 898 899 // Test where fails: Element is not a string 900 try 901 { 902 const json element = R"( { "foo": "bar" } )"_json; 903 parseString(element); 904 ADD_FAILURE() << "Should not have reached this line."; 905 } 906 catch (const std::invalid_argument& e) 907 { 908 EXPECT_STREQ(e.what(), "Element is not a string"); 909 } 910 911 // Test where fails: Empty string 912 try 913 { 914 const json element = ""; 915 parseString(element); 916 ADD_FAILURE() << "Should not have reached this line."; 917 } 918 catch (const std::invalid_argument& e) 919 { 920 EXPECT_STREQ(e.what(), "Element contains an empty string"); 921 } 922 } 923 924 TEST(ConfigFileParserTests, ParseUint8) 925 { 926 // Test where works: 0 927 { 928 const json element = R"( 0 )"_json; 929 uint8_t value = parseUint8(element); 930 EXPECT_EQ(value, 0); 931 } 932 933 // Test where works: UINT8_MAX 934 { 935 const json element = R"( 255 )"_json; 936 uint8_t value = parseUint8(element); 937 EXPECT_EQ(value, 255); 938 } 939 940 // Test where fails: Element is not an integer 941 try 942 { 943 const json element = R"( 1.03 )"_json; 944 parseUint8(element); 945 ADD_FAILURE() << "Should not have reached this line."; 946 } 947 catch (const std::invalid_argument& e) 948 { 949 EXPECT_STREQ(e.what(), "Element is not an integer"); 950 } 951 952 // Test where fails: Value < 0 953 try 954 { 955 const json element = R"( -1 )"_json; 956 parseUint8(element); 957 ADD_FAILURE() << "Should not have reached this line."; 958 } 959 catch (const std::invalid_argument& e) 960 { 961 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer"); 962 } 963 964 // Test where fails: Value > UINT8_MAX 965 try 966 { 967 const json element = R"( 256 )"_json; 968 parseUint8(element); 969 ADD_FAILURE() << "Should not have reached this line."; 970 } 971 catch (const std::invalid_argument& e) 972 { 973 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer"); 974 } 975 } 976 977 TEST(ConfigFileParserTests, ParseUnsignedInteger) 978 { 979 // Test where works: 1 980 { 981 const json element = R"( 1 )"_json; 982 unsigned int value = parseUnsignedInteger(element); 983 EXPECT_EQ(value, 1); 984 } 985 986 // Test where fails: Element is not an integer 987 try 988 { 989 const json element = R"( 1.5 )"_json; 990 parseUnsignedInteger(element); 991 ADD_FAILURE() << "Should not have reached this line."; 992 } 993 catch (const std::invalid_argument& e) 994 { 995 EXPECT_STREQ(e.what(), "Element is not an unsigned integer"); 996 } 997 998 // Test where fails: Value < 0 999 try 1000 { 1001 const json element = R"( -1 )"_json; 1002 parseUnsignedInteger(element); 1003 ADD_FAILURE() << "Should not have reached this line."; 1004 } 1005 catch (const std::invalid_argument& e) 1006 { 1007 EXPECT_STREQ(e.what(), "Element is not an unsigned integer"); 1008 } 1009 } 1010 1011 TEST(ConfigFileParserTests, VerifyIsArray) 1012 { 1013 // Test where element is an array 1014 { 1015 const json element = R"( [ "foo", "bar" ] )"_json; 1016 verifyIsArray(element); 1017 } 1018 1019 // Test where element is not an array 1020 try 1021 { 1022 const json element = R"( { "foo": "bar" } )"_json; 1023 verifyIsArray(element); 1024 ADD_FAILURE() << "Should not have reached this line."; 1025 } 1026 catch (const std::invalid_argument& e) 1027 { 1028 EXPECT_STREQ(e.what(), "Element is not an array"); 1029 } 1030 } 1031 1032 TEST(ConfigFileParserTests, VerifyIsObject) 1033 { 1034 // Test where element is an object 1035 { 1036 const json element = R"( { "foo": "bar" } )"_json; 1037 verifyIsObject(element); 1038 } 1039 1040 // Test where element is not an object 1041 try 1042 { 1043 const json element = R"( [ "foo", "bar" ] )"_json; 1044 verifyIsObject(element); 1045 ADD_FAILURE() << "Should not have reached this line."; 1046 } 1047 catch (const std::invalid_argument& e) 1048 { 1049 EXPECT_STREQ(e.what(), "Element is not an object"); 1050 } 1051 } 1052 1053 TEST(ConfigFileParserTests, VerifyPropertyCount) 1054 { 1055 // Test where element has expected number of properties 1056 { 1057 const json element = R"( 1058 { 1059 "line": 131, 1060 "active_low": true 1061 } 1062 )"_json; 1063 verifyPropertyCount(element, 2); 1064 } 1065 1066 // Test where element has unexpected number of properties 1067 try 1068 { 1069 const json element = R"( 1070 { 1071 "line": 131, 1072 "active_low": true, 1073 "foo": 1.3 1074 } 1075 )"_json; 1076 verifyPropertyCount(element, 2); 1077 ADD_FAILURE() << "Should not have reached this line."; 1078 } 1079 catch (const std::invalid_argument& e) 1080 { 1081 EXPECT_STREQ(e.what(), "Element contains an invalid property"); 1082 } 1083 } 1084