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(Invoke(
80                 [&](sdbusplus::bus::bus& bus, const std::string& service,
81                     const std::string& path, 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(sdbusplus::bus::bus& bus, const std::string& service,
440                         const std::string& path, 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 =
450     std::function<void(sdbusplus::bus::bus&, const std::string&,
451                        const std::string&, SensorProperties*)>;
452 
453 // TODO: There is definitely a cleaner way to do this.
454 class DbusPassiveTest3kMaxObj : public ::testing::Test
455 {
456   protected:
457     DbusPassiveTest3kMaxObj() :
458         sdbus_mock(),
459         bus_mock(std::move(sdbusplus::get_mocked_new(&sdbus_mock))),
460         helper(std::make_unique<DbusHelperMock>())
461     {
462         EXPECT_CALL(*helper, getService(_, StrEq(SensorIntf), StrEq(path)))
463             .WillOnce(Return("asdf"));
464 
465         EXPECT_CALL(*helper,
466                     getProperties(_, StrEq("asdf"), StrEq(path), NotNull()))
467             .WillOnce(_getProps);
468         EXPECT_CALL(*helper, thresholdsAsserted(_, StrEq("asdf"), StrEq(path)))
469             .WillOnce(Return(false));
470 
471         auto info = conf::SensorConfig();
472         ri = DbusPassive::createDbusPassive(bus_mock, type, id,
473                                             std::move(helper), &info, nullptr);
474         passive = reinterpret_cast<DbusPassive*>(ri.get());
475         EXPECT_FALSE(passive == nullptr);
476     }
477 
478     sdbusplus::SdBusMock sdbus_mock;
479     sdbusplus::bus::bus bus_mock;
480     std::unique_ptr<DbusHelperMock> helper;
481     std::string type = "temp";
482     std::string id = "id";
483     std::string path = "/xyz/openbmc_project/sensors/temperature/id";
484     int64_t _scale = -3;
485     int64_t _value = 10;
486 
487     std::unique_ptr<ReadInterface> ri;
488     DbusPassive* passive;
489     GetPropertiesFunction _getProps = &GetPropertiesMax3k;
490 };
491 
492 TEST_F(DbusPassiveTest3kMaxObj, ReadMinAndMaxReturnsExpected)
493 {
494     EXPECT_DOUBLE_EQ(0, passive->getMin());
495     EXPECT_DOUBLE_EQ(3, passive->getMax());
496 }
497 
498 class DbusPassiveTest3kMaxIgnoredObj : public ::testing::Test
499 {
500   protected:
501     DbusPassiveTest3kMaxIgnoredObj() :
502         sdbus_mock(),
503         bus_mock(std::move(sdbusplus::get_mocked_new(&sdbus_mock))),
504         helper(std::make_unique<DbusHelperMock>())
505     {
506         EXPECT_CALL(*helper, getService(_, StrEq(SensorIntf), StrEq(path)))
507             .WillOnce(Return("asdf"));
508 
509         EXPECT_CALL(*helper,
510                     getProperties(_, StrEq("asdf"), StrEq(path), NotNull()))
511             .WillOnce(_getProps);
512         EXPECT_CALL(*helper, thresholdsAsserted(_, StrEq("asdf"), StrEq(path)))
513             .WillOnce(Return(false));
514 
515         auto info = conf::SensorConfig();
516         info.ignoreDbusMinMax = true;
517         ri = DbusPassive::createDbusPassive(bus_mock, type, id,
518                                             std::move(helper), &info, nullptr);
519         passive = reinterpret_cast<DbusPassive*>(ri.get());
520         EXPECT_FALSE(passive == nullptr);
521     }
522 
523     sdbusplus::SdBusMock sdbus_mock;
524     sdbusplus::bus::bus bus_mock;
525     std::unique_ptr<DbusHelperMock> helper;
526     std::string type = "temp";
527     std::string id = "id";
528     std::string path = "/xyz/openbmc_project/sensors/temperature/id";
529     int64_t _scale = -3;
530     int64_t _value = 10;
531 
532     std::unique_ptr<ReadInterface> ri;
533     DbusPassive* passive;
534     GetPropertiesFunction _getProps = &GetPropertiesMax3k;
535 };
536 
537 TEST_F(DbusPassiveTest3kMaxIgnoredObj, ReadMinAndMaxReturnsExpected)
538 {
539     EXPECT_DOUBLE_EQ(0, passive->getMin());
540     EXPECT_DOUBLE_EQ(0, passive->getMax());
541 }
542 
543 } // namespace
544 } // namespace pid_control
545