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