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 static std::string debugZoneInterface = "xyz.openbmc_project.Debug.Pid.Zone"; 30 static std::string enableInterface = "xyz.openbmc_project.Object.Enable"; 31 static std::string debugThermalPowerInterface = 32 "xyz.openbmc_project.Debug.Pid.ThermalPower"; 33 34 namespace 35 { 36 37 TEST(PidZoneConstructorTest, BoringConstructorTest) 38 { 39 // Build a PID Zone. 40 41 sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode, 42 sdbus_mock_enable; 43 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); 44 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); 45 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); 46 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); 47 48 EXPECT_CALL(sdbus_mock_host, 49 sd_bus_add_object_manager( 50 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors"))) 51 .WillOnce(Return(0)); 52 53 SensorManager m(bus_mock_passive, bus_mock_host); 54 55 bool defer = true; 56 const char* objPath = "/path/"; 57 int64_t zone = 1; 58 double minThermalOutput = 1000.0; 59 double failSafePercent = 0; 60 conf::CycleTime cycleTime; 61 62 double d; 63 std::vector<std::string> properties; 64 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties, 65 &d); 66 SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface, 67 properties, &d); 68 69 std::string sensorname = "temp1"; 70 std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" + 71 sensorname; 72 73 double de; 74 std::vector<std::string> propertiesenable; 75 SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(), 76 enableInterface, propertiesenable, &de); 77 78 EXPECT_CALL(sdbus_mock_enable, 79 sd_bus_add_object_vtable( 80 IsNull(), NotNull(), StrEq(pidsensorpath.c_str()), 81 StrEq(debugThermalPowerInterface), NotNull(), NotNull())) 82 .Times(::testing::AnyNumber()) 83 .WillOnce(Return(0)); 84 85 DbusPidZone p(zone, minThermalOutput, failSafePercent, cycleTime, m, 86 bus_mock_mode, objPath, defer); 87 // Success. 88 } 89 90 } // namespace 91 92 class PidZoneTest : public ::testing::Test 93 { 94 protected: 95 PidZoneTest() : 96 property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(), 97 sdbus_mock_mode(), sdbus_mock_enable() 98 { 99 EXPECT_CALL(sdbus_mock_host, 100 sd_bus_add_object_manager( 101 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors"))) 102 .WillOnce(Return(0)); 103 104 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); 105 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); 106 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); 107 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); 108 109 // Compiler weirdly not happy about just instantiating mgr(...); 110 SensorManager m(bus_mock_passive, bus_mock_host); 111 mgr = std::move(m); 112 113 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, 114 properties, &property_index); 115 SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface, 116 properties, &property_index); 117 118 SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(), 119 enableInterface, propertiesenable, 120 &propertyenable_index); 121 EXPECT_CALL(sdbus_mock_enable, 122 sd_bus_add_object_vtable(IsNull(), NotNull(), 123 StrEq(pidsensorpath.c_str()), 124 StrEq(debugThermalPowerInterface), 125 NotNull(), NotNull())) 126 .Times(::testing::AnyNumber()) 127 .WillOnce(Return(0)); 128 129 zone = std::make_unique<DbusPidZone>(zoneId, minThermalOutput, 130 failSafePercent, cycleTime, mgr, 131 bus_mock_mode, objPath, defer); 132 } 133 134 // unused 135 double property_index; 136 std::vector<std::string> properties; 137 double propertyenable_index; 138 std::vector<std::string> propertiesenable; 139 140 sdbusplus::SdBusMock sdbus_mock_passive; 141 sdbusplus::SdBusMock sdbus_mock_host; 142 sdbusplus::SdBusMock sdbus_mock_mode; 143 sdbusplus::SdBusMock sdbus_mock_enable; 144 int64_t zoneId = 1; 145 double minThermalOutput = 1000.0; 146 double failSafePercent = 0; 147 double setpoint = 50.0; 148 bool defer = true; 149 const char* objPath = "/path/"; 150 SensorManager mgr; 151 conf::CycleTime cycleTime; 152 153 std::string sensorname = "temp1"; 154 std::string sensorType = "temp"; 155 std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" + 156 sensorname; 157 158 std::unique_ptr<DbusPidZone> zone; 159 }; 160 161 TEST_F(PidZoneTest, GetZoneId_ReturnsExpected) 162 { 163 // Verifies the zoneId returned is what we expect. 164 165 EXPECT_EQ(zoneId, zone->getZoneID()); 166 } 167 168 TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected) 169 { 170 // Verifies that the zone starts in manual mode. Verifies that one can set 171 // the mode. 172 EXPECT_FALSE(zone->getManualMode()); 173 174 zone->setManualMode(true); 175 EXPECT_TRUE(zone->getManualMode()); 176 } 177 178 TEST_F(PidZoneTest, AddPidControlProcessGetAndSetEnableTest_BehavesAsExpected) 179 { 180 // Verifies that the zone starts in enable mode. Verifies that one can set 181 // enable the mode. 182 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); 183 184 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( 185 IsNull(), StrEq(pidsensorpath.c_str()), 186 StrEq(enableInterface), NotNull())) 187 .Times(::testing::AnyNumber()) 188 .WillOnce(Invoke( 189 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, 190 [[maybe_unused]] const char* interface, const char** names) { 191 EXPECT_STREQ("Enable", names[0]); 192 return 0; 193 })); 194 195 zone->addPidControlProcess(sensorname, sensorType, setpoint, 196 bus_mock_enable, pidsensorpath.c_str(), defer); 197 EXPECT_TRUE(zone->isPidProcessEnabled(sensorname)); 198 } 199 200 TEST_F(PidZoneTest, SetManualMode_RedundantWritesEnabledOnceAfterManualMode) 201 { 202 // Tests adding a fan PID controller to the zone, and verifies it's 203 // touched during processing. 204 205 std::unique_ptr<PIDController> tpid = 206 std::make_unique<ControllerMock>("fan1", zone.get()); 207 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get()); 208 209 // Access the internal pid configuration to clear it out (unrelated to the 210 // test). 211 ec::pid_info_t* info = tpid->getPIDInfo(); 212 std::memset(info, 0x00, sizeof(ec::pid_info_t)); 213 214 zone->addFanPID(std::move(tpid)); 215 216 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); 217 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); 218 EXPECT_CALL(*tmock, outputProc(_)); 219 220 // while zone is in auto mode redundant writes should be disabled 221 EXPECT_FALSE(zone->getRedundantWrite()); 222 223 // but switching from manual to auto enables a single redundant write 224 zone->setManualMode(true); 225 zone->setManualMode(false); 226 EXPECT_TRUE(zone->getRedundantWrite()); 227 228 // after one iteration of a pid loop redundant write should be cleared 229 zone->processFans(); 230 EXPECT_FALSE(zone->getRedundantWrite()); 231 } 232 233 TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected) 234 { 235 // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest 236 // and getMinThermalSetPoint. 237 238 // Need to add pid control process for the zone that can enable 239 // the process and add the set point. 240 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); 241 242 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( 243 IsNull(), StrEq(pidsensorpath.c_str()), 244 StrEq(enableInterface), NotNull())) 245 .Times(::testing::AnyNumber()) 246 .WillOnce(Invoke( 247 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, 248 [[maybe_unused]] const char* interface, const char** names) { 249 EXPECT_STREQ("Enable", names[0]); 250 return 0; 251 })); 252 253 zone->addPidControlProcess(sensorname, sensorType, setpoint, 254 bus_mock_enable, pidsensorpath.c_str(), defer); 255 256 // At least one value must be above the minimum thermal setpoint used in 257 // the constructor otherwise it'll choose that value 258 std::vector<double> values = {100, 200, 300, 400, 500, 5000}; 259 260 for (auto v : values) 261 { 262 zone->addSetPoint(v, sensorname); 263 } 264 265 // This will pull the maximum RPM setpoint request. 266 zone->determineMaxSetPointRequest(); 267 EXPECT_EQ(5000, zone->getMaxSetPointRequest()); 268 269 // Clear the values, so it'll choose the minimum thermal setpoint. 270 zone->clearSetPoints(); 271 272 // This will go through the RPM set point values and grab the maximum. 273 zone->determineMaxSetPointRequest(); 274 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest()); 275 } 276 277 TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected) 278 { 279 // Tests adding several RPM setpoints, however, they're all lower than the 280 // configured minimal thermal setpoint RPM value. 281 282 // Need to add pid control process for the zone that can enable 283 // the process and add the set point. 284 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); 285 286 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( 287 IsNull(), StrEq(pidsensorpath.c_str()), 288 StrEq(enableInterface), NotNull())) 289 .Times(::testing::AnyNumber()) 290 .WillOnce(Invoke( 291 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, 292 [[maybe_unused]] const char* interface, const char** names) { 293 EXPECT_STREQ("Enable", names[0]); 294 return 0; 295 })); 296 297 zone->addPidControlProcess(sensorname, sensorType, setpoint, 298 bus_mock_enable, pidsensorpath.c_str(), defer); 299 300 std::vector<double> values = {100, 200, 300, 400, 500}; 301 302 for (auto v : values) 303 { 304 zone->addSetPoint(v, sensorname); 305 } 306 307 // This will pull the maximum RPM setpoint request. 308 zone->determineMaxSetPointRequest(); 309 310 // Verifies the value returned in the minimal thermal rpm set point. 311 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest()); 312 } 313 314 TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected) 315 { 316 // Verify the value used to create the object is stored. 317 // when the final failsafe percent is zero , it indicate 318 // no failsafe percent is configured , set it to 100% as 319 // the default setting. 320 321 std::vector<double> values = {0, 0, 0}; 322 int64_t defaultPercent = 100; 323 324 zone->addPidFailSafePercent("temp1", values[0]); 325 zone->addPidFailSafePercent("temp2", values[1]); 326 zone->addPidFailSafePercent("temp3", values[2]); 327 328 zone->initPidFailSafePercent(); 329 330 EXPECT_EQ(defaultPercent, zone->getFailSafePercent()); 331 } 332 333 TEST_F(PidZoneTest, GetFailSafePercent_VerifyReturnsExpected) 334 { 335 // Tests adding PID controller with FailSafePercent to the zone, 336 // and verifies it's returned as expected. 337 338 std::vector<double> values = {60, 80, 70}; 339 double max_value = 0; 340 341 for (const auto& value : values) 342 { 343 max_value = std::max(max_value, value); 344 } 345 346 zone->addPidFailSafePercent("temp1", values[0]); 347 zone->addPidFailSafePercent("temp2", values[1]); 348 zone->addPidFailSafePercent("temp3", values[2]); 349 350 zone->initPidFailSafePercent(); 351 352 EXPECT_EQ(max_value, zone->getFailSafePercent()); 353 } 354 355 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors) 356 { 357 // This test will add a couple thermal inputs, and verify that the zone 358 // initializes into failsafe mode, and will read each sensor. 359 360 std::string name1 = "temp1"; 361 int64_t timeout = 1; 362 363 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 364 timeout); 365 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 366 367 std::string name2 = "temp2"; 368 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2, 369 timeout); 370 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 371 372 std::string type = "unchecked"; 373 mgr.addSensor(type, name1, std::move(sensor1)); 374 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 375 mgr.addSensor(type, name2, std::move(sensor2)); 376 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 377 378 // Now that the sensors exist, add them to the zone. 379 zone->addThermalInput(name1, false); 380 zone->addThermalInput(name2, false); 381 382 // Initialize Zone 383 zone->initializeCache(); 384 385 // Verify now in failsafe mode. 386 EXPECT_TRUE(zone->getFailSafeMode()); 387 388 ReadReturn r1; 389 r1.value = 10.0; 390 r1.updated = std::chrono::high_resolution_clock::now(); 391 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 392 393 ReadReturn r2; 394 r2.value = 11.0; 395 r2.updated = std::chrono::high_resolution_clock::now(); 396 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 397 398 // Read the sensors, this will put the values into the cache. 399 zone->updateSensors(); 400 401 // We should no longer be in failsafe mode. 402 EXPECT_FALSE(zone->getFailSafeMode()); 403 404 EXPECT_EQ(r1.value, zone->getCachedValue(name1)); 405 EXPECT_EQ(r2.value, zone->getCachedValue(name2)); 406 } 407 408 TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached) 409 { 410 // This will add a couple fan inputs, and verify the values are cached. 411 412 std::string name1 = "fan1"; 413 int64_t timeout = 2; 414 415 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 416 timeout); 417 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 418 419 std::string name2 = "fan2"; 420 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2, 421 timeout); 422 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 423 424 std::string type = "unchecked"; 425 mgr.addSensor(type, name1, std::move(sensor1)); 426 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 427 mgr.addSensor(type, name2, std::move(sensor2)); 428 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 429 430 // Now that the sensors exist, add them to the zone. 431 zone->addFanInput(name1, false); 432 zone->addFanInput(name2, false); 433 434 // Initialize Zone 435 zone->initializeCache(); 436 437 ReadReturn r1; 438 r1.value = 10.0; 439 r1.updated = std::chrono::high_resolution_clock::now(); 440 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 441 442 ReadReturn r2; 443 r2.value = 11.0; 444 r2.updated = std::chrono::high_resolution_clock::now(); 445 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 446 447 // Method under test will read through each fan sensor for the zone and 448 // cache the values. 449 zone->updateFanTelemetry(); 450 451 EXPECT_EQ(r1.value, zone->getCachedValue(name1)); 452 EXPECT_EQ(r2.value, zone->getCachedValue(name2)); 453 } 454 455 TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode) 456 { 457 // On the second updateSensors call, the updated timestamp will be beyond 458 // the timeout limit. 459 460 int64_t timeout = 1; 461 462 std::string name1 = "temp1"; 463 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 464 timeout); 465 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 466 467 std::string name2 = "temp2"; 468 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2, 469 timeout); 470 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 471 472 std::string type = "unchecked"; 473 mgr.addSensor(type, name1, std::move(sensor1)); 474 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 475 mgr.addSensor(type, name2, std::move(sensor2)); 476 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 477 478 zone->addThermalInput(name1, false); 479 zone->addThermalInput(name2, false); 480 481 // Initialize Zone 482 zone->initializeCache(); 483 484 // Verify now in failsafe mode. 485 EXPECT_TRUE(zone->getFailSafeMode()); 486 487 ReadReturn r1; 488 r1.value = 10.0; 489 r1.updated = std::chrono::high_resolution_clock::now(); 490 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 491 492 ReadReturn r2; 493 r2.value = 11.0; 494 r2.updated = std::chrono::high_resolution_clock::now(); 495 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 496 497 zone->updateSensors(); 498 EXPECT_FALSE(zone->getFailSafeMode()); 499 500 // Ok, so we're not in failsafe mode, so let's set updated to the past. 501 // sensor1 will have an updated field older than its timeout value, but 502 // sensor2 will be fine. :D 503 r1.updated -= std::chrono::seconds(3); 504 r2.updated = std::chrono::high_resolution_clock::now(); 505 506 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 507 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 508 509 // Method under test will read each sensor. One sensor's value is older 510 // than the timeout for that sensor and this triggers failsafe mode. 511 zone->updateSensors(); 512 EXPECT_TRUE(zone->getFailSafeMode()); 513 } 514 515 TEST_F(PidZoneTest, ThermalInput_MissingIsAcceptableNoFailSafe) 516 { 517 // This is similar to the above test, but because missingIsAcceptable 518 // is set for sensor1, the zone should not enter failsafe mode when 519 // only sensor1 goes missing. 520 // However, sensor2 going missing should still trigger failsafe mode. 521 522 int64_t timeout = 1; 523 524 std::string name1 = "temp1"; 525 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 526 timeout); 527 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 528 529 std::string name2 = "temp2"; 530 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2, 531 timeout); 532 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 533 534 std::string type = "unchecked"; 535 mgr.addSensor(type, name1, std::move(sensor1)); 536 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 537 mgr.addSensor(type, name2, std::move(sensor2)); 538 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 539 540 // Only sensor1 has MissingIsAcceptable enabled for it 541 zone->addThermalInput(name1, true); 542 zone->addThermalInput(name2, false); 543 544 // Initialize Zone 545 zone->initializeCache(); 546 547 // As sensors are not initialized, zone should be in failsafe mode 548 EXPECT_TRUE(zone->getFailSafeMode()); 549 550 // r1 not populated here, intentionally, to simulate a sensor that 551 // is not available yet, perhaps takes a long time to start up. 552 ReadReturn r1; 553 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 554 555 ReadReturn r2; 556 r2.value = 11.0; 557 r2.updated = std::chrono::high_resolution_clock::now(); 558 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 559 560 zone->updateSensors(); 561 562 // Only sensor2 has been initialized here. Failsafe should be false, 563 // because sensor1 MissingIsAcceptable so it is OK for it to go missing. 564 EXPECT_FALSE(zone->getFailSafeMode()); 565 566 r1.value = 10.0; 567 r1.updated = std::chrono::high_resolution_clock::now(); 568 569 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 570 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 571 zone->updateSensors(); 572 573 // Both sensors are now properly initialized 574 EXPECT_FALSE(zone->getFailSafeMode()); 575 576 // Ok, so we're not in failsafe mode, so let's set updated to the past. 577 // sensor1 will have an updated field older than its timeout value, but 578 // sensor2 will be fine. :D 579 r1.updated -= std::chrono::seconds(3); 580 r2.updated = std::chrono::high_resolution_clock::now(); 581 582 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 583 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 584 zone->updateSensors(); 585 586 // MissingIsAcceptable is true for sensor1, so the zone should not be 587 // thrown into failsafe mode. 588 EXPECT_FALSE(zone->getFailSafeMode()); 589 590 // Do the same thing, but for the opposite sensors: r1 is good, 591 // but r2 is set to some time in the past. 592 r1.updated = std::chrono::high_resolution_clock::now(); 593 r2.updated -= std::chrono::seconds(3); 594 595 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 596 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 597 zone->updateSensors(); 598 599 // Now, the zone should be in failsafe mode, because sensor2 does not 600 // have MissingIsAcceptable set true, it is still subject to failsafe. 601 EXPECT_TRUE(zone->getFailSafeMode()); 602 603 r1.updated = std::chrono::high_resolution_clock::now(); 604 r2.updated = std::chrono::high_resolution_clock::now(); 605 606 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 607 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 608 zone->updateSensors(); 609 610 // The failsafe mode should cease, as both sensors are good again. 611 EXPECT_FALSE(zone->getFailSafeMode()); 612 } 613 614 TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors) 615 { 616 // This will add a couple fan inputs, and verify the values are cached. 617 618 std::string name1 = "fan1"; 619 int64_t timeout = 2; 620 621 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 622 timeout); 623 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 624 625 std::string name2 = "fan2"; 626 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2, 627 timeout); 628 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 629 630 std::string type = "unchecked"; 631 mgr.addSensor(type, name1, std::move(sensor1)); 632 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 633 mgr.addSensor(type, name2, std::move(sensor2)); 634 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 635 636 // Now that the sensors exist, add them to the zone. 637 zone->addFanInput(name1, false); 638 zone->addFanInput(name2, false); 639 640 // Initialize Zone 641 zone->initializeCache(); 642 643 // Verify now in failsafe mode. 644 EXPECT_TRUE(zone->getFailSafeMode()); 645 646 ReadReturn r1; 647 r1.value = 10.0; 648 r1.updated = std::chrono::high_resolution_clock::now(); 649 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 650 651 ReadReturn r2; 652 r2.value = 11.0; 653 r2.updated = std::chrono::high_resolution_clock::now(); 654 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 655 656 // Method under test will read through each fan sensor for the zone and 657 // cache the values. 658 zone->updateFanTelemetry(); 659 660 // We should no longer be in failsafe mode. 661 EXPECT_FALSE(zone->getFailSafeMode()); 662 663 EXPECT_EQ(r1.value, zone->getCachedValue(name1)); 664 EXPECT_EQ(r2.value, zone->getCachedValue(name2)); 665 } 666 667 TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode) 668 { 669 // This will add a couple fan inputs, and verify the values are cached. 670 671 std::string name1 = "fan1"; 672 int64_t timeout = 2; 673 674 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 675 timeout); 676 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 677 678 std::string name2 = "fan2"; 679 std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2, 680 timeout); 681 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); 682 683 std::string type = "unchecked"; 684 mgr.addSensor(type, name1, std::move(sensor1)); 685 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 686 mgr.addSensor(type, name2, std::move(sensor2)); 687 EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); 688 689 // Now that the sensors exist, add them to the zone. 690 zone->addFanInput(name1, false); 691 zone->addFanInput(name2, false); 692 693 // Initialize Zone 694 zone->initializeCache(); 695 696 // Verify now in failsafe mode. 697 EXPECT_TRUE(zone->getFailSafeMode()); 698 699 ReadReturn r1; 700 r1.value = 10.0; 701 r1.updated = std::chrono::high_resolution_clock::now(); 702 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 703 704 ReadReturn r2; 705 r2.value = 11.0; 706 r2.updated = std::chrono::high_resolution_clock::now(); 707 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 708 709 // Method under test will read through each fan sensor for the zone and 710 // cache the values. 711 zone->updateFanTelemetry(); 712 713 // We should no longer be in failsafe mode. 714 EXPECT_FALSE(zone->getFailSafeMode()); 715 716 r1.updated -= std::chrono::seconds(3); 717 r2.updated = std::chrono::high_resolution_clock::now(); 718 719 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); 720 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); 721 722 zone->updateFanTelemetry(); 723 EXPECT_TRUE(zone->getFailSafeMode()); 724 } 725 726 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected) 727 { 728 // One can grab a sensor from the manager through the zone. 729 730 int64_t timeout = 1; 731 732 std::string name1 = "temp1"; 733 std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1, 734 timeout); 735 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); 736 737 std::string type = "unchecked"; 738 mgr.addSensor(type, name1, std::move(sensor1)); 739 EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); 740 741 zone->addThermalInput(name1, false); 742 743 // Verify method under test returns the pointer we expect. 744 EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1)); 745 } 746 747 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed) 748 { 749 // Tests adding a thermal PID controller to the zone, and verifies it's 750 // touched during processing. 751 752 std::unique_ptr<PIDController> tpid = 753 std::make_unique<ControllerMock>("thermal1", zone.get()); 754 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get()); 755 756 // Access the internal pid configuration to clear it out (unrelated to the 757 // test). 758 ec::pid_info_t* info = tpid->getPIDInfo(); 759 std::memset(info, 0x00, sizeof(ec::pid_info_t)); 760 761 zone->addThermalPID(std::move(tpid)); 762 763 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); 764 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); 765 EXPECT_CALL(*tmock, outputProc(_)); 766 767 // Method under test will, for each thermal PID, call setpt, input, and 768 // output. 769 zone->processThermals(); 770 } 771 772 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed) 773 { 774 // Tests adding a fan PID controller to the zone, and verifies it's 775 // touched during processing. 776 777 std::unique_ptr<PIDController> tpid = 778 std::make_unique<ControllerMock>("fan1", zone.get()); 779 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get()); 780 781 // Access the internal pid configuration to clear it out (unrelated to the 782 // test). 783 ec::pid_info_t* info = tpid->getPIDInfo(); 784 std::memset(info, 0x00, sizeof(ec::pid_info_t)); 785 786 zone->addFanPID(std::move(tpid)); 787 788 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); 789 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); 790 EXPECT_CALL(*tmock, outputProc(_)); 791 792 // Method under test will, for each fan PID, call setpt, input, and output. 793 zone->processFans(); 794 } 795 796 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected) 797 { 798 // The manual(bool) method is inherited from the dbus mode interface. 799 800 // Verifies that someone doesn't remove the internal call to the dbus 801 // object from which we're inheriting. 802 EXPECT_CALL(sdbus_mock_mode, 803 sd_bus_emit_properties_changed_strv( 804 IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull())) 805 .WillOnce(Invoke( 806 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, 807 [[maybe_unused]] const char* interface, const char** names) { 808 EXPECT_STREQ("Manual", names[0]); 809 return 0; 810 })); 811 812 // Method under test will set the manual mode to true and broadcast this 813 // change on dbus. 814 zone->manual(true); 815 EXPECT_TRUE(zone->getManualMode()); 816 } 817 818 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected) 819 { 820 // This property is implemented by us as read-only, such that trying to 821 // write to it will have no effect. 822 EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode()); 823 } 824 825 } // namespace 826 } // namespace pid_control 827