blob: c12a977c02ec867984e47ada55c54e9f966b128f [file] [log] [blame]
/**
* Copyright © 2021 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "config_file_parser_error.hpp"
#include "error_logging.hpp"
#include "error_logging_utils.hpp"
#include "i2c_interface.hpp"
#include "journal.hpp"
#include "mock_error_logging.hpp"
#include "mock_journal.hpp"
#include "mock_services.hpp"
#include "pmbus_error.hpp"
#include "test_sdbus_error.hpp"
#include "write_verification_error.hpp"
#include <errno.h>
#include <sdbusplus/exception.hpp>
#include <exception>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace phosphor::power::regulators;
namespace fs = std::filesystem;
using ::testing::Ref;
TEST(ErrorLoggingUtilsTests, LogError_3Parameters)
{
// Create exception with two nesting levels; top priority is inner
// PMBusError
std::exception_ptr eptr;
try
{
try
{
throw PMBusError{"VOUT_MODE contains unsupported data format",
"reg1",
"/xyz/openbmc_project/inventory/system/chassis/"
"motherboard/reg1"};
}
catch (...)
{
std::throw_with_nested(
std::runtime_error{"Unable to set output voltage"});
}
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logPMBusError() to be called.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(
errorLogging,
logPMBusError(
Entry::Level::Error, Ref(journal),
"/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1"))
.Times(1);
// Log error based on the nested exception
error_logging_utils::logError(eptr, Entry::Level::Error, services);
}
TEST(ErrorLoggingUtilsTests, LogError_4Parameters)
{
// Test where exception pointer is null
{
std::exception_ptr eptr;
// Create MockServices. Don't expect any log*() methods to be called.
MockServices services{};
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
}
// Test where exception is not nested
{
std::exception_ptr eptr;
try
{
throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
0x30, ENODEV};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logI2CError() to be called.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logI2CError(Entry::Level::Critical, Ref(journal),
"/dev/i2c-8", 0x30, ENODEV))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Critical, services,
history);
}
// Test where exception is nested
{
std::exception_ptr eptr;
try
{
try
{
throw std::invalid_argument{"JSON element is not an array"};
}
catch (...)
{
std::throw_with_nested(ConfigFileParserError{
fs::path{"/etc/phosphor-regulators/config.json"},
"Unable to parse JSON configuration file"});
}
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logConfigFileError() to be called.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logConfigFileError(Entry::Level::Warning, Ref(journal)))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Warning, services,
history);
}
// Test where exception is a ConfigFileParserError
{
std::exception_ptr eptr;
try
{
throw ConfigFileParserError{
fs::path{"/etc/phosphor-regulators/config.json"},
"Unable to parse JSON configuration file"};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logConfigFileError() to be called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logConfigFileError(Entry::Level::Error, Ref(journal)))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
}
// Test where exception is a PMBusError
{
std::exception_ptr eptr;
try
{
throw PMBusError{"VOUT_MODE contains unsupported data format",
"reg1",
"/xyz/openbmc_project/inventory/system/chassis/"
"motherboard/reg1"};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logPMBusError() to be called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logPMBusError(Entry::Level::Error, Ref(journal),
"/xyz/openbmc_project/inventory/system/"
"chassis/motherboard/reg1"))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
}
// Test where exception is a WriteVerificationError
{
std::exception_ptr eptr;
try
{
throw WriteVerificationError{
"value_written: 0xDEAD, value_read: 0xBEEF", "reg1",
"/xyz/openbmc_project/inventory/system/chassis/motherboard/"
"reg1"};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logWriteVerificationError() to be
// called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging, logWriteVerificationError(
Entry::Level::Warning, Ref(journal),
"/xyz/openbmc_project/inventory/system/"
"chassis/motherboard/reg1"))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Warning, services,
history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Warning, services,
history);
}
// Test where exception is a I2CException
{
std::exception_ptr eptr;
try
{
throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
0x30, ENODEV};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logI2CError() to be called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logI2CError(Entry::Level::Informational, Ref(journal),
"/dev/i2c-8", 0x30, ENODEV))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Informational,
services, history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Informational,
services, history);
}
// Test where exception is a sdbusplus::exception_t
{
std::exception_ptr eptr;
try
{
// Throw TestSDBusError; exception_t is a pure virtual base class
throw TestSDBusError{"DBusError: Invalid object path."};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logDBusError() to be called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logDBusError(Entry::Level::Debug, Ref(journal)))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Debug, services,
history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Debug, services,
history);
}
// Test where exception is a std::exception
{
std::exception_ptr eptr;
try
{
throw std::runtime_error{
"Unable to read configuration file: No such file or directory"};
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logInternalError() to be called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logInternalError(Entry::Level::Error, Ref(journal)))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Error, services,
history);
}
// Test where exception is unknown type
{
std::exception_ptr eptr;
try
{
throw 23;
}
catch (...)
{
eptr = std::current_exception();
}
// Create MockServices. Expect logInternalError() to be called once.
MockServices services{};
MockErrorLogging& errorLogging = services.getMockErrorLogging();
MockJournal& journal = services.getMockJournal();
EXPECT_CALL(errorLogging,
logInternalError(Entry::Level::Warning, Ref(journal)))
.Times(1);
// Log error based on the nested exception
ErrorHistory history{};
error_logging_utils::logError(eptr, Entry::Level::Warning, services,
history);
// Try to log error again. Should not happen due to ErrorHistory.
error_logging_utils::logError(eptr, Entry::Level::Warning, services,
history);
}
}
TEST(ErrorLoggingUtilsTests, GetExceptionToLog)
{
// Test where exception is not nested
{
std::exception_ptr eptr;
try
{
throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
0x30, ENODEV};
}
catch (...)
{
eptr = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(eptr);
EXPECT_EQ(eptr, exceptionToLog);
}
// Test where exception is nested: Highest priority is innermost exception
{
std::exception_ptr inner, outer;
try
{
try
{
throw PMBusError{
"VOUT_MODE contains unsupported data format", "reg1",
"/xyz/openbmc_project/inventory/system/chassis/"
"motherboard/reg1"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable to set output voltage"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(inner, exceptionToLog);
}
// Test where exception is nested: Highest priority is middle exception
{
std::exception_ptr inner, middle, outer;
try
{
try
{
try
{
throw std::invalid_argument{"JSON element is not an array"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(ConfigFileParserError{
fs::path{"/etc/phosphor-regulators/config.json"},
"Unable to parse JSON configuration file"});
}
}
catch (...)
{
middle = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable to load config file"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(middle, exceptionToLog);
}
// Test where exception is nested: Highest priority is outermost exception
{
std::exception_ptr inner, outer;
try
{
try
{
throw std::invalid_argument{"JSON element is not an array"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(ConfigFileParserError{
fs::path{"/etc/phosphor-regulators/config.json"},
"Unable to parse JSON configuration file"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(outer, exceptionToLog);
}
// Test where exception is nested: Two exceptions have same priority.
// Should return outermost exception with that priority.
{
std::exception_ptr inner, outer;
try
{
try
{
throw std::invalid_argument{"JSON element is not an array"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable to load config file"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(outer, exceptionToLog);
}
// Test where exception is nested: Highest priority is ConfigFileParserError
{
std::exception_ptr inner, outer;
try
{
try
{
throw ConfigFileParserError{
fs::path{"/etc/phosphor-regulators/config.json"},
"Unable to parse JSON configuration file"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable to load config file"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(inner, exceptionToLog);
}
// Test where exception is nested: Highest priority is PMBusError
{
std::exception_ptr inner, outer;
try
{
try
{
throw std::invalid_argument{"Invalid VOUT_MODE value"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(PMBusError{
"VOUT_MODE contains unsupported data format", "reg1",
"/xyz/openbmc_project/inventory/system/chassis/motherboard/"
"reg1"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(outer, exceptionToLog);
}
// Test where exception is nested: Highest priority is
// WriteVerificationError
{
std::exception_ptr inner, outer;
try
{
try
{
throw WriteVerificationError{
"value_written: 0xDEAD, value_read: 0xBEEF", "reg1",
"/xyz/openbmc_project/inventory/system/chassis/motherboard/"
"reg1"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable set voltage"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(inner, exceptionToLog);
}
// Test where exception is nested: Highest priority is I2CException
{
std::exception_ptr inner, outer;
try
{
try
{
throw std::invalid_argument{"No such device"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(i2c::I2CException{
"Unable to open device reg1", "/dev/i2c-8", 0x30, ENODEV});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(outer, exceptionToLog);
}
// Test where exception is nested: Highest priority is
// sdbusplus::exception_t
{
std::exception_ptr inner, outer;
try
{
try
{
// Throw TestSDBusError; exception_t is pure virtual class
throw TestSDBusError{"DBusError: Invalid object path."};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable to call D-Bus method"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(inner, exceptionToLog);
}
// Test where exception is nested: Highest priority is std::exception
{
std::exception_ptr inner, outer;
try
{
try
{
throw std::invalid_argument{"No such file or directory"};
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(
std::runtime_error{"Unable load config file"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(outer, exceptionToLog);
}
// Test where exception is nested: Highest priority is unknown type
{
std::exception_ptr inner, outer;
try
{
try
{
throw 23;
}
catch (...)
{
inner = std::current_exception();
std::throw_with_nested(std::string{"Unable load config file"});
}
}
catch (...)
{
outer = std::current_exception();
}
std::exception_ptr exceptionToLog =
error_logging_utils::internal::getExceptionToLog(outer);
EXPECT_EQ(outer, exceptionToLog);
}
}