SdBusError: Add test cases

This adds basic unit testing to the SdBusError class so we have full
coverage of all the functionality.

Tested:
    Ran through the unit test suite including the changes that build
    valgrind runs and code coverage suppport.

Change-Id: I6d3bdbd2e0332ae5372796cb2a380ccddbee10ec
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/Makefile.am b/test/Makefile.am
index 0c71949..442d084 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -24,6 +24,10 @@
 bus_match_SOURCES = bus/match.cpp
 bus_match_LDADD = $(gtest_ldadd)
 
+check_PROGRAMS += exception_sdbus_error
+exception_sdbus_error_SOURCES = exception/sdbus_error.cpp
+exception_sdbus_error_LDADD = $(gtest_ldadd)
+
 check_PROGRAMS += message_append
 message_append_SOURCES = message/append.cpp
 message_append_CXXFLAGS = $(legacy_test_cxxflags)
diff --git a/test/exception/sdbus_error.cpp b/test/exception/sdbus_error.cpp
new file mode 100644
index 0000000..02d063d
--- /dev/null
+++ b/test/exception/sdbus_error.cpp
@@ -0,0 +1,153 @@
+#include <cstdlib>
+#include <gtest/gtest.h>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/test/sdbus_mock.hpp>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#include <systemd/sd-bus.h>
+#include <utility>
+
+// Needed for constuctor error testing
+extern sdbusplus::SdBusImpl sdbus_impl;
+
+namespace
+{
+
+using sdbusplus::exception::SdBusError;
+using testing::Return;
+using testing::_;
+
+std::error_code errnoToErrorCode(int error)
+{
+    return std::error_code(error, std::generic_category());
+}
+
+TEST(SdBusError, BasicErrno)
+{
+    const int errorVal = EBUSY;
+    const std::string prefix = "BasicErrno";
+
+    // Build the reference sd_bus_error
+    sd_bus_error error = SD_BUS_ERROR_NULL;
+    EXPECT_EQ(-errorVal, sd_bus_error_set_errno(&error, errorVal));
+    EXPECT_TRUE(sd_bus_error_is_set(&error));
+
+    // Build the SdBusError
+    SdBusError err(errorVal, prefix.c_str());
+
+    // Make sure inheritance is defined correctly
+    sdbusplus::exception::exception& sdbusErr = err;
+    EXPECT_EQ(std::string{error.name}, sdbusErr.name());
+    EXPECT_EQ(std::string{error.message}, sdbusErr.description());
+    std::system_error& systemErr = err;
+    EXPECT_EQ(errnoToErrorCode(errorVal), systemErr.code());
+    std::exception& stdErr = sdbusErr;
+    EXPECT_EQ(prefix + ": " + error.name + ": " + error.message, stdErr.what());
+
+    sd_bus_error_free(&error);
+}
+
+TEST(SdBusError, EnomemErrno)
+{
+    // Make sure no exception is thrown on construction
+    SdBusError err(ENOMEM, "EnomemErrno");
+}
+
+TEST(SdBusError, NotSetErrno)
+{
+    const int errorVal = EBUSY;
+
+    sdbusplus::SdBusMock sdbus;
+    EXPECT_CALL(sdbus, sd_bus_error_set_errno(_, errorVal))
+        .Times(1)
+        .WillOnce(Return(errorVal));
+    EXPECT_CALL(sdbus, sd_bus_error_is_set(_)).Times(1).WillOnce(Return(false));
+    EXPECT_THROW(SdBusError(errorVal, "NotSetErrno", &sdbus),
+                 std::runtime_error);
+}
+
+TEST(SdBusError, Move)
+{
+    const int errorVal = EIO;
+    const std::string prefix = "Move";
+
+    // Build the reference sd_bus_error
+    sd_bus_error error = SD_BUS_ERROR_NULL;
+    EXPECT_EQ(-errorVal, sd_bus_error_set_errno(&error, errorVal));
+    EXPECT_TRUE(sd_bus_error_is_set(&error));
+    const std::string name{error.name};
+    const std::string message{error.message};
+    const std::string what = prefix + ": " + error.name + ": " + error.message;
+
+    SdBusError errFinal(EBUSY, "Move2");
+    // Nest to make sure RAII works for moves
+    {
+        // Build our first SdBusError
+        SdBusError err(errorVal, prefix.c_str());
+
+        EXPECT_EQ(name, err.name());
+        EXPECT_EQ(message, err.description());
+        EXPECT_EQ(what, err.what());
+        EXPECT_EQ(errnoToErrorCode(errorVal), err.code());
+
+        // Move our SdBusError to a new one
+        SdBusError errNew(std::move(err));
+
+        // Ensure the old object was cleaned up
+        EXPECT_EQ(nullptr, err.name());
+        EXPECT_EQ(nullptr, err.description());
+        EXPECT_EQ(std::string{}, err.what());
+
+        // Ensure our new object has the same data but moved
+        EXPECT_EQ(name, errNew.name());
+        EXPECT_EQ(message, errNew.description());
+        EXPECT_EQ(what, errNew.what());
+        EXPECT_EQ(errnoToErrorCode(errorVal), errNew.code());
+
+        // Move our SdBusError using the operator=()
+        errFinal = std::move(errNew);
+
+        // Ensure the old object was cleaned up
+        EXPECT_EQ(nullptr, errNew.name());
+        EXPECT_EQ(nullptr, errNew.description());
+        EXPECT_EQ(std::string{}, errNew.what());
+    }
+
+    // Ensure our new object has the same data but moved
+    EXPECT_EQ(name, errFinal.name());
+    EXPECT_EQ(message, errFinal.description());
+    EXPECT_EQ(what, errFinal.what());
+    EXPECT_EQ(errnoToErrorCode(errorVal), errFinal.code());
+
+    sd_bus_error_free(&error);
+}
+
+TEST(SdBusError, BasicError)
+{
+    const std::string name = "org.freedesktop.DBus.Error.Failed";
+    const std::string description = "TestCase";
+    const std::string prefix = "BasicError";
+
+    sd_bus_error error = SD_BUS_ERROR_NULL;
+    sd_bus_error_set(&error, name.c_str(), description.c_str());
+    EXPECT_TRUE(sd_bus_error_is_set(&error));
+    const char* nameBeforeMove = error.name;
+    const int errorVal = sd_bus_error_get_errno(&error);
+    SdBusError err(&error, prefix.c_str());
+
+    // We expect a move not copy
+    EXPECT_EQ(nameBeforeMove, err.name());
+
+    // The SdBusError should have moved our error so it should be freeable
+    EXPECT_FALSE(sd_bus_error_is_set(&error));
+    sd_bus_error_free(&error);
+    sd_bus_error_free(&error);
+
+    EXPECT_EQ(name, err.name());
+    EXPECT_EQ(description, err.description());
+    EXPECT_EQ(prefix + ": " + name + ": " + description, err.what());
+    EXPECT_EQ(errnoToErrorCode(errorVal), err.code());
+}
+
+} // namespace