1 /** 2 * Copyright © 2021 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_error.hpp" 17 #include "error_logging.hpp" 18 #include "error_logging_utils.hpp" 19 #include "i2c_interface.hpp" 20 #include "journal.hpp" 21 #include "mock_error_logging.hpp" 22 #include "mock_journal.hpp" 23 #include "mock_services.hpp" 24 #include "pmbus_error.hpp" 25 #include "test_sdbus_error.hpp" 26 #include "write_verification_error.hpp" 27 28 #include <errno.h> 29 30 #include <sdbusplus/exception.hpp> 31 32 #include <exception> 33 #include <filesystem> 34 #include <stdexcept> 35 #include <string> 36 37 #include <gmock/gmock.h> 38 #include <gtest/gtest.h> 39 40 using namespace phosphor::power::regulators; 41 42 namespace fs = std::filesystem; 43 44 using ::testing::Ref; 45 46 TEST(ErrorLoggingUtilsTests, LogError_3Parameters) 47 { 48 // Create exception with two nesting levels; top priority is inner 49 // PMBusError 50 std::exception_ptr eptr; 51 try 52 { 53 try 54 { 55 throw PMBusError{"VOUT_MODE contains unsupported data format", 56 "reg1", 57 "/xyz/openbmc_project/inventory/system/chassis/" 58 "motherboard/reg1"}; 59 } 60 catch (...) 61 { 62 std::throw_with_nested( 63 std::runtime_error{"Unable to set output voltage"}); 64 } 65 } 66 catch (...) 67 { 68 eptr = std::current_exception(); 69 } 70 71 // Create MockServices. Expect logPMBusError() to be called. 72 MockServices services{}; 73 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 74 MockJournal& journal = services.getMockJournal(); 75 EXPECT_CALL( 76 errorLogging, 77 logPMBusError( 78 Entry::Level::Error, Ref(journal), 79 "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1")) 80 .Times(1); 81 82 // Log error based on the nested exception 83 error_logging_utils::logError(eptr, Entry::Level::Error, services); 84 } 85 86 TEST(ErrorLoggingUtilsTests, LogError_4Parameters) 87 { 88 // Test where exception pointer is null 89 { 90 std::exception_ptr eptr; 91 92 // Create MockServices. Don't expect any log*() methods to be called. 93 MockServices services{}; 94 95 ErrorHistory history{}; 96 error_logging_utils::logError(eptr, Entry::Level::Error, services, 97 history); 98 } 99 100 // Test where exception is not nested 101 { 102 std::exception_ptr eptr; 103 try 104 { 105 throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8", 106 0x30, ENODEV}; 107 } 108 catch (...) 109 { 110 eptr = std::current_exception(); 111 } 112 113 // Create MockServices. Expect logI2CError() to be called. 114 MockServices services{}; 115 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 116 MockJournal& journal = services.getMockJournal(); 117 EXPECT_CALL(errorLogging, 118 logI2CError(Entry::Level::Critical, Ref(journal), 119 "/dev/i2c-8", 0x30, ENODEV)) 120 .Times(1); 121 122 // Log error based on the nested exception 123 ErrorHistory history{}; 124 error_logging_utils::logError(eptr, Entry::Level::Critical, services, 125 history); 126 } 127 128 // Test where exception is nested 129 { 130 std::exception_ptr eptr; 131 try 132 { 133 try 134 { 135 throw std::invalid_argument{"JSON element is not an array"}; 136 } 137 catch (...) 138 { 139 std::throw_with_nested(ConfigFileParserError{ 140 fs::path{"/etc/phosphor-regulators/config.json"}, 141 "Unable to parse JSON configuration file"}); 142 } 143 } 144 catch (...) 145 { 146 eptr = std::current_exception(); 147 } 148 149 // Create MockServices. Expect logConfigFileError() to be called. 150 MockServices services{}; 151 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 152 MockJournal& journal = services.getMockJournal(); 153 EXPECT_CALL(errorLogging, 154 logConfigFileError(Entry::Level::Warning, Ref(journal))) 155 .Times(1); 156 157 // Log error based on the nested exception 158 ErrorHistory history{}; 159 error_logging_utils::logError(eptr, Entry::Level::Warning, services, 160 history); 161 } 162 163 // Test where exception is a ConfigFileParserError 164 { 165 std::exception_ptr eptr; 166 try 167 { 168 throw ConfigFileParserError{ 169 fs::path{"/etc/phosphor-regulators/config.json"}, 170 "Unable to parse JSON configuration file"}; 171 } 172 catch (...) 173 { 174 eptr = std::current_exception(); 175 } 176 177 // Create MockServices. Expect logConfigFileError() to be called once. 178 MockServices services{}; 179 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 180 MockJournal& journal = services.getMockJournal(); 181 EXPECT_CALL(errorLogging, 182 logConfigFileError(Entry::Level::Error, Ref(journal))) 183 .Times(1); 184 185 // Log error based on the nested exception 186 ErrorHistory history{}; 187 error_logging_utils::logError(eptr, Entry::Level::Error, services, 188 history); 189 190 // Try to log error again. Should not happen due to ErrorHistory. 191 error_logging_utils::logError(eptr, Entry::Level::Error, services, 192 history); 193 } 194 195 // Test where exception is a PMBusError 196 { 197 std::exception_ptr eptr; 198 try 199 { 200 throw PMBusError{"VOUT_MODE contains unsupported data format", 201 "reg1", 202 "/xyz/openbmc_project/inventory/system/chassis/" 203 "motherboard/reg1"}; 204 } 205 catch (...) 206 { 207 eptr = std::current_exception(); 208 } 209 210 // Create MockServices. Expect logPMBusError() to be called once. 211 MockServices services{}; 212 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 213 MockJournal& journal = services.getMockJournal(); 214 EXPECT_CALL(errorLogging, 215 logPMBusError(Entry::Level::Error, Ref(journal), 216 "/xyz/openbmc_project/inventory/system/" 217 "chassis/motherboard/reg1")) 218 .Times(1); 219 220 // Log error based on the nested exception 221 ErrorHistory history{}; 222 error_logging_utils::logError(eptr, Entry::Level::Error, services, 223 history); 224 225 // Try to log error again. Should not happen due to ErrorHistory. 226 error_logging_utils::logError(eptr, Entry::Level::Error, services, 227 history); 228 } 229 230 // Test where exception is a WriteVerificationError 231 { 232 std::exception_ptr eptr; 233 try 234 { 235 throw WriteVerificationError{ 236 "value_written: 0xDEAD, value_read: 0xBEEF", "reg1", 237 "/xyz/openbmc_project/inventory/system/chassis/motherboard/" 238 "reg1"}; 239 } 240 catch (...) 241 { 242 eptr = std::current_exception(); 243 } 244 245 // Create MockServices. Expect logWriteVerificationError() to be 246 // called once. 247 MockServices services{}; 248 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 249 MockJournal& journal = services.getMockJournal(); 250 EXPECT_CALL(errorLogging, logWriteVerificationError( 251 Entry::Level::Warning, Ref(journal), 252 "/xyz/openbmc_project/inventory/system/" 253 "chassis/motherboard/reg1")) 254 .Times(1); 255 256 // Log error based on the nested exception 257 ErrorHistory history{}; 258 error_logging_utils::logError(eptr, Entry::Level::Warning, services, 259 history); 260 261 // Try to log error again. Should not happen due to ErrorHistory. 262 error_logging_utils::logError(eptr, Entry::Level::Warning, services, 263 history); 264 } 265 266 // Test where exception is a I2CException 267 { 268 std::exception_ptr eptr; 269 try 270 { 271 throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8", 272 0x30, ENODEV}; 273 } 274 catch (...) 275 { 276 eptr = std::current_exception(); 277 } 278 279 // Create MockServices. Expect logI2CError() to be called once. 280 MockServices services{}; 281 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 282 MockJournal& journal = services.getMockJournal(); 283 EXPECT_CALL(errorLogging, 284 logI2CError(Entry::Level::Informational, Ref(journal), 285 "/dev/i2c-8", 0x30, ENODEV)) 286 .Times(1); 287 288 // Log error based on the nested exception 289 ErrorHistory history{}; 290 error_logging_utils::logError(eptr, Entry::Level::Informational, 291 services, history); 292 293 // Try to log error again. Should not happen due to ErrorHistory. 294 error_logging_utils::logError(eptr, Entry::Level::Informational, 295 services, history); 296 } 297 298 // Test where exception is a sdbusplus::exception_t 299 { 300 std::exception_ptr eptr; 301 try 302 { 303 // Throw TestSDBusError; exception_t is a pure virtual base class 304 throw TestSDBusError{"DBusError: Invalid object path."}; 305 } 306 catch (...) 307 { 308 eptr = std::current_exception(); 309 } 310 311 // Create MockServices. Expect logDBusError() to be called once. 312 MockServices services{}; 313 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 314 MockJournal& journal = services.getMockJournal(); 315 EXPECT_CALL(errorLogging, 316 logDBusError(Entry::Level::Debug, Ref(journal))) 317 .Times(1); 318 319 // Log error based on the nested exception 320 ErrorHistory history{}; 321 error_logging_utils::logError(eptr, Entry::Level::Debug, services, 322 history); 323 324 // Try to log error again. Should not happen due to ErrorHistory. 325 error_logging_utils::logError(eptr, Entry::Level::Debug, services, 326 history); 327 } 328 329 // Test where exception is a std::exception 330 { 331 std::exception_ptr eptr; 332 try 333 { 334 throw std::runtime_error{ 335 "Unable to read configuration file: No such file or directory"}; 336 } 337 catch (...) 338 { 339 eptr = std::current_exception(); 340 } 341 342 // Create MockServices. Expect logInternalError() to be called once. 343 MockServices services{}; 344 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 345 MockJournal& journal = services.getMockJournal(); 346 EXPECT_CALL(errorLogging, 347 logInternalError(Entry::Level::Error, Ref(journal))) 348 .Times(1); 349 350 // Log error based on the nested exception 351 ErrorHistory history{}; 352 error_logging_utils::logError(eptr, Entry::Level::Error, services, 353 history); 354 355 // Try to log error again. Should not happen due to ErrorHistory. 356 error_logging_utils::logError(eptr, Entry::Level::Error, services, 357 history); 358 } 359 360 // Test where exception is unknown type 361 { 362 std::exception_ptr eptr; 363 try 364 { 365 throw 23; 366 } 367 catch (...) 368 { 369 eptr = std::current_exception(); 370 } 371 372 // Create MockServices. Expect logInternalError() to be called once. 373 MockServices services{}; 374 MockErrorLogging& errorLogging = services.getMockErrorLogging(); 375 MockJournal& journal = services.getMockJournal(); 376 EXPECT_CALL(errorLogging, 377 logInternalError(Entry::Level::Warning, Ref(journal))) 378 .Times(1); 379 380 // Log error based on the nested exception 381 ErrorHistory history{}; 382 error_logging_utils::logError(eptr, Entry::Level::Warning, services, 383 history); 384 385 // Try to log error again. Should not happen due to ErrorHistory. 386 error_logging_utils::logError(eptr, Entry::Level::Warning, services, 387 history); 388 } 389 } 390 391 TEST(ErrorLoggingUtilsTests, GetExceptionToLog) 392 { 393 // Test where exception is not nested 394 { 395 std::exception_ptr eptr; 396 try 397 { 398 throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8", 399 0x30, ENODEV}; 400 } 401 catch (...) 402 { 403 eptr = std::current_exception(); 404 } 405 406 std::exception_ptr exceptionToLog = 407 error_logging_utils::internal::getExceptionToLog(eptr); 408 EXPECT_EQ(eptr, exceptionToLog); 409 } 410 411 // Test where exception is nested: Highest priority is innermost exception 412 { 413 std::exception_ptr inner, outer; 414 try 415 { 416 try 417 { 418 throw PMBusError{ 419 "VOUT_MODE contains unsupported data format", "reg1", 420 "/xyz/openbmc_project/inventory/system/chassis/" 421 "motherboard/reg1"}; 422 } 423 catch (...) 424 { 425 inner = std::current_exception(); 426 std::throw_with_nested( 427 std::runtime_error{"Unable to set output voltage"}); 428 } 429 } 430 catch (...) 431 { 432 outer = std::current_exception(); 433 } 434 435 std::exception_ptr exceptionToLog = 436 error_logging_utils::internal::getExceptionToLog(outer); 437 EXPECT_EQ(inner, exceptionToLog); 438 } 439 440 // Test where exception is nested: Highest priority is middle exception 441 { 442 std::exception_ptr inner, middle, outer; 443 try 444 { 445 try 446 { 447 try 448 { 449 throw std::invalid_argument{"JSON element is not an array"}; 450 } 451 catch (...) 452 { 453 inner = std::current_exception(); 454 std::throw_with_nested(ConfigFileParserError{ 455 fs::path{"/etc/phosphor-regulators/config.json"}, 456 "Unable to parse JSON configuration file"}); 457 } 458 } 459 catch (...) 460 { 461 middle = std::current_exception(); 462 std::throw_with_nested( 463 std::runtime_error{"Unable to load config file"}); 464 } 465 } 466 catch (...) 467 { 468 outer = std::current_exception(); 469 } 470 471 std::exception_ptr exceptionToLog = 472 error_logging_utils::internal::getExceptionToLog(outer); 473 EXPECT_EQ(middle, exceptionToLog); 474 } 475 476 // Test where exception is nested: Highest priority is outermost exception 477 { 478 std::exception_ptr inner, outer; 479 try 480 { 481 try 482 { 483 throw std::invalid_argument{"JSON element is not an array"}; 484 } 485 catch (...) 486 { 487 inner = std::current_exception(); 488 std::throw_with_nested(ConfigFileParserError{ 489 fs::path{"/etc/phosphor-regulators/config.json"}, 490 "Unable to parse JSON configuration file"}); 491 } 492 } 493 catch (...) 494 { 495 outer = std::current_exception(); 496 } 497 498 std::exception_ptr exceptionToLog = 499 error_logging_utils::internal::getExceptionToLog(outer); 500 EXPECT_EQ(outer, exceptionToLog); 501 } 502 503 // Test where exception is nested: Two exceptions have same priority. 504 // Should return outermost exception with that priority. 505 { 506 std::exception_ptr inner, outer; 507 try 508 { 509 try 510 { 511 throw std::invalid_argument{"JSON element is not an array"}; 512 } 513 catch (...) 514 { 515 inner = std::current_exception(); 516 std::throw_with_nested( 517 std::runtime_error{"Unable to load config file"}); 518 } 519 } 520 catch (...) 521 { 522 outer = std::current_exception(); 523 } 524 525 std::exception_ptr exceptionToLog = 526 error_logging_utils::internal::getExceptionToLog(outer); 527 EXPECT_EQ(outer, exceptionToLog); 528 } 529 530 // Test where exception is nested: Highest priority is ConfigFileParserError 531 { 532 std::exception_ptr inner, outer; 533 try 534 { 535 try 536 { 537 throw ConfigFileParserError{ 538 fs::path{"/etc/phosphor-regulators/config.json"}, 539 "Unable to parse JSON configuration file"}; 540 } 541 catch (...) 542 { 543 inner = std::current_exception(); 544 std::throw_with_nested( 545 std::runtime_error{"Unable to load config file"}); 546 } 547 } 548 catch (...) 549 { 550 outer = std::current_exception(); 551 } 552 553 std::exception_ptr exceptionToLog = 554 error_logging_utils::internal::getExceptionToLog(outer); 555 EXPECT_EQ(inner, exceptionToLog); 556 } 557 558 // Test where exception is nested: Highest priority is PMBusError 559 { 560 std::exception_ptr inner, outer; 561 try 562 { 563 try 564 { 565 throw std::invalid_argument{"Invalid VOUT_MODE value"}; 566 } 567 catch (...) 568 { 569 inner = std::current_exception(); 570 std::throw_with_nested(PMBusError{ 571 "VOUT_MODE contains unsupported data format", "reg1", 572 "/xyz/openbmc_project/inventory/system/chassis/motherboard/" 573 "reg1"}); 574 } 575 } 576 catch (...) 577 { 578 outer = std::current_exception(); 579 } 580 581 std::exception_ptr exceptionToLog = 582 error_logging_utils::internal::getExceptionToLog(outer); 583 EXPECT_EQ(outer, exceptionToLog); 584 } 585 586 // Test where exception is nested: Highest priority is 587 // WriteVerificationError 588 { 589 std::exception_ptr inner, outer; 590 try 591 { 592 try 593 { 594 throw WriteVerificationError{ 595 "value_written: 0xDEAD, value_read: 0xBEEF", "reg1", 596 "/xyz/openbmc_project/inventory/system/chassis/motherboard/" 597 "reg1"}; 598 } 599 catch (...) 600 { 601 inner = std::current_exception(); 602 std::throw_with_nested( 603 std::runtime_error{"Unable set voltage"}); 604 } 605 } 606 catch (...) 607 { 608 outer = std::current_exception(); 609 } 610 611 std::exception_ptr exceptionToLog = 612 error_logging_utils::internal::getExceptionToLog(outer); 613 EXPECT_EQ(inner, exceptionToLog); 614 } 615 616 // Test where exception is nested: Highest priority is I2CException 617 { 618 std::exception_ptr inner, outer; 619 try 620 { 621 try 622 { 623 throw std::invalid_argument{"No such device"}; 624 } 625 catch (...) 626 { 627 inner = std::current_exception(); 628 std::throw_with_nested(i2c::I2CException{ 629 "Unable to open device reg1", "/dev/i2c-8", 0x30, ENODEV}); 630 } 631 } 632 catch (...) 633 { 634 outer = std::current_exception(); 635 } 636 637 std::exception_ptr exceptionToLog = 638 error_logging_utils::internal::getExceptionToLog(outer); 639 EXPECT_EQ(outer, exceptionToLog); 640 } 641 642 // Test where exception is nested: Highest priority is 643 // sdbusplus::exception_t 644 { 645 std::exception_ptr inner, outer; 646 try 647 { 648 try 649 { 650 // Throw TestSDBusError; exception_t is pure virtual class 651 throw TestSDBusError{"DBusError: Invalid object path."}; 652 } 653 catch (...) 654 { 655 inner = std::current_exception(); 656 std::throw_with_nested( 657 std::runtime_error{"Unable to call D-Bus method"}); 658 } 659 } 660 catch (...) 661 { 662 outer = std::current_exception(); 663 } 664 665 std::exception_ptr exceptionToLog = 666 error_logging_utils::internal::getExceptionToLog(outer); 667 EXPECT_EQ(inner, exceptionToLog); 668 } 669 670 // Test where exception is nested: Highest priority is std::exception 671 { 672 std::exception_ptr inner, outer; 673 try 674 { 675 try 676 { 677 throw std::invalid_argument{"No such file or directory"}; 678 } 679 catch (...) 680 { 681 inner = std::current_exception(); 682 std::throw_with_nested( 683 std::runtime_error{"Unable load config file"}); 684 } 685 } 686 catch (...) 687 { 688 outer = std::current_exception(); 689 } 690 691 std::exception_ptr exceptionToLog = 692 error_logging_utils::internal::getExceptionToLog(outer); 693 EXPECT_EQ(outer, exceptionToLog); 694 } 695 696 // Test where exception is nested: Highest priority is unknown type 697 { 698 std::exception_ptr inner, outer; 699 try 700 { 701 try 702 { 703 throw 23; 704 } 705 catch (...) 706 { 707 inner = std::current_exception(); 708 std::throw_with_nested(std::string{"Unable load config file"}); 709 } 710 } 711 catch (...) 712 { 713 outer = std::current_exception(); 714 } 715 716 std::exception_ptr exceptionToLog = 717 error_logging_utils::internal::getExceptionToLog(outer); 718 EXPECT_EQ(outer, exceptionToLog); 719 } 720 } 721