regulators: Enhance exception utilities

Implement function that finds all exceptions within a nested exception
and returns them in a vector.

This function makes it easier to handle nested exceptions.  You can
iterate over them in a simple loop instead of writing recursive code.

The new function exists in the exception_utils namespace.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I36b135584ec6024625a572bbe0522643406245cd
diff --git a/phosphor-regulators/src/exception_utils.cpp b/phosphor-regulators/src/exception_utils.cpp
index 6f237e6..4640d0b 100644
--- a/phosphor-regulators/src/exception_utils.cpp
+++ b/phosphor-regulators/src/exception_utils.cpp
@@ -19,6 +19,13 @@
 namespace phosphor::power::regulators::exception_utils
 {
 
+std::vector<std::exception_ptr> getExceptions(std::exception_ptr eptr)
+{
+    std::vector<std::exception_ptr> exceptions;
+    internal::getExceptions(eptr, exceptions);
+    return exceptions;
+}
+
 std::vector<std::string> getMessages(const std::exception& e)
 {
     std::vector<std::string> messages{};
@@ -29,6 +36,30 @@
 namespace internal
 {
 
+void getExceptions(std::exception_ptr eptr,
+                   std::vector<std::exception_ptr>& exceptions)
+{
+    // Verify exception pointer is not null
+    if (eptr)
+    {
+        // If this exception is nested, add inner exception(s) to vector
+        try
+        {
+            std::rethrow_exception(eptr);
+        }
+        catch (const std::nested_exception& e)
+        {
+            getExceptions(e.nested_ptr(), exceptions);
+        }
+        catch (...)
+        {
+        }
+
+        // Append this exception to vector
+        exceptions.emplace_back(eptr);
+    }
+}
+
 void getMessages(const std::exception& e, std::vector<std::string>& messages)
 {
     // If this exception is nested, get messages from inner exception(s)
diff --git a/phosphor-regulators/src/exception_utils.hpp b/phosphor-regulators/src/exception_utils.hpp
index 81d700b..e90f0b6 100644
--- a/phosphor-regulators/src/exception_utils.hpp
+++ b/phosphor-regulators/src/exception_utils.hpp
@@ -30,6 +30,21 @@
 {
 
 /**
+ * Returns a vector containing the specified exception and any nested inner
+ * exceptions.
+ *
+ * If the exception contains nested inner exceptions, the returned vector will
+ * be ordered from innermost exception to outermost exception.
+ *
+ * This function makes it easier to handle nested exceptions.  You can iterate
+ * over them in a simple loop instead of writing a recursive function.
+ *
+ * @param eptr exception pointer
+ * @return vector of exceptions, from innermost to outermost
+ */
+std::vector<std::exception_ptr> getExceptions(std::exception_ptr eptr);
+
+/**
  * Gets the error messages from the specified exception and any nested inner
  * exceptions.
  *
@@ -49,6 +64,19 @@
 {
 
 /**
+ * Builds a vector containing the specified exception and any nested inner
+ * exceptions.
+ *
+ * Stores the exceptions in the specified vector, from innermost exception to
+ * outermost exception.
+ *
+ * @param eptr exception pointer
+ * @param exceptions vector where exceptions will be stored
+ */
+void getExceptions(std::exception_ptr eptr,
+                   std::vector<std::exception_ptr>& exceptions);
+
+/**
  * Gets the error messages from the specified exception and any nested inner
  * exceptions.
  *
diff --git a/phosphor-regulators/test/exception_utils_tests.cpp b/phosphor-regulators/test/exception_utils_tests.cpp
index 0d950ef..55045b5 100644
--- a/phosphor-regulators/test/exception_utils_tests.cpp
+++ b/phosphor-regulators/test/exception_utils_tests.cpp
@@ -26,6 +26,74 @@
 
 using namespace phosphor::power::regulators;
 
+TEST(ExceptionUtilsTests, GetExceptions)
+{
+    // Test where exception pointer is null
+    {
+        std::exception_ptr eptr;
+        std::vector<std::exception_ptr> exceptions =
+            exception_utils::getExceptions(eptr);
+        EXPECT_EQ(exceptions.size(), 0);
+    }
+
+    // Test where exception pointer is not null
+    {
+        // Create nested exception with two nesting levels
+        std::exception_ptr eptr;
+        try
+        {
+            try
+            {
+                throw std::logic_error{"JSON element is not an array"};
+            }
+            catch (...)
+            {
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to parse config file"});
+            }
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Get vector containing exceptions
+        std::vector<std::exception_ptr> exceptions =
+            exception_utils::getExceptions(eptr);
+        EXPECT_EQ(exceptions.size(), 2);
+
+        // Verify first exception in vector, which is the innermost exception
+        try
+        {
+            std::rethrow_exception(exceptions[0]);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const std::logic_error& e)
+        {
+            EXPECT_STREQ(e.what(), "JSON element is not an array");
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Unexpected exception type";
+        }
+
+        // Verify second exception in vector, which is the outermost exception
+        try
+        {
+            std::rethrow_exception(exceptions[1]);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const std::runtime_error& e)
+        {
+            EXPECT_STREQ(e.what(), "Unable to parse config file");
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Unexpected exception type";
+        }
+    }
+}
+
 TEST(ExceptionUtilsTests, GetMessages)
 {
     try
@@ -49,8 +117,132 @@
     }
 }
 
-// Test for getMessages() function in the internal namespace
-TEST(ExceptionUtilsTests, GetMessagesInternal)
+TEST(ExceptionUtilsTests, InternalGetExceptions)
+{
+    // Test where exception pointer is null
+    {
+        std::exception_ptr eptr;
+        std::vector<std::exception_ptr> exceptions;
+        exception_utils::internal::getExceptions(eptr, exceptions);
+        EXPECT_EQ(exceptions.size(), 0);
+    }
+
+    // Test where exception is not nested
+    {
+        // Create exception
+        std::exception_ptr eptr;
+        try
+        {
+            throw std::logic_error{"JSON element is not an array"};
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Build vector of exceptions
+        std::vector<std::exception_ptr> exceptions;
+        exception_utils::internal::getExceptions(eptr, exceptions);
+        EXPECT_EQ(exceptions.size(), 1);
+
+        // Verify exception in vector
+        try
+        {
+            std::rethrow_exception(exceptions[0]);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const std::logic_error& e)
+        {
+            EXPECT_STREQ(e.what(), "JSON element is not an array");
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Unexpected exception type";
+        }
+    }
+
+    // Test where exception is nested
+    {
+        // Throw exception with three levels of nesting
+        std::exception_ptr eptr;
+        try
+        {
+            try
+            {
+                try
+                {
+                    throw std::string{"Invalid JSON element"};
+                }
+                catch (...)
+                {
+                    std::throw_with_nested(
+                        std::logic_error{"JSON element is not an array"});
+                }
+            }
+            catch (...)
+            {
+                std::throw_with_nested(
+                    std::runtime_error{"Unable to parse config file"});
+            }
+        }
+        catch (...)
+        {
+            eptr = std::current_exception();
+        }
+
+        // Build vector of exceptions
+        std::vector<std::exception_ptr> exceptions;
+        exception_utils::internal::getExceptions(eptr, exceptions);
+        EXPECT_EQ(exceptions.size(), 3);
+
+        // Verify first exception in vector, which is the innermost exception
+        try
+        {
+            std::rethrow_exception(exceptions[0]);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const std::string& s)
+        {
+            EXPECT_EQ(s, "Invalid JSON element");
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Unexpected exception type";
+        }
+
+        // Verify second exception in vector
+        try
+        {
+            std::rethrow_exception(exceptions[1]);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const std::logic_error& e)
+        {
+            EXPECT_STREQ(e.what(), "JSON element is not an array");
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Unexpected exception type";
+        }
+
+        // Verify third exception in vector, which is the outermost exception
+        try
+        {
+            std::rethrow_exception(exceptions[2]);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const std::runtime_error& e)
+        {
+            EXPECT_STREQ(e.what(), "Unable to parse config file");
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Unexpected exception type";
+        }
+    }
+}
+
+TEST(ExceptionUtilsTests, InternalGetMessages)
 {
     // Test where exception is not nested
     {