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