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