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