1 #include "activation.hpp" 2 #include "mocked_activation_listener.hpp" 3 #include "mocked_association_interface.hpp" 4 #include "mocked_utils.hpp" 5 6 #include <sdbusplus/test/sdbus_mock.hpp> 7 8 #include <gmock/gmock.h> 9 #include <gtest/gtest.h> 10 11 using namespace phosphor::software::updater; 12 13 using ::testing::_; 14 using ::testing::Return; 15 using ::testing::StrEq; 16 17 using std::any; 18 19 class TestActivation : public ::testing::Test 20 { 21 public: 22 using PropertyType = utils::UtilsInterface::PropertyType; 23 using Status = Activation::Status; 24 using RequestedStatus = Activation::RequestedActivations; 25 TestActivation() : 26 mockedUtils( 27 reinterpret_cast<const utils::MockedUtils&>(utils::getUtils())) 28 { 29 // By default make it compatible with the test software 30 ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER))) 31 .WillByDefault(Return(any(PropertyType(std::string("TestManu"))))); 32 ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MODEL))) 33 .WillByDefault(Return(any(PropertyType(std::string("TestModel"))))); 34 ON_CALL(mockedUtils, isAssociated(_, _)).WillByDefault(Return(false)); 35 } 36 ~TestActivation() 37 { 38 utils::freeUtils(); 39 } 40 41 void onUpdateDone() 42 { 43 activation->onUpdateDone(); 44 } 45 void onUpdateFailed() 46 { 47 activation->onUpdateFailed(); 48 } 49 int getProgress() 50 { 51 return activation->activationProgress->progress(); 52 } 53 const auto& getPsuQueue() 54 { 55 return activation->psuQueue; 56 } 57 std::string getUpdateService(const std::string& psuInventoryPath) 58 { 59 return activation->getUpdateService(psuInventoryPath); 60 } 61 62 sdbusplus::SdBusMock sdbusMock; 63 sdbusplus::bus_t mockedBus = sdbusplus::get_mocked_new(&sdbusMock); 64 const utils::MockedUtils& mockedUtils; 65 MockedAssociationInterface mockedAssociationInterface; 66 MockedActivationListener mockedActivationListener; 67 std::unique_ptr<Activation> activation; 68 std::string versionId = "abcdefgh"; 69 std::string extVersion = "manufacturer=TestManu,model=TestModel"; 70 std::string filePath = "/tmp/images/abcdefgh"; 71 std::string dBusPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId; 72 Status status = Status::Ready; 73 AssociationList associations; 74 }; 75 76 TEST_F(TestActivation, ctordtor) 77 { 78 activation = std::make_unique<Activation>( 79 mockedBus, dBusPath, versionId, extVersion, status, associations, 80 filePath, &mockedAssociationInterface, &mockedActivationListener); 81 } 82 83 TEST_F(TestActivation, ctorWithInvalidExtVersion) 84 { 85 extVersion = "invalid text"; 86 activation = std::make_unique<Activation>( 87 mockedBus, dBusPath, versionId, extVersion, status, associations, 88 filePath, &mockedAssociationInterface, &mockedActivationListener); 89 } 90 91 TEST_F(TestActivation, getUpdateService) 92 { 93 std::string psuInventoryPath = "/com/example/inventory/powersupply1"; 94 std::string toCompare = "psu-update@-com-example-inventory-" 95 "powersupply1\\x20-tmp-images-12345678.service"; 96 versionId = "12345678"; 97 filePath = "/tmp/images/12345678"; 98 99 activation = std::make_unique<Activation>( 100 mockedBus, dBusPath, versionId, extVersion, status, associations, 101 filePath, &mockedAssociationInterface, &mockedActivationListener); 102 103 auto service = getUpdateService(psuInventoryPath); 104 EXPECT_EQ(toCompare, service); 105 } 106 107 TEST_F(TestActivation, doUpdateWhenNoPSU) 108 { 109 activation = std::make_unique<Activation>( 110 mockedBus, dBusPath, versionId, extVersion, status, associations, 111 filePath, &mockedAssociationInterface, &mockedActivationListener); 112 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 113 .WillByDefault( 114 Return(std::vector<std::string>({}))); // No PSU inventory 115 activation->requestedActivation(RequestedStatus::Active); 116 117 EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath)) 118 .Times(0); 119 EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath)) 120 .Times(0); 121 EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath)) 122 .Times(0); 123 EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0); 124 EXPECT_EQ(Status::Failed, activation->activation()); 125 } 126 127 TEST_F(TestActivation, doUpdateOnePSUOK) 128 { 129 constexpr auto psu0 = "/com/example/inventory/psu0"; 130 activation = std::make_unique<Activation>( 131 mockedBus, dBusPath, versionId, extVersion, status, associations, 132 filePath, &mockedAssociationInterface, &mockedActivationListener); 133 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 134 .WillByDefault( 135 Return(std::vector<std::string>({psu0}))); // One PSU inventory 136 activation->requestedActivation(RequestedStatus::Active); 137 138 EXPECT_EQ(Status::Activating, activation->activation()); 139 140 EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath)) 141 .Times(1); 142 EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath)) 143 .Times(1); 144 EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath)) 145 .Times(1); 146 EXPECT_CALL(mockedActivationListener, 147 onUpdateDone(StrEq(versionId), StrEq(psu0))) 148 .Times(1); 149 onUpdateDone(); 150 EXPECT_EQ(Status::Active, activation->activation()); 151 } 152 153 TEST_F(TestActivation, doUpdateFourPSUsOK) 154 { 155 constexpr auto psu0 = "/com/example/inventory/psu0"; 156 constexpr auto psu1 = "/com/example/inventory/psu1"; 157 constexpr auto psu2 = "/com/example/inventory/psu2"; 158 constexpr auto psu3 = "/com/example/inventory/psu3"; 159 activation = std::make_unique<Activation>( 160 mockedBus, dBusPath, versionId, extVersion, status, associations, 161 filePath, &mockedAssociationInterface, &mockedActivationListener); 162 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 163 .WillByDefault(Return( 164 std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs 165 activation->requestedActivation(RequestedStatus::Active); 166 167 EXPECT_EQ(Status::Activating, activation->activation()); 168 EXPECT_EQ(10, getProgress()); 169 170 EXPECT_CALL(mockedActivationListener, 171 onUpdateDone(StrEq(versionId), StrEq(psu0))) 172 .Times(1); 173 onUpdateDone(); 174 EXPECT_EQ(Status::Activating, activation->activation()); 175 EXPECT_EQ(30, getProgress()); 176 177 EXPECT_CALL(mockedActivationListener, 178 onUpdateDone(StrEq(versionId), StrEq(psu1))) 179 .Times(1); 180 onUpdateDone(); 181 EXPECT_EQ(Status::Activating, activation->activation()); 182 EXPECT_EQ(50, getProgress()); 183 184 EXPECT_CALL(mockedActivationListener, 185 onUpdateDone(StrEq(versionId), StrEq(psu2))) 186 .Times(1); 187 onUpdateDone(); 188 EXPECT_EQ(Status::Activating, activation->activation()); 189 EXPECT_EQ(70, getProgress()); 190 191 EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath)) 192 .Times(1); 193 EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath)) 194 .Times(1); 195 EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath)) 196 .Times(1); 197 198 EXPECT_CALL(mockedActivationListener, 199 onUpdateDone(StrEq(versionId), StrEq(psu3))) 200 .Times(1); 201 onUpdateDone(); 202 EXPECT_EQ(Status::Active, activation->activation()); 203 } 204 205 TEST_F(TestActivation, doUpdateFourPSUsFailonSecond) 206 { 207 constexpr auto psu0 = "/com/example/inventory/psu0"; 208 constexpr auto psu1 = "/com/example/inventory/psu1"; 209 constexpr auto psu2 = "/com/example/inventory/psu2"; 210 constexpr auto psu3 = "/com/example/inventory/psu3"; 211 activation = std::make_unique<Activation>( 212 mockedBus, dBusPath, versionId, extVersion, status, associations, 213 filePath, &mockedAssociationInterface, &mockedActivationListener); 214 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 215 .WillByDefault(Return( 216 std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs 217 activation->requestedActivation(RequestedStatus::Active); 218 219 EXPECT_EQ(Status::Activating, activation->activation()); 220 EXPECT_EQ(10, getProgress()); 221 222 EXPECT_CALL(mockedActivationListener, 223 onUpdateDone(StrEq(versionId), StrEq(psu0))) 224 .Times(1); 225 onUpdateDone(); 226 EXPECT_EQ(Status::Activating, activation->activation()); 227 EXPECT_EQ(30, getProgress()); 228 229 EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath)) 230 .Times(0); 231 EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath)) 232 .Times(0); 233 EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath)) 234 .Times(0); 235 EXPECT_CALL(mockedActivationListener, onUpdateDone(_, _)).Times(0); 236 onUpdateFailed(); 237 EXPECT_EQ(Status::Failed, activation->activation()); 238 } 239 240 TEST_F(TestActivation, doUpdateOnExceptionFromDbus) 241 { 242 constexpr auto psu0 = "/com/example/inventory/psu0"; 243 activation = std::make_unique<Activation>( 244 mockedBus, dBusPath, versionId, extVersion, status, associations, 245 filePath, &mockedAssociationInterface, &mockedActivationListener); 246 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 247 .WillByDefault( 248 Return(std::vector<std::string>({psu0}))); // One PSU inventory 249 ON_CALL(sdbusMock, sd_bus_call(_, _, _, _, nullptr)) 250 .WillByDefault(Return(-1)); // Make sdbus call failure 251 activation->requestedActivation(RequestedStatus::Active); 252 253 EXPECT_EQ(Status::Failed, activation->activation()); 254 } 255 256 TEST_F(TestActivation, doUpdateOnePSUModelNotCompatible) 257 { 258 constexpr auto psu0 = "/com/example/inventory/psu0"; 259 extVersion = "manufacturer=TestManu,model=DifferentModel"; 260 activation = std::make_unique<Activation>( 261 mockedBus, dBusPath, versionId, extVersion, status, associations, 262 filePath, &mockedAssociationInterface, &mockedActivationListener); 263 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 264 .WillByDefault(Return(std::vector<std::string>({psu0}))); 265 activation->requestedActivation(RequestedStatus::Active); 266 267 EXPECT_EQ(Status::Ready, activation->activation()); 268 } 269 270 TEST_F(TestActivation, doUpdateOnePSUManufactureNotCompatible) 271 { 272 constexpr auto psu0 = "/com/example/inventory/psu0"; 273 extVersion = "manufacturer=DifferentManu,model=TestModel"; 274 activation = std::make_unique<Activation>( 275 mockedBus, dBusPath, versionId, extVersion, status, associations, 276 filePath, &mockedAssociationInterface, &mockedActivationListener); 277 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 278 .WillByDefault(Return(std::vector<std::string>({psu0}))); 279 activation->requestedActivation(RequestedStatus::Active); 280 281 EXPECT_EQ(Status::Ready, activation->activation()); 282 } 283 284 TEST_F(TestActivation, doUpdateOnePSUSelfManufactureIsEmpty) 285 { 286 ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(MANUFACTURER))) 287 .WillByDefault(Return(any(PropertyType(std::string(""))))); 288 extVersion = "manufacturer=AnyManu,model=TestModel"; 289 // Below is the same as doUpdateOnePSUOK case 290 constexpr auto psu0 = "/com/example/inventory/psu0"; 291 activation = std::make_unique<Activation>( 292 mockedBus, dBusPath, versionId, extVersion, status, associations, 293 filePath, &mockedAssociationInterface, &mockedActivationListener); 294 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 295 .WillByDefault( 296 Return(std::vector<std::string>({psu0}))); // One PSU inventory 297 activation->requestedActivation(RequestedStatus::Active); 298 299 EXPECT_EQ(Status::Activating, activation->activation()); 300 301 EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath)) 302 .Times(1); 303 EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath)) 304 .Times(1); 305 EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath)) 306 .Times(1); 307 onUpdateDone(); 308 EXPECT_EQ(Status::Active, activation->activation()); 309 } 310 311 TEST_F(TestActivation, doUpdateFourPSUsSecondPSUNotCompatible) 312 { 313 constexpr auto psu0 = "/com/example/inventory/psu0"; 314 constexpr auto psu1 = "/com/example/inventory/psu1"; 315 constexpr auto psu2 = "/com/example/inventory/psu2"; 316 constexpr auto psu3 = "/com/example/inventory/psu3"; 317 ON_CALL(mockedUtils, getPropertyImpl(_, _, StrEq(psu1), _, StrEq(MODEL))) 318 .WillByDefault( 319 Return(any(PropertyType(std::string("DifferentModel"))))); 320 activation = std::make_unique<Activation>( 321 mockedBus, dBusPath, versionId, extVersion, status, associations, 322 filePath, &mockedAssociationInterface, &mockedActivationListener); 323 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 324 .WillByDefault(Return( 325 std::vector<std::string>({psu0, psu1, psu2, psu3}))); // 4 PSUs 326 activation->requestedActivation(RequestedStatus::Active); 327 328 const auto& psuQueue = getPsuQueue(); 329 EXPECT_EQ(3u, psuQueue.size()); 330 331 // Only 3 PSUs shall be updated, and psu1 shall be skipped 332 EXPECT_EQ(Status::Activating, activation->activation()); 333 EXPECT_EQ(10, getProgress()); 334 335 onUpdateDone(); 336 EXPECT_EQ(Status::Activating, activation->activation()); 337 EXPECT_EQ(36, getProgress()); 338 339 onUpdateDone(); 340 EXPECT_EQ(Status::Activating, activation->activation()); 341 EXPECT_EQ(62, getProgress()); 342 343 EXPECT_CALL(mockedAssociationInterface, createActiveAssociation(dBusPath)) 344 .Times(1); 345 EXPECT_CALL(mockedAssociationInterface, addFunctionalAssociation(dBusPath)) 346 .Times(1); 347 EXPECT_CALL(mockedAssociationInterface, addUpdateableAssociation(dBusPath)) 348 .Times(1); 349 350 onUpdateDone(); 351 EXPECT_EQ(Status::Active, activation->activation()); 352 } 353 354 TEST_F(TestActivation, doUpdateWhenNoFilePathInActiveState) 355 { 356 filePath = ""; 357 status = Status::Active; // Typically, a running PSU software is active 358 // without file path 359 constexpr auto psu0 = "/com/example/inventory/psu0"; 360 activation = std::make_unique<Activation>( 361 mockedBus, dBusPath, versionId, extVersion, status, associations, 362 filePath, &mockedAssociationInterface, &mockedActivationListener); 363 364 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 365 .WillByDefault( 366 Return(std::vector<std::string>({psu0}))); // One PSU inventory 367 368 // There shall be no DBus call to start update service 369 EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _, 370 StrEq("StartUnit"))) 371 .Times(0); 372 373 activation->requestedActivation(RequestedStatus::Active); 374 EXPECT_EQ(Status::Active, activation->activation()); 375 } 376 377 TEST_F(TestActivation, doUpdateWhenNoFilePathInReadyState) 378 { 379 filePath = ""; 380 status = Status::Ready; // Usually a Ready activation should have file path, 381 // but we are testing this case as well 382 constexpr auto psu0 = "/com/example/inventory/psu0"; 383 activation = std::make_unique<Activation>( 384 mockedBus, dBusPath, versionId, extVersion, status, associations, 385 filePath, &mockedAssociationInterface, &mockedActivationListener); 386 387 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 388 .WillByDefault( 389 Return(std::vector<std::string>({psu0}))); // One PSU inventory 390 391 // There shall be no DBus call to start update service 392 EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _, 393 StrEq("StartUnit"))) 394 .Times(0); 395 396 activation->requestedActivation(RequestedStatus::Active); 397 EXPECT_EQ(Status::Ready, activation->activation()); 398 } 399 400 TEST_F(TestActivation, doUpdateWhenPSUIsAssociated) 401 { 402 constexpr auto psu0 = "/com/example/inventory/psu0"; 403 status = Status::Active; // Typically, a running PSU software is associated 404 activation = std::make_unique<Activation>( 405 mockedBus, dBusPath, versionId, extVersion, status, associations, 406 filePath, &mockedAssociationInterface, &mockedActivationListener); 407 408 ON_CALL(mockedUtils, getPSUInventoryPath(_)) 409 .WillByDefault( 410 Return(std::vector<std::string>({psu0}))); // One PSU inventory 411 412 // When PSU is already associated, there shall be no DBus call to start 413 // update service 414 EXPECT_CALL(mockedUtils, isAssociated(StrEq(psu0), _)) 415 .WillOnce(Return(true)); 416 EXPECT_CALL(sdbusMock, sd_bus_message_new_method_call(_, _, _, _, _, 417 StrEq("StartUnit"))) 418 .Times(0); 419 420 activation->requestedActivation(RequestedStatus::Active); 421 EXPECT_EQ(Status::Active, activation->activation()); 422 } 423