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