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