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