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