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