1 #include "pid/ec/pid.hpp" 2 #include "pid/zone.hpp" 3 #include "sensors/manager.hpp" 4 #include "test/controller_mock.hpp" 5 #include "test/helpers.hpp" 6 #include "test/sensor_mock.hpp" 7 8 #include <sdbusplus/test/sdbus_mock.hpp> 9 10 #include <chrono> 11 #include <cstring> 12 #include <vector> 13 14 #include <gmock/gmock.h> 15 #include <gtest/gtest.h> 16 17 namespace pid_control 18 { 19 namespace 20 { 21 22 using ::testing::_; 23 using ::testing::IsNull; 24 using ::testing::Return; 25 using ::testing::StrEq; 26 27 static std::string modeInterface = "xyz.openbmc_project.Control.Mode"; 28 29 namespace 30 { 31 32 TEST(PidZoneConstructorTest, BoringConstructorTest) 33 { 34 // Build a PID Zone. 35 36 sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode; 37 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); 38 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); 39 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); 40 41 EXPECT_CALL(sdbus_mock_host, 42 sd_bus_add_object_manager( 43 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors"))) 44 .WillOnce(Return(0)); 45 46 SensorManager m(bus_mock_passive, bus_mock_host); 47 48 bool defer = true; 49 const char* objPath = "/path/"; 50 int64_t zone = 1; 51 double minThermalOutput = 1000.0; 52 double failSafePercent = 0.75; 53 54 double d; 55 std::vector<std::string> properties; 56 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties, 57 &d); 58 59 DbusPidZone p(zone, minThermalOutput, failSafePercent, m, bus_mock_mode, 60 objPath, defer); 61 // Success. 62 } 63 64 } // namespace 65 66 class PidZoneTest : public ::testing::Test 67 { 68 protected: 69 PidZoneTest() : 70 property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(), 71 sdbus_mock_mode() 72 { 73 EXPECT_CALL(sdbus_mock_host, 74 sd_bus_add_object_manager( 75 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors"))) 76 .WillOnce(Return(0)); 77 78 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); 79 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); 80 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); 81 82 // Compiler weirdly not happy about just instantiating mgr(...); 83 SensorManager m(bus_mock_passive, bus_mock_host); 84 mgr = std::move(m); 85 86 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, 87 properties, &property_index); 88 89 zone = std::make_unique<DbusPidZone>(zoneId, minThermalOutput, 90 failSafePercent, mgr, 91 bus_mock_mode, objPath, defer); 92 } 93 94 // unused 95 double property_index; 96 std::vector<std::string> properties; 97 98 sdbusplus::SdBusMock sdbus_mock_passive; 99 sdbusplus::SdBusMock sdbus_mock_host; 100 sdbusplus::SdBusMock sdbus_mock_mode; 101 int64_t zoneId = 1; 102 double minThermalOutput = 1000.0; 103 double failSafePercent = 0.75; 104 bool defer = true; 105 const char* objPath = "/path/"; 106 SensorManager mgr; 107 108 std::unique_ptr<DbusPidZone> zone; 109 }; 110 111 TEST_F(PidZoneTest, GetZoneId_ReturnsExpected) 112 { 113 // Verifies the zoneId returned is what we expect. 114 115 EXPECT_EQ(zoneId, zone->getZoneID()); 116 } 117 118 TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected) 119 { 120 // Verifies that the zone starts in manual mode. Verifies that one can set 121 // the mode. 122 EXPECT_FALSE(zone->getManualMode()); 123 124 zone->setManualMode(true); 125 EXPECT_TRUE(zone->getManualMode()); 126 } 127 128 TEST_F(PidZoneTest, SetManualMode_RedundantWritesEnabledOnceAfterManualMode) 129 { 130 // Tests adding a fan PID controller to the zone, and verifies it's 131 // touched during processing. 132 133 std::unique_ptr<PIDController> tpid = 134 std::make_unique<ControllerMock>("fan1", zone.get()); 135 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get()); 136 137 // Access the internal pid configuration to clear it out (unrelated to the 138 // test). 139 ec::pid_info_t* info = tpid->getPIDInfo(); 140 std::memset(info, 0x00, sizeof(ec::pid_info_t)); 141 142 zone->addFanPID(std::move(tpid)); 143 144 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); 145 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); 146 EXPECT_CALL(*tmock, outputProc(_)); 147 148 // while zone is in auto mode redundant writes should be disabled 149 EXPECT_FALSE(zone->getRedundantWrite()); 150 151 // but switching from manual to auto enables a single redundant write 152 zone->setManualMode(true); 153 zone->setManualMode(false); 154 EXPECT_TRUE(zone->getRedundantWrite()); 155 156 // after one iteration of a pid loop redundant write should be cleared 157 zone->processFans(); 158 EXPECT_FALSE(zone->getRedundantWrite()); 159 } 160 161 TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected) 162 { 163 // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest 164 // and getMinThermalSetPoint. 165 166 // At least one value must be above the minimum thermal setpoint used in 167 // the constructor otherwise it'll choose that value 168 std::vector<double> values = {100, 200, 300, 400, 500, 5000}; 169 for (auto v : values) 170 { 171 zone->addSetPoint(v, ""); 172 } 173 174 // This will pull the maximum RPM setpoint request. 175 zone->determineMaxSetPointRequest(); 176 EXPECT_EQ(5000, zone->getMaxSetPointRequest()); 177 178 // Clear the values, so it'll choose the minimum thermal setpoint. 179 zone->clearSetPoints(); 180 181 // This will go through the RPM set point values and grab the maximum. 182 zone->determineMaxSetPointRequest(); 183 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest()); 184 } 185 186 TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected) 187 { 188 // Tests adding several RPM setpoints, however, they're all lower than the 189 // configured minimal thermal setpoint RPM value. 190 191 std::vector<double> values = {100, 200, 300, 400, 500}; 192 for (auto v : values) 193 { 194 zone->addSetPoint(v, ""); 195 } 196 197 // This will pull the maximum RPM setpoint request. 198 zone->determineMaxSetPointRequest(); 199 200 // Verifies the value returned in the minimal thermal rpm set point. 201 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest()); 202 } 203 204 TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected) 205 { 206 // Verify the value used to create the object is stored. 207 EXPECT_EQ(failSafePercent, zone->getFailSafePercent()); 208 } 209 210 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors) 211 { 212 // This test will add a couple thermal inputs, and verify that the zone 213 // initializes into failsafe mode, and will read each sensor. 214 215 std::string name1 = "temp1"; 216 int64_t timeout = 1; 217 218 std::unique_ptr<Sensor> sensor1 = 219 std::make_unique<SensorMock>(name1, timeout); 220 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 221 222 std::string name2 = "temp2"; 223 std::unique_ptr<Sensor> sensor2 = 224 std::make_unique<SensorMock>(name2, timeout); 225 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 226 227 std::string type = "unchecked"; 228 mgr.addSensor(type, name1, std::move(sensor1)); 229 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 230 mgr.addSensor(type, name2, std::move(sensor2)); 231 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 232 233 // Now that the sensors exist, add them to the zone. 234 zone->addThermalInput(name1); 235 zone->addThermalInput(name2); 236 237 // Initialize Zone 238 zone->initializeCache(); 239 240 // Verify now in failsafe mode. 241 EXPECT_TRUE(zone->getFailSafeMode()); 242 243 ReadReturn r1; 244 r1.value = 10.0; 245 r1.updated = std::chrono::high_resolution_clock::now(); 246 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 247 248 ReadReturn r2; 249 r2.value = 11.0; 250 r2.updated = std::chrono::high_resolution_clock::now(); 251 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 252 253 // Read the sensors, this will put the values into the cache. 254 zone->updateSensors(); 255 256 // We should no longer be in failsafe mode. 257 EXPECT_FALSE(zone->getFailSafeMode()); 258 259 EXPECT_EQ(r1.value, zone->getCachedValue(name1)); 260 EXPECT_EQ(r2.value, zone->getCachedValue(name2)); 261 } 262 263 TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached) 264 { 265 // This will add a couple fan inputs, and verify the values are cached. 266 267 std::string name1 = "fan1"; 268 int64_t timeout = 2; 269 270 std::unique_ptr<Sensor> sensor1 = 271 std::make_unique<SensorMock>(name1, timeout); 272 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 273 274 std::string name2 = "fan2"; 275 std::unique_ptr<Sensor> sensor2 = 276 std::make_unique<SensorMock>(name2, timeout); 277 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 278 279 std::string type = "unchecked"; 280 mgr.addSensor(type, name1, std::move(sensor1)); 281 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 282 mgr.addSensor(type, name2, std::move(sensor2)); 283 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 284 285 // Now that the sensors exist, add them to the zone. 286 zone->addFanInput(name1); 287 zone->addFanInput(name2); 288 289 // Initialize Zone 290 zone->initializeCache(); 291 292 ReadReturn r1; 293 r1.value = 10.0; 294 r1.updated = std::chrono::high_resolution_clock::now(); 295 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 296 297 ReadReturn r2; 298 r2.value = 11.0; 299 r2.updated = std::chrono::high_resolution_clock::now(); 300 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 301 302 // Method under test will read through each fan sensor for the zone and 303 // cache the values. 304 zone->updateFanTelemetry(); 305 306 EXPECT_EQ(r1.value, zone->getCachedValue(name1)); 307 EXPECT_EQ(r2.value, zone->getCachedValue(name2)); 308 } 309 310 TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode) 311 { 312 // On the second updateSensors call, the updated timestamp will be beyond 313 // the timeout limit. 314 315 int64_t timeout = 1; 316 317 std::string name1 = "temp1"; 318 std::unique_ptr<Sensor> sensor1 = 319 std::make_unique<SensorMock>(name1, timeout); 320 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 321 322 std::string name2 = "temp2"; 323 std::unique_ptr<Sensor> sensor2 = 324 std::make_unique<SensorMock>(name2, timeout); 325 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 326 327 std::string type = "unchecked"; 328 mgr.addSensor(type, name1, std::move(sensor1)); 329 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 330 mgr.addSensor(type, name2, std::move(sensor2)); 331 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 332 333 zone->addThermalInput(name1); 334 zone->addThermalInput(name2); 335 336 // Initialize Zone 337 zone->initializeCache(); 338 339 // Verify now in failsafe mode. 340 EXPECT_TRUE(zone->getFailSafeMode()); 341 342 ReadReturn r1; 343 r1.value = 10.0; 344 r1.updated = std::chrono::high_resolution_clock::now(); 345 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 346 347 ReadReturn r2; 348 r2.value = 11.0; 349 r2.updated = std::chrono::high_resolution_clock::now(); 350 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 351 352 zone->updateSensors(); 353 EXPECT_FALSE(zone->getFailSafeMode()); 354 355 // Ok, so we're not in failsafe mode, so let's set updated to the past. 356 // sensor1 will have an updated field older than its timeout value, but 357 // sensor2 will be fine. :D 358 r1.updated -= std::chrono::seconds(3); 359 r2.updated = std::chrono::high_resolution_clock::now(); 360 361 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 362 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 363 364 // Method under test will read each sensor. One sensor's value is older 365 // than the timeout for that sensor and this triggers failsafe mode. 366 zone->updateSensors(); 367 EXPECT_TRUE(zone->getFailSafeMode()); 368 } 369 370 TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors) 371 { 372 // This will add a couple fan inputs, and verify the values are cached. 373 374 std::string name1 = "fan1"; 375 int64_t timeout = 2; 376 377 std::unique_ptr<Sensor> sensor1 = 378 std::make_unique<SensorMock>(name1, timeout); 379 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 380 381 std::string name2 = "fan2"; 382 std::unique_ptr<Sensor> sensor2 = 383 std::make_unique<SensorMock>(name2, timeout); 384 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 385 386 std::string type = "unchecked"; 387 mgr.addSensor(type, name1, std::move(sensor1)); 388 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 389 mgr.addSensor(type, name2, std::move(sensor2)); 390 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 391 392 // Now that the sensors exist, add them to the zone. 393 zone->addFanInput(name1); 394 zone->addFanInput(name2); 395 396 // Initialize Zone 397 zone->initializeCache(); 398 399 // Verify now in failsafe mode. 400 EXPECT_TRUE(zone->getFailSafeMode()); 401 402 ReadReturn r1; 403 r1.value = 10.0; 404 r1.updated = std::chrono::high_resolution_clock::now(); 405 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 406 407 ReadReturn r2; 408 r2.value = 11.0; 409 r2.updated = std::chrono::high_resolution_clock::now(); 410 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 411 412 // Method under test will read through each fan sensor for the zone and 413 // cache the values. 414 zone->updateFanTelemetry(); 415 416 // We should no longer be in failsafe mode. 417 EXPECT_FALSE(zone->getFailSafeMode()); 418 419 EXPECT_EQ(r1.value, zone->getCachedValue(name1)); 420 EXPECT_EQ(r2.value, zone->getCachedValue(name2)); 421 } 422 423 TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode) 424 { 425 // This will add a couple fan inputs, and verify the values are cached. 426 427 std::string name1 = "fan1"; 428 int64_t timeout = 2; 429 430 std::unique_ptr<Sensor> sensor1 = 431 std::make_unique<SensorMock>(name1, timeout); 432 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 433 434 std::string name2 = "fan2"; 435 std::unique_ptr<Sensor> sensor2 = 436 std::make_unique<SensorMock>(name2, timeout); 437 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 438 439 std::string type = "unchecked"; 440 mgr.addSensor(type, name1, std::move(sensor1)); 441 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 442 mgr.addSensor(type, name2, std::move(sensor2)); 443 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 444 445 // Now that the sensors exist, add them to the zone. 446 zone->addFanInput(name1); 447 zone->addFanInput(name2); 448 449 // Initialize Zone 450 zone->initializeCache(); 451 452 // Verify now in failsafe mode. 453 EXPECT_TRUE(zone->getFailSafeMode()); 454 455 ReadReturn r1; 456 r1.value = 10.0; 457 r1.updated = std::chrono::high_resolution_clock::now(); 458 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 459 460 ReadReturn r2; 461 r2.value = 11.0; 462 r2.updated = std::chrono::high_resolution_clock::now(); 463 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 464 465 // Method under test will read through each fan sensor for the zone and 466 // cache the values. 467 zone->updateFanTelemetry(); 468 469 // We should no longer be in failsafe mode. 470 EXPECT_FALSE(zone->getFailSafeMode()); 471 472 r1.updated -= std::chrono::seconds(3); 473 r2.updated = std::chrono::high_resolution_clock::now(); 474 475 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 476 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 477 478 zone->updateFanTelemetry(); 479 EXPECT_TRUE(zone->getFailSafeMode()); 480 } 481 482 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected) 483 { 484 // One can grab a sensor from the manager through the zone. 485 486 int64_t timeout = 1; 487 488 std::string name1 = "temp1"; 489 std::unique_ptr<Sensor> sensor1 = 490 std::make_unique<SensorMock>(name1, timeout); 491 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 492 493 std::string type = "unchecked"; 494 mgr.addSensor(type, name1, std::move(sensor1)); 495 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 496 497 zone->addThermalInput(name1); 498 499 // Verify method under test returns the pointer we expect. 500 EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1)); 501 } 502 503 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed) 504 { 505 // Tests adding a thermal PID controller to the zone, and verifies it's 506 // touched during processing. 507 508 std::unique_ptr<PIDController> tpid = 509 std::make_unique<ControllerMock>("thermal1", zone.get()); 510 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get()); 511 512 // Access the internal pid configuration to clear it out (unrelated to the 513 // test). 514 ec::pid_info_t* info = tpid->getPIDInfo(); 515 std::memset(info, 0x00, sizeof(ec::pid_info_t)); 516 517 zone->addThermalPID(std::move(tpid)); 518 519 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); 520 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); 521 EXPECT_CALL(*tmock, outputProc(_)); 522 523 // Method under test will, for each thermal PID, call setpt, input, and 524 // output. 525 zone->processThermals(); 526 } 527 528 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed) 529 { 530 // Tests adding a fan PID controller to the zone, and verifies it's 531 // touched during processing. 532 533 std::unique_ptr<PIDController> tpid = 534 std::make_unique<ControllerMock>("fan1", zone.get()); 535 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get()); 536 537 // Access the internal pid configuration to clear it out (unrelated to the 538 // test). 539 ec::pid_info_t* info = tpid->getPIDInfo(); 540 std::memset(info, 0x00, sizeof(ec::pid_info_t)); 541 542 zone->addFanPID(std::move(tpid)); 543 544 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); 545 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); 546 EXPECT_CALL(*tmock, outputProc(_)); 547 548 // Method under test will, for each fan PID, call setpt, input, and output. 549 zone->processFans(); 550 } 551 552 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected) 553 { 554 // The manual(bool) method is inherited from the dbus mode interface. 555 556 // Verifies that someone doesn't remove the internal call to the dbus 557 // object from which we're inheriting. 558 EXPECT_CALL(sdbus_mock_mode, 559 sd_bus_emit_properties_changed_strv( 560 IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull())) 561 .WillOnce(Invoke( 562 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, 563 [[maybe_unused]] const char* interface, const char** names) { 564 EXPECT_STREQ("Manual", names[0]); 565 return 0; 566 })); 567 568 // Method under test will set the manual mode to true and broadcast this 569 // change on dbus. 570 zone->manual(true); 571 EXPECT_TRUE(zone->getManualMode()); 572 } 573 574 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected) 575 { 576 // This property is implemented by us as read-only, such that trying to 577 // write to it will have no effect. 578 EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode()); 579 } 580 581 } // namespace 582 } // namespace pid_control 583