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->isPowerSupplyRail()); 310 EXPECT_FALSE(rail->getCheckStatusVout()); 311 EXPECT_FALSE(rail->getCompareVoltageToLimit()); 312 EXPECT_FALSE(rail->getGPIO().has_value()); 313 } 314 315 // Test where works: All properties specified 316 { 317 const json element = R"( 318 { 319 "name": "12.0VB", 320 "presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply1", 321 "page": 11, 322 "is_power_supply_rail": true, 323 "check_status_vout": true, 324 "compare_voltage_to_limit": true, 325 "gpio": { "line": 60, "active_low": true } 326 } 327 )"_json; 328 std::unique_ptr<Rail> rail = parseRail(element); 329 EXPECT_EQ(rail->getName(), "12.0VB"); 330 EXPECT_TRUE(rail->getPresence().has_value()); 331 EXPECT_EQ(rail->getPresence().value(), 332 "/xyz/openbmc_project/inventory/system/chassis/powersupply1"); 333 EXPECT_TRUE(rail->getPage().has_value()); 334 EXPECT_EQ(rail->getPage().value(), 11); 335 EXPECT_TRUE(rail->isPowerSupplyRail()); 336 EXPECT_TRUE(rail->getCheckStatusVout()); 337 EXPECT_TRUE(rail->getCompareVoltageToLimit()); 338 EXPECT_TRUE(rail->getGPIO().has_value()); 339 EXPECT_EQ(rail->getGPIO().value().line, 60); 340 EXPECT_TRUE(rail->getGPIO().value().activeLow); 341 } 342 343 // Test where fails: Element is not an object 344 try 345 { 346 const json element = R"( [ "vdda", "vddb" ] )"_json; 347 parseRail(element); 348 ADD_FAILURE() << "Should not have reached this line."; 349 } 350 catch (const std::invalid_argument& e) 351 { 352 EXPECT_STREQ(e.what(), "Element is not an object"); 353 } 354 355 // Test where fails: Required name property not specified 356 try 357 { 358 const json element = R"( 359 { 360 "page": 11 361 } 362 )"_json; 363 parseRail(element); 364 ADD_FAILURE() << "Should not have reached this line."; 365 } 366 catch (const std::invalid_argument& e) 367 { 368 EXPECT_STREQ(e.what(), "Required property missing: name"); 369 } 370 371 // Test where fails: name value is invalid 372 try 373 { 374 const json element = R"( 375 { 376 "name": 31, 377 "page": 11 378 } 379 )"_json; 380 parseRail(element); 381 ADD_FAILURE() << "Should not have reached this line."; 382 } 383 catch (const std::invalid_argument& e) 384 { 385 EXPECT_STREQ(e.what(), "Element is not a string"); 386 } 387 388 // Test where fails: presence value is invalid 389 try 390 { 391 const json element = R"( 392 { 393 "name": "VCS_CPU1", 394 "presence": false 395 } 396 )"_json; 397 parseRail(element); 398 ADD_FAILURE() << "Should not have reached this line."; 399 } 400 catch (const std::invalid_argument& e) 401 { 402 EXPECT_STREQ(e.what(), "Element is not a string"); 403 } 404 405 // Test where fails: page value is invalid 406 try 407 { 408 const json element = R"( 409 { 410 "name": "VCS_CPU1", 411 "page": 256 412 } 413 )"_json; 414 parseRail(element); 415 ADD_FAILURE() << "Should not have reached this line."; 416 } 417 catch (const std::invalid_argument& e) 418 { 419 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer"); 420 } 421 422 // Test where fails: is_power_supply_rail value is invalid 423 try 424 { 425 const json element = R"( 426 { 427 "name": "12.0VA", 428 "is_power_supply_rail": "true" 429 } 430 )"_json; 431 parseRail(element); 432 ADD_FAILURE() << "Should not have reached this line."; 433 } 434 catch (const std::invalid_argument& e) 435 { 436 EXPECT_STREQ(e.what(), "Element is not a boolean"); 437 } 438 439 // Test where fails: check_status_vout value is invalid 440 try 441 { 442 const json element = R"( 443 { 444 "name": "VCS_CPU1", 445 "check_status_vout": "false" 446 } 447 )"_json; 448 parseRail(element); 449 ADD_FAILURE() << "Should not have reached this line."; 450 } 451 catch (const std::invalid_argument& e) 452 { 453 EXPECT_STREQ(e.what(), "Element is not a boolean"); 454 } 455 456 // Test where fails: compare_voltage_to_limit value is invalid 457 try 458 { 459 const json element = R"( 460 { 461 "name": "VCS_CPU1", 462 "compare_voltage_to_limit": 23 463 } 464 )"_json; 465 parseRail(element); 466 ADD_FAILURE() << "Should not have reached this line."; 467 } 468 catch (const std::invalid_argument& e) 469 { 470 EXPECT_STREQ(e.what(), "Element is not a boolean"); 471 } 472 473 // Test where fails: gpio value is invalid 474 try 475 { 476 const json element = R"( 477 { 478 "name": "VCS_CPU1", 479 "gpio": 131 480 } 481 )"_json; 482 parseRail(element); 483 ADD_FAILURE() << "Should not have reached this line."; 484 } 485 catch (const std::invalid_argument& e) 486 { 487 EXPECT_STREQ(e.what(), "Element is not an object"); 488 } 489 490 // Test where fails: check_status_vout is true and page not specified 491 try 492 { 493 const json element = R"( 494 { 495 "name": "VCS_CPU1", 496 "check_status_vout": true 497 } 498 )"_json; 499 parseRail(element); 500 ADD_FAILURE() << "Should not have reached this line."; 501 } 502 catch (const std::invalid_argument& e) 503 { 504 EXPECT_STREQ(e.what(), "Required property missing: page"); 505 } 506 507 // Test where fails: compare_voltage_to_limit is true and page not 508 // specified 509 try 510 { 511 const json element = R"( 512 { 513 "name": "VCS_CPU1", 514 "compare_voltage_to_limit": true 515 } 516 )"_json; 517 parseRail(element); 518 ADD_FAILURE() << "Should not have reached this line."; 519 } 520 catch (const std::invalid_argument& e) 521 { 522 EXPECT_STREQ(e.what(), "Required property missing: page"); 523 } 524 525 // Test where fails: Invalid property specified 526 try 527 { 528 const json element = R"( 529 { 530 "name": "VCS_CPU1", 531 "foo": "bar" 532 } 533 )"_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 contains an invalid property"); 540 } 541 } 542 543 TEST(ConfigFileParserTests, ParseRailArray) 544 { 545 // Test where works: Array is empty 546 { 547 const json element = R"( 548 [ 549 ] 550 )"_json; 551 std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element); 552 EXPECT_EQ(rails.size(), 0); 553 } 554 555 // Test where works: Array is not empty 556 { 557 const json element = R"( 558 [ 559 { "name": "VDD_CPU0" }, 560 { "name": "VCS_CPU1" } 561 ] 562 )"_json; 563 std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element); 564 EXPECT_EQ(rails.size(), 2); 565 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0"); 566 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1"); 567 } 568 569 // Test where fails: Element is not an array 570 try 571 { 572 const json element = R"( 573 { 574 "foo": "bar" 575 } 576 )"_json; 577 parseRailArray(element); 578 ADD_FAILURE() << "Should not have reached this line."; 579 } 580 catch (const std::invalid_argument& e) 581 { 582 EXPECT_STREQ(e.what(), "Element is not an array"); 583 } 584 585 // Test where fails: Element within array is invalid 586 try 587 { 588 const json element = R"( 589 [ 590 { "name": "VDD_CPU0" }, 591 23 592 ] 593 )"_json; 594 parseRailArray(element); 595 ADD_FAILURE() << "Should not have reached this line."; 596 } 597 catch (const std::invalid_argument& e) 598 { 599 EXPECT_STREQ(e.what(), "Element is not an object"); 600 } 601 } 602 603 TEST(ConfigFileParserTests, ParseRoot) 604 { 605 // Test where works 606 { 607 const json element = R"( 608 { 609 "rails": [ 610 { 611 "name": "VDD_CPU0", 612 "page": 11, 613 "check_status_vout": true 614 }, 615 { 616 "name": "VCS_CPU1", 617 "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1", 618 "gpio": { "line": 60 } 619 } 620 ] 621 } 622 )"_json; 623 std::vector<std::unique_ptr<Rail>> rails = parseRoot(element); 624 EXPECT_EQ(rails.size(), 2); 625 EXPECT_EQ(rails[0]->getName(), "VDD_CPU0"); 626 EXPECT_EQ(rails[1]->getName(), "VCS_CPU1"); 627 } 628 629 // Test where fails: Element is not an object 630 try 631 { 632 const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json; 633 parseRoot(element); 634 ADD_FAILURE() << "Should not have reached this line."; 635 } 636 catch (const std::invalid_argument& e) 637 { 638 EXPECT_STREQ(e.what(), "Element is not an object"); 639 } 640 641 // Test where fails: Required rails property not specified 642 try 643 { 644 const json element = R"( 645 { 646 } 647 )"_json; 648 parseRoot(element); 649 ADD_FAILURE() << "Should not have reached this line."; 650 } 651 catch (const std::invalid_argument& e) 652 { 653 EXPECT_STREQ(e.what(), "Required property missing: rails"); 654 } 655 656 // Test where fails: rails value is invalid 657 try 658 { 659 const json element = R"( 660 { 661 "rails": 31 662 } 663 )"_json; 664 parseRoot(element); 665 ADD_FAILURE() << "Should not have reached this line."; 666 } 667 catch (const std::invalid_argument& e) 668 { 669 EXPECT_STREQ(e.what(), "Element is not an array"); 670 } 671 672 // Test where fails: Invalid property specified 673 try 674 { 675 const json element = R"( 676 { 677 "rails": [ 678 { 679 "name": "VDD_CPU0", 680 "page": 11, 681 "check_status_vout": true 682 } 683 ], 684 "foo": true 685 } 686 )"_json; 687 parseRoot(element); 688 ADD_FAILURE() << "Should not have reached this line."; 689 } 690 catch (const std::invalid_argument& e) 691 { 692 EXPECT_STREQ(e.what(), "Element contains an invalid property"); 693 } 694 } 695 696 TEST(ConfigFileParserTests, ParseString) 697 { 698 // Test where works: Empty string 699 { 700 const json element = ""; 701 std::string value = parseString(element, true); 702 EXPECT_EQ(value, ""); 703 } 704 705 // Test where works: Non-empty string 706 { 707 const json element = "vdd_cpu1"; 708 std::string value = parseString(element, false); 709 EXPECT_EQ(value, "vdd_cpu1"); 710 } 711 712 // Test where fails: Element is not a string 713 try 714 { 715 const json element = R"( { "foo": "bar" } )"_json; 716 parseString(element); 717 ADD_FAILURE() << "Should not have reached this line."; 718 } 719 catch (const std::invalid_argument& e) 720 { 721 EXPECT_STREQ(e.what(), "Element is not a string"); 722 } 723 724 // Test where fails: Empty string 725 try 726 { 727 const json element = ""; 728 parseString(element); 729 ADD_FAILURE() << "Should not have reached this line."; 730 } 731 catch (const std::invalid_argument& e) 732 { 733 EXPECT_STREQ(e.what(), "Element contains an empty string"); 734 } 735 } 736 737 TEST(ConfigFileParserTests, ParseUint8) 738 { 739 // Test where works: 0 740 { 741 const json element = R"( 0 )"_json; 742 uint8_t value = parseUint8(element); 743 EXPECT_EQ(value, 0); 744 } 745 746 // Test where works: UINT8_MAX 747 { 748 const json element = R"( 255 )"_json; 749 uint8_t value = parseUint8(element); 750 EXPECT_EQ(value, 255); 751 } 752 753 // Test where fails: Element is not an integer 754 try 755 { 756 const json element = R"( 1.03 )"_json; 757 parseUint8(element); 758 ADD_FAILURE() << "Should not have reached this line."; 759 } 760 catch (const std::invalid_argument& e) 761 { 762 EXPECT_STREQ(e.what(), "Element is not an integer"); 763 } 764 765 // Test where fails: Value < 0 766 try 767 { 768 const json element = R"( -1 )"_json; 769 parseUint8(element); 770 ADD_FAILURE() << "Should not have reached this line."; 771 } 772 catch (const std::invalid_argument& e) 773 { 774 EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer"); 775 } 776 777 // Test where fails: Value > UINT8_MAX 778 try 779 { 780 const json element = R"( 256 )"_json; 781 parseUint8(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 8-bit unsigned integer"); 787 } 788 } 789 790 TEST(ConfigFileParserTests, ParseUnsignedInteger) 791 { 792 // Test where works: 1 793 { 794 const json element = R"( 1 )"_json; 795 unsigned int value = parseUnsignedInteger(element); 796 EXPECT_EQ(value, 1); 797 } 798 799 // Test where fails: Element is not an integer 800 try 801 { 802 const json element = R"( 1.5 )"_json; 803 parseUnsignedInteger(element); 804 ADD_FAILURE() << "Should not have reached this line."; 805 } 806 catch (const std::invalid_argument& e) 807 { 808 EXPECT_STREQ(e.what(), "Element is not an unsigned integer"); 809 } 810 811 // Test where fails: Value < 0 812 try 813 { 814 const json element = R"( -1 )"_json; 815 parseUnsignedInteger(element); 816 ADD_FAILURE() << "Should not have reached this line."; 817 } 818 catch (const std::invalid_argument& e) 819 { 820 EXPECT_STREQ(e.what(), "Element is not an unsigned integer"); 821 } 822 } 823 824 TEST(ConfigFileParserTests, VerifyIsArray) 825 { 826 // Test where element is an array 827 { 828 const json element = R"( [ "foo", "bar" ] )"_json; 829 verifyIsArray(element); 830 } 831 832 // Test where element is not an array 833 try 834 { 835 const json element = R"( { "foo": "bar" } )"_json; 836 verifyIsArray(element); 837 ADD_FAILURE() << "Should not have reached this line."; 838 } 839 catch (const std::invalid_argument& e) 840 { 841 EXPECT_STREQ(e.what(), "Element is not an array"); 842 } 843 } 844 845 TEST(ConfigFileParserTests, VerifyIsObject) 846 { 847 // Test where element is an object 848 { 849 const json element = R"( { "foo": "bar" } )"_json; 850 verifyIsObject(element); 851 } 852 853 // Test where element is not an object 854 try 855 { 856 const json element = R"( [ "foo", "bar" ] )"_json; 857 verifyIsObject(element); 858 ADD_FAILURE() << "Should not have reached this line."; 859 } 860 catch (const std::invalid_argument& e) 861 { 862 EXPECT_STREQ(e.what(), "Element is not an object"); 863 } 864 } 865 866 TEST(ConfigFileParserTests, VerifyPropertyCount) 867 { 868 // Test where element has expected number of properties 869 { 870 const json element = R"( 871 { 872 "line": 131, 873 "active_low": true 874 } 875 )"_json; 876 verifyPropertyCount(element, 2); 877 } 878 879 // Test where element has unexpected number of properties 880 try 881 { 882 const json element = R"( 883 { 884 "line": 131, 885 "active_low": true, 886 "foo": 1.3 887 } 888 )"_json; 889 verifyPropertyCount(element, 2); 890 ADD_FAILURE() << "Should not have reached this line."; 891 } 892 catch (const std::invalid_argument& e) 893 { 894 EXPECT_STREQ(e.what(), "Element contains an invalid property"); 895 } 896 } 897