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