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