xref: /openbmc/phosphor-pid-control/test/dbus_passive_unittest.cpp (revision a076487aab0ee71ea32d53f798d5ca6b31677278)
1 #include "conf.hpp"
2 #include "dbus/dbuspassive.hpp"
3 #include "test/dbushelper_mock.hpp"
4 
5 #include <sdbusplus/test/sdbus_mock.hpp>
6 
7 #include <functional>
8 #include <string>
9 #include <variant>
10 
11 #include <gmock/gmock.h>
12 #include <gtest/gtest.h>
13 
14 namespace pid_control
15 {
16 namespace
17 {
18 
19 using ::testing::_;
20 using ::testing::InSequence;
21 using ::testing::Invoke;
22 using ::testing::IsNull;
23 using ::testing::NotNull;
24 using ::testing::Return;
25 using ::testing::StrEq;
26 
27 std::string SensorIntf = "xyz.openbmc_project.Sensor.Value";
28 
29 TEST(DbusPassiveTest, FactoryFailsWithInvalidType)
30 {
31     // Verify the type is checked by the factory.
32 
33     sdbusplus::SdBusMock sdbus_mock;
34     auto bus_mock = sdbusplus::get_mocked_new(&sdbus_mock);
35     std::string type = "invalid";
36     std::string id = "id";
37 
38     DbusHelperMock helper;
39     auto info = conf::SensorConfig();
40 
41     std::unique_ptr<ReadInterface> ri = DbusPassive::createDbusPassive(
42         bus_mock, type, id, &helper, &info, nullptr);
43 
44     EXPECT_EQ(ri, nullptr);
45 }
46 
47 TEST(DbusPassiveTest, BoringConstructorTest)
48 {
49     // Simply build the object, does no error checking.
50 
51     sdbusplus::SdBusMock sdbus_mock;
52     auto bus_mock = sdbusplus::get_mocked_new(&sdbus_mock);
53     std::string type = "invalid";
54     std::string id = "id";
55     std::string path = "/xyz/openbmc_project/sensors/unknown/id";
56 
57     DbusHelperMock helper;
58     struct SensorProperties properties;
59 
60     DbusPassive(bus_mock, type, id, &helper, properties, false, path, nullptr);
61     // Success
62 }
63 
64 class DbusPassiveTestObj : public ::testing::Test
65 {
66   protected:
67     DbusPassiveTestObj() :
68         sdbus_mock(),
69         bus_mock(std::move(sdbusplus::get_mocked_new(&sdbus_mock))), helper()
70     {
71         EXPECT_CALL(helper, getService(_, StrEq(SensorIntf), StrEq(path)))
72             .WillOnce(Return("asdf"));
73 
74         EXPECT_CALL(helper,
75                     getProperties(_, StrEq("asdf"), StrEq(path), NotNull()))
76             .WillOnce(Invoke(
77                 [&](sdbusplus::bus::bus& bus, const std::string& service,
78                     const std::string& path, struct SensorProperties* prop) {
79                     prop->scale = _scale;
80                     prop->value = _value;
81                     prop->unit = "x";
82                     prop->min = 0;
83                     prop->max = 0;
84                 }));
85         EXPECT_CALL(helper, thresholdsAsserted(_, StrEq("asdf"), StrEq(path)))
86             .WillOnce(Return(false));
87 
88         auto info = conf::SensorConfig();
89         ri = DbusPassive::createDbusPassive(bus_mock, type, id, &helper, &info,
90                                             nullptr);
91         passive = reinterpret_cast<DbusPassive*>(ri.get());
92         EXPECT_FALSE(passive == nullptr);
93     }
94 
95     sdbusplus::SdBusMock sdbus_mock;
96     sdbusplus::bus::bus bus_mock;
97     DbusHelperMock helper;
98     std::string type = "temp";
99     std::string id = "id";
100     std::string path = "/xyz/openbmc_project/sensors/temperature/id";
101     int64_t _scale = -3;
102     int64_t _value = 10;
103 
104     std::unique_ptr<ReadInterface> ri;
105     DbusPassive* passive;
106 };
107 
108 TEST_F(DbusPassiveTestObj, ReadReturnsExpectedValues)
109 {
110     // Verify read is returning the values.
111     ReadReturn v;
112     v.value = 0.01;
113     // TODO: updated is set when the value is created, so we can range check
114     // it.
115     ReadReturn r = passive->read();
116     EXPECT_EQ(v.value, r.value);
117 }
118 
119 TEST_F(DbusPassiveTestObj, SetValueUpdatesValue)
120 {
121     // Verify setvalue does as advertised.
122 
123     double value = 0.01;
124     passive->setValue(value);
125 
126     // TODO: updated is set when the value is set, so we can range check it.
127     ReadReturn r = passive->read();
128     EXPECT_EQ(value, r.value);
129 }
130 
131 TEST_F(DbusPassiveTestObj, GetScaleReturnsExpectedValue)
132 {
133     // Verify the scale is returned as expected.
134     EXPECT_EQ(_scale, passive->getScale());
135 }
136 
137 TEST_F(DbusPassiveTestObj, getIDReturnsExpectedValue)
138 {
139     // Verify getID returns the expected value.
140     EXPECT_EQ(id, passive->getID());
141 }
142 
143 TEST_F(DbusPassiveTestObj, GetMinValueReturnsExpectedValue)
144 {
145     EXPECT_DOUBLE_EQ(0, passive->getMin());
146 }
147 
148 TEST_F(DbusPassiveTestObj, VerifyHandlesDbusSignal)
149 {
150     // The dbus passive sensor listens for updates and if it's the Value
151     // property, it needs to handle it.
152 
153     EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
154         .WillOnce(Return(nullptr));
155     sdbusplus::message::message msg(nullptr, &sdbus_mock);
156 
157     const char* Value = "Value";
158     int64_t xValue = 10000;
159     const char* intf = "xyz.openbmc_project.Sensor.Value";
160     // string, std::map<std::string, std::variant<int64_t>>
161     // msg.read(msgSensor, msgData);
162 
163     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
164         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
165             const char** s = static_cast<const char**>(p);
166             // Read the first parameter, the string.
167             *s = intf;
168             return 0;
169         }))
170         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
171             const char** s = static_cast<const char**>(p);
172             *s = Value;
173             // Read the string in the pair (dictionary).
174             return 0;
175         }));
176 
177     // std::map
178     EXPECT_CALL(sdbus_mock,
179                 sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
180         .WillOnce(Return(0));
181 
182     // while !at_end()
183     EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
184         .WillOnce(Return(0))
185         .WillOnce(Return(1)); // So it exits the loop after reading one pair.
186 
187     // std::pair
188     EXPECT_CALL(sdbus_mock,
189                 sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
190         .WillOnce(Return(0));
191 
192     EXPECT_CALL(sdbus_mock,
193                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
194         .WillOnce(Return(1));
195     EXPECT_CALL(sdbus_mock,
196                 sd_bus_message_enter_container(IsNull(), 'v', StrEq("x")))
197         .WillOnce(Return(0));
198 
199     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'x', NotNull()))
200         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
201             int64_t* s = static_cast<int64_t*>(p);
202             *s = xValue;
203             return 0;
204         }));
205 
206     EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
207         .WillOnce(Return(0))  /* variant. */
208         .WillOnce(Return(0))  /* std::pair */
209         .WillOnce(Return(0)); /* std::map */
210 
211     int rv = handleSensorValue(msg, passive);
212     EXPECT_EQ(rv, 0); // It's always 0.
213 
214     ReadReturn r = passive->read();
215     EXPECT_EQ(10, r.value);
216 }
217 
218 TEST_F(DbusPassiveTestObj, VerifyIgnoresOtherPropertySignal)
219 {
220     // The dbus passive sensor listens for updates and if it's the Value
221     // property, it needs to handle it.  In this case, it won't be.
222 
223     EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
224         .WillOnce(Return(nullptr));
225     sdbusplus::message::message msg(nullptr, &sdbus_mock);
226 
227     const char* Scale = "Scale";
228     int64_t xScale = -6;
229     const char* intf = "xyz.openbmc_project.Sensor.Value";
230     // string, std::map<std::string, std::variant<int64_t>>
231     // msg.read(msgSensor, msgData);
232 
233     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
234         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
235             const char** s = static_cast<const char**>(p);
236             // Read the first parameter, the string.
237             *s = intf;
238             return 0;
239         }))
240         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
241             const char** s = static_cast<const char**>(p);
242             *s = Scale;
243             // Read the string in the pair (dictionary).
244             return 0;
245         }));
246 
247     // std::map
248     EXPECT_CALL(sdbus_mock,
249                 sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
250         .WillOnce(Return(0));
251 
252     // while !at_end()
253     EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
254         .WillOnce(Return(0))
255         .WillOnce(Return(1)); // So it exits the loop after reading one pair.
256 
257     // std::pair
258     EXPECT_CALL(sdbus_mock,
259                 sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
260         .WillOnce(Return(0));
261 
262     EXPECT_CALL(sdbus_mock,
263                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
264         .WillOnce(Return(1));
265     EXPECT_CALL(sdbus_mock,
266                 sd_bus_message_enter_container(IsNull(), 'v', StrEq("x")))
267         .WillOnce(Return(0));
268 
269     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'x', NotNull()))
270         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
271             int64_t* s = static_cast<int64_t*>(p);
272             *s = xScale;
273             return 0;
274         }));
275 
276     EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
277         .WillOnce(Return(0))  /* variant. */
278         .WillOnce(Return(0))  /* std::pair */
279         .WillOnce(Return(0)); /* std::map */
280 
281     int rv = handleSensorValue(msg, passive);
282     EXPECT_EQ(rv, 0); // It's always 0.
283 
284     ReadReturn r = passive->read();
285     EXPECT_EQ(0.01, r.value);
286 }
287 TEST_F(DbusPassiveTestObj, VerifyCriticalThresholdAssert)
288 {
289 
290     // Verifies when a threshold is crossed the sensor goes into error state
291     EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
292         .WillOnce(Return(nullptr));
293     sdbusplus::message::message msg(nullptr, &sdbus_mock);
294 
295     const char* criticalAlarm = "CriticalAlarmHigh";
296     bool alarm = true;
297     const char* intf = "xyz.openbmc_project.Sensor.Threshold.Critical";
298 
299     passive->setFailed(false);
300 
301     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
302         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
303             const char** s = static_cast<const char**>(p);
304             // Read the first parameter, the string.
305             *s = intf;
306             return 0;
307         }))
308         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
309             const char** s = static_cast<const char**>(p);
310             *s = criticalAlarm;
311             // Read the string in the pair (dictionary).
312             return 0;
313         }));
314 
315     // std::map
316     EXPECT_CALL(sdbus_mock,
317                 sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
318         .WillOnce(Return(0));
319 
320     // while !at_end()
321     EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
322         .WillOnce(Return(0))
323         .WillOnce(Return(1)); // So it exits the loop after reading one pair.
324 
325     // std::pair
326     EXPECT_CALL(sdbus_mock,
327                 sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
328         .WillOnce(Return(0));
329 
330     EXPECT_CALL(sdbus_mock,
331                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
332         .WillOnce(Return(0));
333     EXPECT_CALL(sdbus_mock,
334                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("d")))
335         .WillOnce(Return(0));
336     EXPECT_CALL(sdbus_mock,
337                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("b")))
338         .WillOnce(Return(1));
339     EXPECT_CALL(sdbus_mock,
340                 sd_bus_message_enter_container(IsNull(), 'v', StrEq("b")))
341         .WillOnce(Return(0));
342 
343     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'b', NotNull()))
344         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
345             bool* s = static_cast<bool*>(p);
346             *s = alarm;
347             return 0;
348         }));
349 
350     EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
351         .WillOnce(Return(0))  /* variant. */
352         .WillOnce(Return(0))  /* std::pair */
353         .WillOnce(Return(0)); /* std::map */
354 
355     int rv = handleSensorValue(msg, passive);
356     EXPECT_EQ(rv, 0); // It's always 0.
357     bool failed = passive->getFailed();
358     EXPECT_EQ(failed, true);
359 }
360 
361 TEST_F(DbusPassiveTestObj, VerifyCriticalThresholdDeassert)
362 {
363 
364     // Verifies when a threshold is deasserted a failed sensor goes back into
365     // the normal state
366     EXPECT_CALL(sdbus_mock, sd_bus_message_ref(IsNull()))
367         .WillOnce(Return(nullptr));
368     sdbusplus::message::message msg(nullptr, &sdbus_mock);
369 
370     const char* criticalAlarm = "CriticalAlarmHigh";
371     bool alarm = false;
372     const char* intf = "xyz.openbmc_project.Sensor.Threshold.Critical";
373 
374     passive->setFailed(true);
375 
376     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 's', NotNull()))
377         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
378             const char** s = static_cast<const char**>(p);
379             // Read the first parameter, the string.
380             *s = intf;
381             return 0;
382         }))
383         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
384             const char** s = static_cast<const char**>(p);
385             *s = criticalAlarm;
386             // Read the string in the pair (dictionary).
387             return 0;
388         }));
389 
390     // std::map
391     EXPECT_CALL(sdbus_mock,
392                 sd_bus_message_enter_container(IsNull(), 'a', StrEq("{sv}")))
393         .WillOnce(Return(0));
394 
395     // while !at_end()
396     EXPECT_CALL(sdbus_mock, sd_bus_message_at_end(IsNull(), 0))
397         .WillOnce(Return(0))
398         .WillOnce(Return(1)); // So it exits the loop after reading one pair.
399 
400     // std::pair
401     EXPECT_CALL(sdbus_mock,
402                 sd_bus_message_enter_container(IsNull(), 'e', StrEq("sv")))
403         .WillOnce(Return(0));
404 
405     EXPECT_CALL(sdbus_mock,
406                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("x")))
407         .WillOnce(Return(0));
408     EXPECT_CALL(sdbus_mock,
409                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("d")))
410         .WillOnce(Return(0));
411     EXPECT_CALL(sdbus_mock,
412                 sd_bus_message_verify_type(IsNull(), 'v', StrEq("b")))
413         .WillOnce(Return(1));
414     EXPECT_CALL(sdbus_mock,
415                 sd_bus_message_enter_container(IsNull(), 'v', StrEq("b")))
416         .WillOnce(Return(0));
417 
418     EXPECT_CALL(sdbus_mock, sd_bus_message_read_basic(IsNull(), 'b', NotNull()))
419         .WillOnce(Invoke([&](sd_bus_message* m, char type, void* p) {
420             bool* s = static_cast<bool*>(p);
421             *s = alarm;
422             return 0;
423         }));
424 
425     EXPECT_CALL(sdbus_mock, sd_bus_message_exit_container(IsNull()))
426         .WillOnce(Return(0))  /* variant. */
427         .WillOnce(Return(0))  /* std::pair */
428         .WillOnce(Return(0)); /* std::map */
429 
430     int rv = handleSensorValue(msg, passive);
431     EXPECT_EQ(rv, 0); // It's always 0.
432     bool failed = passive->getFailed();
433     EXPECT_EQ(failed, false);
434 }
435 
436 void GetPropertiesMax3k(sdbusplus::bus::bus& bus, const std::string& service,
437                         const std::string& path, SensorProperties* prop)
438 {
439     prop->scale = -3;
440     prop->value = 10;
441     prop->unit = "x";
442     prop->min = 0;
443     prop->max = 3000;
444 }
445 
446 using GetPropertiesFunction =
447     std::function<void(sdbusplus::bus::bus&, const std::string&,
448                        const std::string&, SensorProperties*)>;
449 
450 // TODO: There is definitely a cleaner way to do this.
451 class DbusPassiveTest3kMaxObj : public ::testing::Test
452 {
453   protected:
454     DbusPassiveTest3kMaxObj() :
455         sdbus_mock(),
456         bus_mock(std::move(sdbusplus::get_mocked_new(&sdbus_mock))), helper()
457     {
458         EXPECT_CALL(helper, getService(_, StrEq(SensorIntf), StrEq(path)))
459             .WillOnce(Return("asdf"));
460 
461         EXPECT_CALL(helper,
462                     getProperties(_, StrEq("asdf"), StrEq(path), NotNull()))
463             .WillOnce(_getProps);
464         EXPECT_CALL(helper, thresholdsAsserted(_, StrEq("asdf"), StrEq(path)))
465             .WillOnce(Return(false));
466 
467         auto info = conf::SensorConfig();
468         ri = DbusPassive::createDbusPassive(bus_mock, type, id, &helper, &info,
469                                             nullptr);
470         passive = reinterpret_cast<DbusPassive*>(ri.get());
471         EXPECT_FALSE(passive == nullptr);
472     }
473 
474     sdbusplus::SdBusMock sdbus_mock;
475     sdbusplus::bus::bus bus_mock;
476     DbusHelperMock helper;
477     std::string type = "temp";
478     std::string id = "id";
479     std::string path = "/xyz/openbmc_project/sensors/temperature/id";
480     int64_t _scale = -3;
481     int64_t _value = 10;
482 
483     std::unique_ptr<ReadInterface> ri;
484     DbusPassive* passive;
485     GetPropertiesFunction _getProps = &GetPropertiesMax3k;
486 };
487 
488 TEST_F(DbusPassiveTest3kMaxObj, ReadMinAndMaxReturnsExpected)
489 {
490     EXPECT_DOUBLE_EQ(0, passive->getMin());
491     EXPECT_DOUBLE_EQ(3, passive->getMax());
492 }
493 
494 class DbusPassiveTest3kMaxIgnoredObj : public ::testing::Test
495 {
496   protected:
497     DbusPassiveTest3kMaxIgnoredObj() :
498         sdbus_mock(),
499         bus_mock(std::move(sdbusplus::get_mocked_new(&sdbus_mock))), helper()
500     {
501         EXPECT_CALL(helper, getService(_, StrEq(SensorIntf), StrEq(path)))
502             .WillOnce(Return("asdf"));
503 
504         EXPECT_CALL(helper,
505                     getProperties(_, StrEq("asdf"), StrEq(path), NotNull()))
506             .WillOnce(_getProps);
507         EXPECT_CALL(helper, thresholdsAsserted(_, StrEq("asdf"), StrEq(path)))
508             .WillOnce(Return(false));
509 
510         auto info = conf::SensorConfig();
511         info.ignoreDbusMinMax = true;
512         ri = DbusPassive::createDbusPassive(bus_mock, type, id, &helper, &info,
513                                             nullptr);
514         passive = reinterpret_cast<DbusPassive*>(ri.get());
515         EXPECT_FALSE(passive == nullptr);
516     }
517 
518     sdbusplus::SdBusMock sdbus_mock;
519     sdbusplus::bus::bus bus_mock;
520     DbusHelperMock helper;
521     std::string type = "temp";
522     std::string id = "id";
523     std::string path = "/xyz/openbmc_project/sensors/temperature/id";
524     int64_t _scale = -3;
525     int64_t _value = 10;
526 
527     std::unique_ptr<ReadInterface> ri;
528     DbusPassive* passive;
529     GetPropertiesFunction _getProps = &GetPropertiesMax3k;
530 };
531 
532 TEST_F(DbusPassiveTest3kMaxIgnoredObj, ReadMinAndMaxReturnsExpected)
533 {
534     EXPECT_DOUBLE_EQ(0, passive->getMin());
535     EXPECT_DOUBLE_EQ(0, passive->getMax());
536 }
537 
538 } // namespace
539 } // namespace pid_control
540