Activation: Support to update multiple PSUs

Queue the update on multiple PSUs, and do the update one-by-one.

Tested: Write unit test cases and verify the cases pass.
        On witherspoon, verify the dummy update services are run on all
        PSUs.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: Icfe0f6e74623e54840df8d731d852d53d6071768
diff --git a/test/mocked_utils.hpp b/test/mocked_utils.hpp
index 7afe07c..85f5ffc 100644
--- a/test/mocked_utils.hpp
+++ b/test/mocked_utils.hpp
@@ -33,7 +33,7 @@
                            const char* propertyName));
 };
 
-const UtilsInterface& getUtils()
+inline const UtilsInterface& getUtils()
 {
     static MockedUtils utils;
     return utils;
diff --git a/test/test_activation.cpp b/test/test_activation.cpp
index 0ea3640..338dc60 100644
--- a/test/test_activation.cpp
+++ b/test/test_activation.cpp
@@ -1,4 +1,5 @@
 #include "activation.hpp"
+#include "mocked_utils.hpp"
 
 #include <sdbusplus/test/sdbus_mock.hpp>
 
@@ -7,24 +8,43 @@
 
 using namespace phosphor::software::updater;
 
+using ::testing::_;
+using ::testing::Return;
+
 class TestActivation : public ::testing::Test
 {
   public:
-    using ActivationStatus = sdbusplus::xyz::openbmc_project::Software::server::
-        Activation::Activations;
-    TestActivation()
+    using Status = Activation::Status;
+    using RequestedStatus = Activation::RequestedActivations;
+    TestActivation() :
+        mockedUtils(
+            reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
     {
     }
     ~TestActivation()
     {
     }
+
+    void onUpdateDone()
+    {
+        activation->onUpdateDone();
+    }
+    void onUpdateFailed()
+    {
+        activation->onUpdateFailed();
+    }
+    int getProgress()
+    {
+        return activation->activationProgress->progress();
+    }
     static constexpr auto dBusPath = SOFTWARE_OBJPATH;
     sdbusplus::SdBusMock sdbusMock;
     sdbusplus::bus::bus mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
+    const utils::MockedUtils& mockedUtils;
     std::unique_ptr<Activation> activation;
     std::string versionId = "abcdefgh";
     std::string extVersion = "Some Ext Version";
-    ActivationStatus status = ActivationStatus::Active;
+    Status status = Status::Ready;
     AssociationList associations;
 };
 
@@ -51,3 +71,102 @@
         psuInventoryPath, versionId);
     EXPECT_EQ(toCompare, service);
 }
+
+TEST_F(TestActivation, doUpdateWhenNoPSU)
+{
+    activation = std::make_unique<Activation>(mockedBus, dBusPath, versionId,
+                                              extVersion, status, associations);
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(
+            Return(std::vector<std::string>({}))); // No PSU inventory
+    activation->requestedActivation(RequestedStatus::Active);
+
+    EXPECT_EQ(Status::Failed, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateOnePSUOK)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    activation = std::make_unique<Activation>(mockedBus, dBusPath, versionId,
+                                              extVersion, status, associations);
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(
+            Return(std::vector<std::string>({psu0}))); // One PSU inventory
+    activation->requestedActivation(RequestedStatus::Active);
+
+    EXPECT_EQ(Status::Activating, activation->activation());
+
+    onUpdateDone();
+    EXPECT_EQ(Status::Active, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateFourPSUsOK)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    constexpr auto psu1 = "/com/example/inventory/psu1";
+    constexpr auto psu2 = "/com/example/inventory/psu2";
+    constexpr auto psu3 = "/com/example/inventory/psu3";
+    activation = std::make_unique<Activation>(mockedBus, dBusPath, versionId,
+                                              extVersion, status, associations);
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(Return(
+            std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
+    activation->requestedActivation(RequestedStatus::Active);
+
+    EXPECT_EQ(Status::Activating, activation->activation());
+    EXPECT_EQ(10, getProgress());
+
+    onUpdateDone();
+    EXPECT_EQ(Status::Activating, activation->activation());
+    EXPECT_EQ(30, getProgress());
+
+    onUpdateDone();
+    EXPECT_EQ(Status::Activating, activation->activation());
+    EXPECT_EQ(50, getProgress());
+
+    onUpdateDone();
+    EXPECT_EQ(Status::Activating, activation->activation());
+    EXPECT_EQ(70, getProgress());
+
+    onUpdateDone();
+    EXPECT_EQ(Status::Active, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateFourPSUsFailonSecond)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    constexpr auto psu1 = "/com/example/inventory/psu1";
+    constexpr auto psu2 = "/com/example/inventory/psu2";
+    constexpr auto psu3 = "/com/example/inventory/psu3";
+    activation = std::make_unique<Activation>(mockedBus, dBusPath, versionId,
+                                              extVersion, status, associations);
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(Return(
+            std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs
+    activation->requestedActivation(RequestedStatus::Active);
+
+    EXPECT_EQ(Status::Activating, activation->activation());
+    EXPECT_EQ(10, getProgress());
+
+    onUpdateDone();
+    EXPECT_EQ(Status::Activating, activation->activation());
+    EXPECT_EQ(30, getProgress());
+
+    onUpdateFailed();
+    EXPECT_EQ(Status::Failed, activation->activation());
+}
+
+TEST_F(TestActivation, doUpdateOnExceptionFromDbus)
+{
+    constexpr auto psu0 = "/com/example/inventory/psu0";
+    activation = std::make_unique<Activation>(mockedBus, dBusPath, versionId,
+                                              extVersion, status, associations);
+    ON_CALL(mockedUtils, getPSUInventoryPath(_))
+        .WillByDefault(
+            Return(std::vector<std::string>({psu0}))); // One PSU inventory
+    ON_CALL(sdbusMock, sd_bus_call(_, _, _, _, nullptr))
+        .WillByDefault(Return(-1)); // Make sdbus call failure
+    activation->requestedActivation(RequestedStatus::Active);
+
+    EXPECT_EQ(Status::Failed, activation->activation());
+}
diff --git a/test/test_item_updater.cpp b/test/test_item_updater.cpp
index 9f05ed2..b020c28 100644
--- a/test/test_item_updater.cpp
+++ b/test/test_item_updater.cpp
@@ -25,6 +25,8 @@
             reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
     {
         ON_CALL(mockedUtils, getVersionId(_)).WillByDefault(ReturnArg<0>());
+        ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(PRESENT)))
+            .WillByDefault(Return(any(PropertyType(true))));
     }
 
     ~TestItemUpdater()