regulators: Complete compare_vpd action

Complete implementation of the compare_vpd action in the JSON
configuration file.  For more information about this action see
https://github.com/openbmc/phosphor-power/blob/master/phosphor-regulators/docs/config_file/compare_vpd.md

Implement the execute() method of the CompareVPDAction class.  Obtain
the actual VPD keyword value from the VPD service and compare it with
the expected value.

Also improve doxygen for the execute() method of the
ComparePresenceAction class.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: Ieb93806245babe6782fef95209bed8eed5b32578
diff --git a/phosphor-regulators/src/actions/compare_presence_action.hpp b/phosphor-regulators/src/actions/compare_presence_action.hpp
index b16c98f..f2038d1 100644
--- a/phosphor-regulators/src/actions/compare_presence_action.hpp
+++ b/phosphor-regulators/src/actions/compare_presence_action.hpp
@@ -44,7 +44,8 @@
     /**
      * Constructor.
      *
-     * @param fru Field-Replaceable Unit (FRU)
+     * @param fru Field-Replaceable Unit (FRU). Specify the D-Bus inventory path
+     *            of the FRU.
      * @param value Expected presence value
      */
     explicit ComparePresenceAction(const std::string& fru, bool value) :
@@ -55,8 +56,13 @@
     /**
      * Executes this action.
      *
-     * @param environment Action execution environment.
-     * @return true
+     * Compares the actual presence value to the expected value.
+     *
+     * Throws an exception if an error occurs.
+     *
+     * @param environment action execution environment
+     * @return true if the actual presence value equals the expected value,
+     *         otherwise returns false
      */
     virtual bool execute(ActionEnvironment& environment) override;
 
@@ -91,7 +97,7 @@
     /**
      * Field-Replaceable Unit (FRU) for this action.
      *
-     * Specify the D-Bus inventory path of the FRU.
+     * The D-Bus inventory path of the FRU.
      */
     const std::string fru{};
 
diff --git a/phosphor-regulators/src/actions/compare_vpd_action.cpp b/phosphor-regulators/src/actions/compare_vpd_action.cpp
new file mode 100644
index 0000000..1049cee
--- /dev/null
+++ b/phosphor-regulators/src/actions/compare_vpd_action.cpp
@@ -0,0 +1,58 @@
+/**
+ * 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 "compare_vpd_action.hpp"
+
+#include "action_error.hpp"
+
+#include <exception>
+#include <sstream>
+
+namespace phosphor::power::regulators
+{
+
+bool CompareVPDAction::execute(ActionEnvironment& environment)
+{
+    bool isEqual{false};
+    try
+    {
+        // Get actual VPD keyword value
+        std::string actualValue =
+            environment.getServices().getVPD().getValue(fru, keyword);
+
+        // Check if actual value equals expected value
+        isEqual = (actualValue == value);
+    }
+    catch (const std::exception& e)
+    {
+        // Nest exception within an ActionError so the caller will have both the
+        // low level error information and the action information.
+        std::throw_with_nested(ActionError(*this));
+    }
+    return isEqual;
+}
+
+std::string CompareVPDAction::toString() const
+{
+    std::ostringstream ss;
+    ss << "compare_vpd: { ";
+    ss << "fru: " << fru << ", ";
+    ss << "keyword: " << keyword << ", ";
+    ss << "value: " << value << " }";
+    return ss.str();
+}
+
+} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/actions/compare_vpd_action.hpp b/phosphor-regulators/src/actions/compare_vpd_action.hpp
index 7dc5136..af7e085 100644
--- a/phosphor-regulators/src/actions/compare_vpd_action.hpp
+++ b/phosphor-regulators/src/actions/compare_vpd_action.hpp
@@ -18,7 +18,6 @@
 #include "action.hpp"
 #include "action_environment.hpp"
 
-#include <sstream>
 #include <string>
 
 namespace phosphor::power::regulators
@@ -62,16 +61,15 @@
     /**
      * Executes this action.
      *
-     * TODO: Not implemented yet
+     * Compares the actual VPD keyword value to the expected value.
      *
-     * @param environment Action execution environment.
-     * @return true
+     * Throws an exception if an error occurs.
+     *
+     * @param environment action execution environment
+     * @return true if the keyword value equals the expected value, otherwise
+     *         returns false
      */
-    virtual bool execute(ActionEnvironment& /* environment */) override
-    {
-        // TODO: Not implemented yet
-        return true;
-    }
+    virtual bool execute(ActionEnvironment& environment) override;
 
     /**
      * Returns the Field-Replaceable Unit (FRU).
@@ -108,20 +106,13 @@
      *
      * @return description of action
      */
-    virtual std::string toString() const override
-    {
-        std::ostringstream ss;
-        ss << "compare_vpd: { ";
-        ss << "fru: " << fru << ", ";
-        ss << "keyword: " << keyword << ", ";
-        ss << "value: " << value << " }";
-
-        return ss.str();
-    }
+    virtual std::string toString() const override;
 
   private:
     /**
-     * Field-Replaceable Unit (FRU) that contains the VPD.
+     * Field-Replaceable Unit (FRU) for this action.
+     *
+     * The D-Bus inventory path of the FRU.
      */
     const std::string fru{};
 
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index d7fc414..9993591 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -24,6 +24,7 @@
     'vpd.cpp',
 
     'actions/compare_presence_action.cpp',
+    'actions/compare_vpd_action.cpp',
     'actions/if_action.cpp',
     'actions/i2c_compare_bit_action.cpp',
     'actions/i2c_compare_byte_action.cpp',
diff --git a/phosphor-regulators/test/actions/compare_vpd_action_tests.cpp b/phosphor-regulators/test/actions/compare_vpd_action_tests.cpp
index d44e2e8..1cc7782 100644
--- a/phosphor-regulators/test/actions/compare_vpd_action_tests.cpp
+++ b/phosphor-regulators/test/actions/compare_vpd_action_tests.cpp
@@ -14,16 +14,24 @@
  * limitations under the License.
  */
 #include "action_environment.hpp"
+#include "action_error.hpp"
 #include "compare_vpd_action.hpp"
 #include "id_map.hpp"
+#include "mock_services.hpp"
+#include "mock_vpd.hpp"
 
 #include <exception>
 #include <memory>
 #include <stdexcept>
+#include <string>
 #include <utility>
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+using ::testing::Return;
+using ::testing::Throw;
+
 using namespace phosphor::power::regulators;
 
 TEST(CompareVPDActionTests, Constructor)
@@ -39,7 +47,123 @@
 
 TEST(CompareVPDActionTests, Execute)
 {
-    // TODO: Not implemented yet
+    // Test where works: Actual VPD value is not an empty string
+    {
+        std::string fru{"/xyz/openbmc_project/inventory/system"};
+        std::string keyword{"Model"};
+
+        // Create MockServices object.  VPD service will return "ABCD" as VPD
+        // value 4 times.
+        MockServices services{};
+        MockVPD& vpd = services.getMockVPD();
+        EXPECT_CALL(vpd, getValue(fru, keyword))
+            .Times(4)
+            .WillRepeatedly(Return("ABCD"));
+
+        IDMap idMap{};
+        ActionEnvironment environment{idMap, "", services};
+
+        // Test where returns true: actual value == expected value
+        {
+            CompareVPDAction action{fru, keyword, "ABCD"};
+            EXPECT_TRUE(action.execute(environment));
+        }
+
+        // Test where returns false: actual value != expected value
+        {
+            CompareVPDAction action{fru, keyword, "BEEF"};
+            EXPECT_FALSE(action.execute(environment));
+        }
+
+        // Test where returns false: expected value differs by case
+        {
+            CompareVPDAction action{fru, keyword, "abcd"};
+            EXPECT_FALSE(action.execute(environment));
+        }
+
+        // Test where returns false: expected value is an empty string
+        {
+            CompareVPDAction action{fru, keyword, ""};
+            EXPECT_FALSE(action.execute(environment));
+        }
+    }
+
+    // Test where works: Actual VPD value is an empty string
+    {
+        std::string fru{"/xyz/openbmc_project/inventory/system"};
+        std::string keyword{"Model"};
+
+        // Create MockServices object.  VPD service will return "" as VPD value
+        // 2 times.
+        MockServices services{};
+        MockVPD& vpd = services.getMockVPD();
+        EXPECT_CALL(vpd, getValue(fru, keyword))
+            .Times(2)
+            .WillRepeatedly(Return(""));
+
+        IDMap idMap{};
+        ActionEnvironment environment{idMap, "", services};
+
+        // Test where returns true: actual value == expected value
+        {
+            CompareVPDAction action{fru, keyword, ""};
+            EXPECT_TRUE(action.execute(environment));
+        }
+
+        // Test where returns false: actual value != expected value
+        {
+            CompareVPDAction action{fru, keyword, "ABCD"};
+            EXPECT_FALSE(action.execute(environment));
+        }
+    }
+
+    // Test where fails: Exception thrown when trying to get actual VPD value
+    {
+        std::string fru{"/xyz/openbmc_project/inventory/system"};
+        std::string keyword{"Model"};
+
+        // Create MockServices object.  VPD service will throw an exception.
+        MockServices services{};
+        MockVPD& vpd = services.getMockVPD();
+        EXPECT_CALL(vpd, getValue(fru, keyword))
+            .Times(1)
+            .WillOnce(
+                Throw(std::runtime_error{"D-Bus error: Invalid object path"}));
+
+        IDMap idMap{};
+        ActionEnvironment environment{idMap, "", services};
+
+        try
+        {
+            CompareVPDAction action{fru, keyword, "ABCD"};
+            action.execute(environment);
+            ADD_FAILURE() << "Should not have reached this line.";
+        }
+        catch (const ActionError& e)
+        {
+            EXPECT_STREQ(e.what(), "ActionError: compare_vpd: { fru: "
+                                   "/xyz/openbmc_project/inventory/system, "
+                                   "keyword: Model, value: ABCD }");
+            try
+            {
+                // Re-throw inner exception
+                std::rethrow_if_nested(e);
+                ADD_FAILURE() << "Should not have reached this line.";
+            }
+            catch (const std::runtime_error& re)
+            {
+                EXPECT_STREQ(re.what(), "D-Bus error: Invalid object path");
+            }
+            catch (...)
+            {
+                ADD_FAILURE() << "Should not have caught exception.";
+            }
+        }
+        catch (...)
+        {
+            ADD_FAILURE() << "Should not have caught exception.";
+        }
+    }
 }
 
 TEST(CompareVPDActionTests, GetFRU)