#include "pid/ec/logging.hpp" #include "pid/ec/pid.hpp" #include "pid/zone.hpp" #include "sensors/manager.hpp" #include "test/controller_mock.hpp" #include "test/helpers.hpp" #include "test/sensor_mock.hpp" #include #include #include #include #include #include namespace pid_control { namespace { using ::testing::_; using ::testing::IsNull; using ::testing::Return; using ::testing::StrEq; static std::string modeInterface = "xyz.openbmc_project.Control.Mode"; static std::string debugZoneInterface = "xyz.openbmc_project.Debug.Pid.Zone"; static std::string enableInterface = "xyz.openbmc_project.Object.Enable"; static std::string debugThermalPowerInterface = "xyz.openbmc_project.Debug.Pid.ThermalPower"; namespace { TEST(PidZoneConstructorTest, BoringConstructorTest) { // Build a PID Zone. sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode, sdbus_mock_enable; auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); EXPECT_CALL(sdbus_mock_host, sd_bus_add_object_manager( IsNull(), _, StrEq("/xyz/openbmc_project/extsensors"))) .WillOnce(Return(0)); SensorManager m(bus_mock_passive, bus_mock_host); bool defer = true; bool accSetPoint = false; const char* objPath = "/path/"; int64_t zone = 1; double minThermalOutput = 1000.0; double failSafePercent = 0; conf::CycleTime cycleTime; double d; std::vector properties; SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties, &d); SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface, properties, &d); std::string sensorname = "temp1"; std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" + sensorname; double de; std::vector propertiesenable; SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(), enableInterface, propertiesenable, &de); EXPECT_CALL(sdbus_mock_enable, sd_bus_add_object_vtable( IsNull(), NotNull(), StrEq(pidsensorpath.c_str()), StrEq(debugThermalPowerInterface), NotNull(), NotNull())) .Times(::testing::AnyNumber()) .WillOnce(Return(0)); DbusPidZone p(zone, minThermalOutput, failSafePercent, cycleTime, m, bus_mock_mode, objPath, defer, accSetPoint); // Success. } } // namespace class PidZoneTest : public ::testing::Test { protected: PidZoneTest() : property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(), sdbus_mock_mode(), sdbus_mock_enable() { EXPECT_CALL(sdbus_mock_host, sd_bus_add_object_manager( IsNull(), _, StrEq("/xyz/openbmc_project/extsensors"))) .WillOnce(Return(0)); auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); // Compiler weirdly not happy about just instantiating mgr(...); SensorManager m(bus_mock_passive, bus_mock_host); mgr = std::move(m); SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties, &property_index); SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface, properties, &property_index); SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(), enableInterface, propertiesenable, &propertyenable_index); EXPECT_CALL(sdbus_mock_enable, sd_bus_add_object_vtable(IsNull(), NotNull(), StrEq(pidsensorpath.c_str()), StrEq(debugThermalPowerInterface), NotNull(), NotNull())) .Times(::testing::AnyNumber()) .WillOnce(Return(0)); zone = std::make_unique( zoneId, minThermalOutput, failSafePercent, cycleTime, mgr, bus_mock_mode, objPath, defer, accSetPoint); } // unused double property_index; std::vector properties; double propertyenable_index; std::vector propertiesenable; sdbusplus::SdBusMock sdbus_mock_passive; sdbusplus::SdBusMock sdbus_mock_host; sdbusplus::SdBusMock sdbus_mock_mode; sdbusplus::SdBusMock sdbus_mock_enable; int64_t zoneId = 1; double minThermalOutput = 1000.0; double failSafePercent = 0; double setpoint = 50.0; bool defer = true; bool accSetPoint = false; const char* objPath = "/path/"; SensorManager mgr; conf::CycleTime cycleTime; std::string sensorname = "temp1"; std::string sensorType = "temp"; std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" + sensorname; std::unique_ptr zone; }; TEST_F(PidZoneTest, GetZoneId_ReturnsExpected) { // Verifies the zoneId returned is what we expect. EXPECT_EQ(zoneId, zone->getZoneID()); } TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected) { // Verifies that the zone starts in manual mode. Verifies that one can set // the mode. EXPECT_FALSE(zone->getManualMode()); zone->setManualMode(true); EXPECT_TRUE(zone->getManualMode()); } TEST_F(PidZoneTest, AddPidControlProcessGetAndSetEnableTest_BehavesAsExpected) { // Verifies that the zone starts in enable mode. Verifies that one can set // enable the mode. auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( IsNull(), StrEq(pidsensorpath.c_str()), StrEq(enableInterface), NotNull())) .Times(::testing::AnyNumber()) .WillOnce(Invoke( [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, [[maybe_unused]] const char* interface, const char** names) { EXPECT_STREQ("Enable", names[0]); return 0; })); zone->addPidControlProcess(sensorname, sensorType, setpoint, bus_mock_enable, pidsensorpath.c_str(), defer); EXPECT_TRUE(zone->isPidProcessEnabled(sensorname)); } TEST_F(PidZoneTest, SetManualMode_RedundantWritesEnabledOnceAfterManualMode) { // Tests adding a fan PID controller to the zone, and verifies it's // touched during processing. std::unique_ptr tpid = std::make_unique("fan1", zone.get()); ControllerMock* tmock = reinterpret_cast(tpid.get()); // Access the internal pid configuration to clear it out (unrelated to the // test). ec::pid_info_t* info = tpid->getPIDInfo(); std::memset(info, 0x00, sizeof(ec::pid_info_t)); zone->addFanPID(std::move(tpid)); EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); EXPECT_CALL(*tmock, outputProc(_)); // while zone is in auto mode redundant writes should be disabled EXPECT_FALSE(zone->getRedundantWrite()); // but switching from manual to auto enables a single redundant write zone->setManualMode(true); zone->setManualMode(false); EXPECT_TRUE(zone->getRedundantWrite()); // after one iteration of a pid loop redundant write should be cleared zone->processFans(); EXPECT_FALSE(zone->getRedundantWrite()); } TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected) { // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest // and getMinThermalSetPoint. // Need to add pid control process for the zone that can enable // the process and add the set point. auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( IsNull(), StrEq(pidsensorpath.c_str()), StrEq(enableInterface), NotNull())) .Times(::testing::AnyNumber()) .WillOnce(Invoke( [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, [[maybe_unused]] const char* interface, const char** names) { EXPECT_STREQ("Enable", names[0]); return 0; })); zone->addPidControlProcess(sensorname, sensorType, setpoint, bus_mock_enable, pidsensorpath.c_str(), defer); // At least one value must be above the minimum thermal setpoint used in // the constructor otherwise it'll choose that value std::vector values = {100, 200, 300, 400, 500, 5000}; for (auto v : values) { zone->addSetPoint(v, sensorname); } // This will pull the maximum RPM setpoint request. zone->determineMaxSetPointRequest(); EXPECT_EQ(5000, zone->getMaxSetPointRequest()); // Clear the values, so it'll choose the minimum thermal setpoint. zone->clearSetPoints(); // This will go through the RPM set point values and grab the maximum. zone->determineMaxSetPointRequest(); EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest()); } TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected) { // Tests adding several RPM setpoints, however, they're all lower than the // configured minimal thermal setpoint RPM value. // Need to add pid control process for the zone that can enable // the process and add the set point. auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable); EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( IsNull(), StrEq(pidsensorpath.c_str()), StrEq(enableInterface), NotNull())) .Times(::testing::AnyNumber()) .WillOnce(Invoke( [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, [[maybe_unused]] const char* interface, const char** names) { EXPECT_STREQ("Enable", names[0]); return 0; })); zone->addPidControlProcess(sensorname, sensorType, setpoint, bus_mock_enable, pidsensorpath.c_str(), defer); std::vector values = {100, 200, 300, 400, 500}; for (auto v : values) { zone->addSetPoint(v, sensorname); } // This will pull the maximum RPM setpoint request. zone->determineMaxSetPointRequest(); // Verifies the value returned in the minimal thermal rpm set point. EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest()); } TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected) { // Verify the value used to create the object is stored. // when the final failsafe percent is zero , it indicate // no failsafe percent is configured  , set it to 100% as // the default setting. std::vector values = {0, 0, 0}; int64_t defaultPercent = 100; zone->addPidFailSafePercent("temp1", values[0]); zone->addPidFailSafePercent("temp2", values[1]); zone->addPidFailSafePercent("temp3", values[2]); zone->initPidFailSafePercent(); EXPECT_EQ(defaultPercent, zone->getFailSafePercent()); } TEST_F(PidZoneTest, GetFailSafePercent_VerifyReturnsExpected) { // Tests adding PID controller with FailSafePercent to the zone, // and verifies it's returned as expected. std::vector values = {60, 80, 70}; double max_value = 0; for (const auto& value : values) { max_value = std::max(max_value, value); } zone->addPidFailSafePercent("temp1", values[0]); zone->addPidFailSafePercent("temp2", values[1]); zone->addPidFailSafePercent("temp3", values[2]); zone->initPidFailSafePercent(); EXPECT_EQ(max_value, zone->getFailSafePercent()); } TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors) { // This test will add a couple thermal inputs, and verify that the zone // initializes into failsafe mode, and will read each sensor. std::string name1 = "temp1"; int64_t timeout = 1; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string name2 = "temp2"; std::unique_ptr sensor2 = std::make_unique(name2, timeout); SensorMock* sensor_ptr2 = reinterpret_cast(sensor2.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); mgr.addSensor(type, name2, std::move(sensor2)); EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); // Now that the sensors exist, add them to the zone. zone->addThermalInput(name1, false); zone->addThermalInput(name2, false); // Initialize Zone zone->initializeCache(); // Verify now in failsafe mode. EXPECT_TRUE(zone->getFailSafeMode()); ReadReturn r1; r1.value = 10.0; r1.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); ReadReturn r2; r2.value = 11.0; r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); // Read the sensors, this will put the values into the cache. zone->updateSensors(); // We should no longer be in failsafe mode. EXPECT_FALSE(zone->getFailSafeMode()); EXPECT_EQ(r1.value, zone->getCachedValue(name1)); EXPECT_EQ(r2.value, zone->getCachedValue(name2)); } TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached) { // This will add a couple fan inputs, and verify the values are cached. std::string name1 = "fan1"; int64_t timeout = 2; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string name2 = "fan2"; std::unique_ptr sensor2 = std::make_unique(name2, timeout); SensorMock* sensor_ptr2 = reinterpret_cast(sensor2.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); mgr.addSensor(type, name2, std::move(sensor2)); EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); // Now that the sensors exist, add them to the zone. zone->addFanInput(name1, false); zone->addFanInput(name2, false); // Initialize Zone zone->initializeCache(); ReadReturn r1; r1.value = 10.0; r1.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); ReadReturn r2; r2.value = 11.0; r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); // Method under test will read through each fan sensor for the zone and // cache the values. zone->updateFanTelemetry(); EXPECT_EQ(r1.value, zone->getCachedValue(name1)); EXPECT_EQ(r2.value, zone->getCachedValue(name2)); } TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode) { // On the second updateSensors call, the updated timestamp will be beyond // the timeout limit. int64_t timeout = 1; std::string name1 = "temp1"; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string name2 = "temp2"; std::unique_ptr sensor2 = std::make_unique(name2, timeout); SensorMock* sensor_ptr2 = reinterpret_cast(sensor2.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); mgr.addSensor(type, name2, std::move(sensor2)); EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); zone->addThermalInput(name1, false); zone->addThermalInput(name2, false); // Initialize Zone zone->initializeCache(); // Verify now in failsafe mode. EXPECT_TRUE(zone->getFailSafeMode()); ReadReturn r1; r1.value = 10.0; r1.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); ReadReturn r2; r2.value = 11.0; r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateSensors(); EXPECT_FALSE(zone->getFailSafeMode()); // Ok, so we're not in failsafe mode, so let's set updated to the past. // sensor1 will have an updated field older than its timeout value, but // sensor2 will be fine. :D r1.updated -= std::chrono::seconds(3); r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); // Method under test will read each sensor. One sensor's value is older // than the timeout for that sensor and this triggers failsafe mode. zone->updateSensors(); EXPECT_TRUE(zone->getFailSafeMode()); } TEST_F(PidZoneTest, ThermalInput_MissingIsAcceptableNoFailSafe) { // This is similar to the above test, but because missingIsAcceptable // is set for sensor1, the zone should not enter failsafe mode when // only sensor1 goes missing. // However, sensor2 going missing should still trigger failsafe mode. int64_t timeout = 1; std::string name1 = "temp1"; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string name2 = "temp2"; std::unique_ptr sensor2 = std::make_unique(name2, timeout); SensorMock* sensor_ptr2 = reinterpret_cast(sensor2.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); mgr.addSensor(type, name2, std::move(sensor2)); EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); // Only sensor1 has MissingIsAcceptable enabled for it zone->addThermalInput(name1, true); zone->addThermalInput(name2, false); // Initialize Zone zone->initializeCache(); // As sensors are not initialized, zone should be in failsafe mode EXPECT_TRUE(zone->getFailSafeMode()); // r1 not populated here, intentionally, to simulate a sensor that // is not available yet, perhaps takes a long time to start up. ReadReturn r1; EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); ReadReturn r2; r2.value = 11.0; r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateSensors(); // Only sensor2 has been initialized here. Failsafe should be false, // because sensor1 MissingIsAcceptable so it is OK for it to go missing. EXPECT_FALSE(zone->getFailSafeMode()); r1.value = 10.0; r1.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateSensors(); // Both sensors are now properly initialized EXPECT_FALSE(zone->getFailSafeMode()); // Ok, so we're not in failsafe mode, so let's set updated to the past. // sensor1 will have an updated field older than its timeout value, but // sensor2 will be fine. :D r1.updated -= std::chrono::seconds(3); r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateSensors(); // MissingIsAcceptable is true for sensor1, so the zone should not be // thrown into failsafe mode. EXPECT_FALSE(zone->getFailSafeMode()); // Do the same thing, but for the opposite sensors: r1 is good, // but r2 is set to some time in the past. r1.updated = std::chrono::high_resolution_clock::now(); r2.updated -= std::chrono::seconds(3); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateSensors(); // Now, the zone should be in failsafe mode, because sensor2 does not // have MissingIsAcceptable set true, it is still subject to failsafe. EXPECT_TRUE(zone->getFailSafeMode()); r1.updated = std::chrono::high_resolution_clock::now(); r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateSensors(); // The failsafe mode should cease, as both sensors are good again. EXPECT_FALSE(zone->getFailSafeMode()); } TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors) { // This will add a couple fan inputs, and verify the values are cached. std::string name1 = "fan1"; int64_t timeout = 2; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string name2 = "fan2"; std::unique_ptr sensor2 = std::make_unique(name2, timeout); SensorMock* sensor_ptr2 = reinterpret_cast(sensor2.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); mgr.addSensor(type, name2, std::move(sensor2)); EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); // Now that the sensors exist, add them to the zone. zone->addFanInput(name1, false); zone->addFanInput(name2, false); // Initialize Zone zone->initializeCache(); // Verify now in failsafe mode. EXPECT_TRUE(zone->getFailSafeMode()); ReadReturn r1; r1.value = 10.0; r1.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); ReadReturn r2; r2.value = 11.0; r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); // Method under test will read through each fan sensor for the zone and // cache the values. zone->updateFanTelemetry(); // We should no longer be in failsafe mode. EXPECT_FALSE(zone->getFailSafeMode()); EXPECT_EQ(r1.value, zone->getCachedValue(name1)); EXPECT_EQ(r2.value, zone->getCachedValue(name2)); } TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode) { // This will add a couple fan inputs, and verify the values are cached. std::string name1 = "fan1"; int64_t timeout = 2; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string name2 = "fan2"; std::unique_ptr sensor2 = std::make_unique(name2, timeout); SensorMock* sensor_ptr2 = reinterpret_cast(sensor2.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); mgr.addSensor(type, name2, std::move(sensor2)); EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); // Now that the sensors exist, add them to the zone. zone->addFanInput(name1, false); zone->addFanInput(name2, false); // Initialize Zone zone->initializeCache(); // Verify now in failsafe mode. EXPECT_TRUE(zone->getFailSafeMode()); ReadReturn r1; r1.value = 10.0; r1.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); ReadReturn r2; r2.value = 11.0; r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); // Method under test will read through each fan sensor for the zone and // cache the values. zone->updateFanTelemetry(); // We should no longer be in failsafe mode. EXPECT_FALSE(zone->getFailSafeMode()); r1.updated -= std::chrono::seconds(3); r2.updated = std::chrono::high_resolution_clock::now(); EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); zone->updateFanTelemetry(); EXPECT_TRUE(zone->getFailSafeMode()); } TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected) { // One can grab a sensor from the manager through the zone. int64_t timeout = 1; std::string name1 = "temp1"; std::unique_ptr sensor1 = std::make_unique(name1, timeout); SensorMock* sensor_ptr1 = reinterpret_cast(sensor1.get()); std::string type = "unchecked"; mgr.addSensor(type, name1, std::move(sensor1)); EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); zone->addThermalInput(name1, false); // Verify method under test returns the pointer we expect. EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1)); } TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed) { // Tests adding a thermal PID controller to the zone, and verifies it's // touched during processing. std::unique_ptr tpid = std::make_unique("thermal1", zone.get()); ControllerMock* tmock = reinterpret_cast(tpid.get()); // Access the internal pid configuration to clear it out (unrelated to the // test). ec::pid_info_t* info = tpid->getPIDInfo(); std::memset(info, 0x00, sizeof(ec::pid_info_t)); zone->addThermalPID(std::move(tpid)); EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); EXPECT_CALL(*tmock, outputProc(_)); // Method under test will, for each thermal PID, call setpt, input, and // output. zone->processThermals(); } TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed) { // Tests adding a fan PID controller to the zone, and verifies it's // touched during processing. std::unique_ptr tpid = std::make_unique("fan1", zone.get()); ControllerMock* tmock = reinterpret_cast(tpid.get()); // Access the internal pid configuration to clear it out (unrelated to the // test). ec::pid_info_t* info = tpid->getPIDInfo(); std::memset(info, 0x00, sizeof(ec::pid_info_t)); zone->addFanPID(std::move(tpid)); EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0)); EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0)); EXPECT_CALL(*tmock, outputProc(_)); // Method under test will, for each fan PID, call setpt, input, and output. zone->processFans(); } TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected) { // The manual(bool) method is inherited from the dbus mode interface. // Verifies that someone doesn't remove the internal call to the dbus // object from which we're inheriting. EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv( IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull())) .WillOnce(Invoke( [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path, [[maybe_unused]] const char* interface, const char** names) { EXPECT_STREQ("Manual", names[0]); return 0; })); // Method under test will set the manual mode to true and broadcast this // change on dbus. zone->manual(true); EXPECT_TRUE(zone->getManualMode()); } TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected) { // This property is implemented by us as read-only, such that trying to // write to it will have no effect. EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode()); } } // namespace } // namespace pid_control