xref: /openbmc/phosphor-pid-control/test/pid_zone_unittest.cpp (revision a4270075f7cbdb2dee38f444a59e25b96d8128f4)
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", "Sensor threshold asserted");
321 
322     EXPECT_EQ(failSafePercent, zone->getFailSafePercent());
323 
324     std::map<std::string, std::pair<std::string, double>> failSensorList =
325         zone->getFailSafeSensors();
326     EXPECT_EQ(1, failSensorList.size());
327     EXPECT_EQ("Sensor threshold asserted", failSensorList["temp1"].first);
328     EXPECT_EQ(failSafePercent, failSensorList["temp1"].second);
329 }
330 
TEST_F(PidZoneTest,GetFailSafePercent_MultiFailedReturnsExpected)331 TEST_F(PidZoneTest, GetFailSafePercent_MultiFailedReturnsExpected)
332 {
333     // Tests when multi sensor failed, and verify the final failsafe's sensor
334     // name and duty as expected.
335 
336     std::vector<std::string> input1 = {"temp1"};
337     std::vector<std::string> input2 = {"temp2"};
338     std::vector<std::string> input3 = {"temp3"};
339     std::vector<double> values = {60, 80, 70};
340 
341     zone->addPidFailSafePercent(input1, values[0]);
342     zone->addPidFailSafePercent(input2, values[1]);
343     zone->addPidFailSafePercent(input3, values[2]);
344 
345     zone->markSensorMissing("temp1", "Sensor threshold asserted");
346     zone->markSensorMissing("temp2", "Sensor reading bad");
347     zone->markSensorMissing("temp3", "Sensor unavailable");
348 
349     EXPECT_EQ(80, zone->getFailSafePercent());
350 
351     std::map<std::string, std::pair<std::string, double>> failSensorList =
352         zone->getFailSafeSensors();
353     EXPECT_EQ(3, failSensorList.size());
354     EXPECT_EQ("Sensor threshold asserted", failSensorList["temp1"].first);
355     EXPECT_EQ(60, failSensorList["temp1"].second);
356     EXPECT_EQ("Sensor reading bad", failSensorList["temp2"].first);
357     EXPECT_EQ(80, failSensorList["temp2"].second);
358     EXPECT_EQ("Sensor unavailable", failSensorList["temp3"].first);
359     EXPECT_EQ(70, failSensorList["temp3"].second);
360 }
361 
TEST_F(PidZoneTest,ThermalInputs_FailsafeToValid_ReadsSensors)362 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors)
363 {
364     // This test will add a couple thermal inputs, and verify that the zone
365     // initializes into failsafe mode, and will read each sensor.
366 
367     // Disable failsafe logger for the unit test.
368     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
369     buildFailsafeLoggers(empty_zone_map, 0);
370 
371     std::string name1 = "temp1";
372     int64_t timeout = 1;
373 
374     std::unique_ptr<Sensor> sensor1 =
375         std::make_unique<SensorMock>(name1, timeout);
376     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
377 
378     std::string name2 = "temp2";
379     std::unique_ptr<Sensor> sensor2 =
380         std::make_unique<SensorMock>(name2, timeout);
381     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
382 
383     std::string type = "unchecked";
384     mgr.addSensor(type, name1, std::move(sensor1));
385     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
386     mgr.addSensor(type, name2, std::move(sensor2));
387     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
388 
389     // Now that the sensors exist, add them to the zone.
390     zone->addThermalInput(name1, false);
391     zone->addThermalInput(name2, false);
392 
393     // Initialize Zone
394     zone->initializeCache();
395 
396     // Verify now in failsafe mode.
397     EXPECT_TRUE(zone->getFailSafeMode());
398 
399     ReadReturn r1;
400     r1.value = 10.0;
401     r1.updated = std::chrono::high_resolution_clock::now();
402     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
403 
404     ReadReturn r2;
405     r2.value = 11.0;
406     r2.updated = std::chrono::high_resolution_clock::now();
407     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
408 
409     // Read the sensors, this will put the values into the cache.
410     zone->updateSensors();
411 
412     // We should no longer be in failsafe mode.
413     EXPECT_FALSE(zone->getFailSafeMode());
414 
415     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
416     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
417 }
418 
TEST_F(PidZoneTest,FanInputTest_VerifiesFanValuesCached)419 TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached)
420 {
421     // This will add a couple fan inputs, and verify the values are cached.
422 
423     // Disable failsafe logger for the unit test.
424     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
425     buildFailsafeLoggers(empty_zone_map, 0);
426 
427     std::string name1 = "fan1";
428     int64_t timeout = 2;
429 
430     std::unique_ptr<Sensor> sensor1 =
431         std::make_unique<SensorMock>(name1, timeout);
432     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
433 
434     std::string name2 = "fan2";
435     std::unique_ptr<Sensor> sensor2 =
436         std::make_unique<SensorMock>(name2, timeout);
437     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
438 
439     std::string type = "unchecked";
440     mgr.addSensor(type, name1, std::move(sensor1));
441     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
442     mgr.addSensor(type, name2, std::move(sensor2));
443     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
444 
445     // Now that the sensors exist, add them to the zone.
446     zone->addFanInput(name1, false);
447     zone->addFanInput(name2, false);
448 
449     // Initialize Zone
450     zone->initializeCache();
451 
452     ReadReturn r1;
453     r1.value = 10.0;
454     r1.updated = std::chrono::high_resolution_clock::now();
455     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
456 
457     ReadReturn r2;
458     r2.value = 11.0;
459     r2.updated = std::chrono::high_resolution_clock::now();
460     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
461 
462     // Method under test will read through each fan sensor for the zone and
463     // cache the values.
464     zone->updateFanTelemetry();
465 
466     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
467     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
468 }
469 
TEST_F(PidZoneTest,ThermalInput_ValueTimeoutEntersFailSafeMode)470 TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
471 {
472     // On the second updateSensors call, the updated timestamp will be beyond
473     // the timeout limit.
474 
475     // Disable failsafe logger for the unit test.
476     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
477     buildFailsafeLoggers(empty_zone_map, 0);
478 
479     int64_t timeout = 1;
480 
481     std::string name1 = "temp1";
482     std::unique_ptr<Sensor> sensor1 =
483         std::make_unique<SensorMock>(name1, timeout);
484     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
485 
486     std::string name2 = "temp2";
487     std::unique_ptr<Sensor> sensor2 =
488         std::make_unique<SensorMock>(name2, timeout);
489     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
490 
491     std::string type = "unchecked";
492     mgr.addSensor(type, name1, std::move(sensor1));
493     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
494     mgr.addSensor(type, name2, std::move(sensor2));
495     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
496 
497     zone->addThermalInput(name1, false);
498     zone->addThermalInput(name2, false);
499 
500     // Initialize Zone
501     zone->initializeCache();
502 
503     // Verify now in failsafe mode.
504     EXPECT_TRUE(zone->getFailSafeMode());
505 
506     ReadReturn r1;
507     r1.value = 10.0;
508     r1.updated = std::chrono::high_resolution_clock::now();
509     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
510 
511     ReadReturn r2;
512     r2.value = 11.0;
513     r2.updated = std::chrono::high_resolution_clock::now();
514     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
515 
516     zone->updateSensors();
517     EXPECT_FALSE(zone->getFailSafeMode());
518 
519     // Ok, so we're not in failsafe mode, so let's set updated to the past.
520     // sensor1 will have an updated field older than its timeout value, but
521     // sensor2 will be fine. :D
522     r1.updated -= std::chrono::seconds(3);
523     r2.updated = std::chrono::high_resolution_clock::now();
524 
525     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
526     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
527 
528     // Method under test will read each sensor.  One sensor's value is older
529     // than the timeout for that sensor and this triggers failsafe mode.
530     zone->updateSensors();
531     EXPECT_TRUE(zone->getFailSafeMode());
532 }
533 
TEST_F(PidZoneTest,ThermalInput_MissingIsAcceptableNoFailSafe)534 TEST_F(PidZoneTest, ThermalInput_MissingIsAcceptableNoFailSafe)
535 {
536     // This is similar to the above test, but because missingIsAcceptable
537     // is set for sensor1, the zone should not enter failsafe mode when
538     // only sensor1 goes missing.
539     // However, sensor2 going missing should still trigger failsafe mode.
540 
541     // Disable failsafe logger for the unit test.
542     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
543     buildFailsafeLoggers(empty_zone_map, 0);
544 
545     int64_t timeout = 1;
546 
547     std::string name1 = "temp1";
548     std::unique_ptr<Sensor> sensor1 =
549         std::make_unique<SensorMock>(name1, timeout);
550     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
551 
552     std::string name2 = "temp2";
553     std::unique_ptr<Sensor> sensor2 =
554         std::make_unique<SensorMock>(name2, timeout);
555     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
556 
557     std::string type = "unchecked";
558     mgr.addSensor(type, name1, std::move(sensor1));
559     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
560     mgr.addSensor(type, name2, std::move(sensor2));
561     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
562 
563     // Only sensor1 has MissingIsAcceptable enabled for it
564     zone->addThermalInput(name1, true);
565     zone->addThermalInput(name2, false);
566 
567     // Initialize Zone
568     zone->initializeCache();
569 
570     // As sensors are not initialized, zone should be in failsafe mode
571     EXPECT_TRUE(zone->getFailSafeMode());
572 
573     // r1 not populated here, intentionally, to simulate a sensor that
574     // is not available yet, perhaps takes a long time to start up.
575     ReadReturn r1;
576     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
577 
578     ReadReturn r2;
579     r2.value = 11.0;
580     r2.updated = std::chrono::high_resolution_clock::now();
581     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
582 
583     zone->updateSensors();
584 
585     // Only sensor2 has been initialized here. Failsafe should be false,
586     // because sensor1 MissingIsAcceptable so it is OK for it to go missing.
587     EXPECT_FALSE(zone->getFailSafeMode());
588 
589     r1.value = 10.0;
590     r1.updated = std::chrono::high_resolution_clock::now();
591 
592     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
593     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
594     zone->updateSensors();
595 
596     // Both sensors are now properly initialized
597     EXPECT_FALSE(zone->getFailSafeMode());
598 
599     // Ok, so we're not in failsafe mode, so let's set updated to the past.
600     // sensor1 will have an updated field older than its timeout value, but
601     // sensor2 will be fine. :D
602     r1.updated -= std::chrono::seconds(3);
603     r2.updated = std::chrono::high_resolution_clock::now();
604 
605     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
606     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
607     zone->updateSensors();
608 
609     // MissingIsAcceptable is true for sensor1, so the zone should not be
610     // thrown into failsafe mode.
611     EXPECT_FALSE(zone->getFailSafeMode());
612 
613     // Do the same thing, but for the opposite sensors: r1 is good,
614     // but r2 is set to some time in the past.
615     r1.updated = std::chrono::high_resolution_clock::now();
616     r2.updated -= std::chrono::seconds(3);
617 
618     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
619     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
620     zone->updateSensors();
621 
622     // Now, the zone should be in failsafe mode, because sensor2 does not
623     // have MissingIsAcceptable set true, it is still subject to failsafe.
624     EXPECT_TRUE(zone->getFailSafeMode());
625 
626     r1.updated = std::chrono::high_resolution_clock::now();
627     r2.updated = std::chrono::high_resolution_clock::now();
628 
629     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
630     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
631     zone->updateSensors();
632 
633     // The failsafe mode should cease, as both sensors are good again.
634     EXPECT_FALSE(zone->getFailSafeMode());
635 }
636 
TEST_F(PidZoneTest,FanInputTest_FailsafeToValid_ReadsSensors)637 TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
638 {
639     // This will add a couple fan inputs, and verify the values are cached.
640 
641     // Disable failsafe logger for the unit test.
642     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
643     buildFailsafeLoggers(empty_zone_map, 0);
644 
645     std::string name1 = "fan1";
646     int64_t timeout = 2;
647 
648     std::unique_ptr<Sensor> sensor1 =
649         std::make_unique<SensorMock>(name1, timeout);
650     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
651 
652     std::string name2 = "fan2";
653     std::unique_ptr<Sensor> sensor2 =
654         std::make_unique<SensorMock>(name2, timeout);
655     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
656 
657     std::string type = "unchecked";
658     mgr.addSensor(type, name1, std::move(sensor1));
659     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
660     mgr.addSensor(type, name2, std::move(sensor2));
661     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
662 
663     // Now that the sensors exist, add them to the zone.
664     zone->addFanInput(name1, false);
665     zone->addFanInput(name2, false);
666 
667     // Initialize Zone
668     zone->initializeCache();
669 
670     // Verify now in failsafe mode.
671     EXPECT_TRUE(zone->getFailSafeMode());
672 
673     ReadReturn r1;
674     r1.value = 10.0;
675     r1.updated = std::chrono::high_resolution_clock::now();
676     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
677 
678     ReadReturn r2;
679     r2.value = 11.0;
680     r2.updated = std::chrono::high_resolution_clock::now();
681     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
682 
683     // Method under test will read through each fan sensor for the zone and
684     // cache the values.
685     zone->updateFanTelemetry();
686 
687     // We should no longer be in failsafe mode.
688     EXPECT_FALSE(zone->getFailSafeMode());
689 
690     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
691     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
692 }
693 
TEST_F(PidZoneTest,FanInputTest_ValueTimeoutEntersFailSafeMode)694 TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode)
695 {
696     // This will add a couple fan inputs, and verify the values are cached.
697 
698     // Disable failsafe logger for the unit test.
699     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
700     buildFailsafeLoggers(empty_zone_map, 0);
701 
702     std::string name1 = "fan1";
703     int64_t timeout = 2;
704 
705     std::unique_ptr<Sensor> sensor1 =
706         std::make_unique<SensorMock>(name1, timeout);
707     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
708 
709     std::string name2 = "fan2";
710     std::unique_ptr<Sensor> sensor2 =
711         std::make_unique<SensorMock>(name2, timeout);
712     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
713 
714     std::string type = "unchecked";
715     mgr.addSensor(type, name1, std::move(sensor1));
716     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
717     mgr.addSensor(type, name2, std::move(sensor2));
718     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
719 
720     // Now that the sensors exist, add them to the zone.
721     zone->addFanInput(name1, false);
722     zone->addFanInput(name2, false);
723 
724     // Initialize Zone
725     zone->initializeCache();
726 
727     // Verify now in failsafe mode.
728     EXPECT_TRUE(zone->getFailSafeMode());
729 
730     ReadReturn r1;
731     r1.value = 10.0;
732     r1.updated = std::chrono::high_resolution_clock::now();
733     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
734 
735     ReadReturn r2;
736     r2.value = 11.0;
737     r2.updated = std::chrono::high_resolution_clock::now();
738     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
739 
740     // Method under test will read through each fan sensor for the zone and
741     // cache the values.
742     zone->updateFanTelemetry();
743 
744     // We should no longer be in failsafe mode.
745     EXPECT_FALSE(zone->getFailSafeMode());
746 
747     r1.updated -= std::chrono::seconds(3);
748     r2.updated = std::chrono::high_resolution_clock::now();
749 
750     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
751     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
752 
753     zone->updateFanTelemetry();
754     EXPECT_TRUE(zone->getFailSafeMode());
755 }
756 
TEST_F(PidZoneTest,GetSensorTest_ReturnsExpected)757 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected)
758 {
759     // One can grab a sensor from the manager through the zone.
760 
761     // Disable failsafe logger for the unit test.
762     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
763     buildFailsafeLoggers(empty_zone_map, 0);
764 
765     int64_t timeout = 1;
766 
767     std::string name1 = "temp1";
768     std::unique_ptr<Sensor> sensor1 =
769         std::make_unique<SensorMock>(name1, timeout);
770     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
771 
772     std::string type = "unchecked";
773     mgr.addSensor(type, name1, std::move(sensor1));
774     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
775 
776     zone->addThermalInput(name1, false);
777 
778     // Verify method under test returns the pointer we expect.
779     EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
780 }
781 
TEST_F(PidZoneTest,AddThermalPIDTest_VerifiesThermalPIDsProcessed)782 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
783 {
784     // Tests adding a thermal PID controller to the zone, and verifies it's
785     // touched during processing.
786 
787     std::unique_ptr<PIDController> tpid =
788         std::make_unique<ControllerMock>("thermal1", zone.get());
789     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
790 
791     // Access the internal pid configuration to clear it out (unrelated to the
792     // test).
793     [[maybe_unused]] ec::pid_info_t* info = tpid->getPIDInfo();
794 
795     zone->addThermalPID(std::move(tpid));
796 
797     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
798     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
799     EXPECT_CALL(*tmock, outputProc(_));
800 
801     // Method under test will, for each thermal PID, call setpt, input, and
802     // output.
803     zone->processThermals();
804 }
805 
TEST_F(PidZoneTest,AddFanPIDTest_VerifiesFanPIDsProcessed)806 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
807 {
808     // Tests adding a fan PID controller to the zone, and verifies it's
809     // touched during processing.
810 
811     std::unique_ptr<PIDController> tpid =
812         std::make_unique<ControllerMock>("fan1", zone.get());
813     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
814 
815     // Access the internal pid configuration to clear it out (unrelated to the
816     // test).
817     [[maybe_unused]] ec::pid_info_t* info = tpid->getPIDInfo();
818 
819     zone->addFanPID(std::move(tpid));
820 
821     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
822     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
823     EXPECT_CALL(*tmock, outputProc(_));
824 
825     // Method under test will, for each fan PID, call setpt, input, and output.
826     zone->processFans();
827 }
828 
TEST_F(PidZoneTest,ManualModeDbusTest_VerifySetManualBehavesAsExpected)829 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
830 {
831     // The manual(bool) method is inherited from the dbus mode interface.
832 
833     // Verifies that someone doesn't remove the internal call to the dbus
834     // object from which we're inheriting.
835     EXPECT_CALL(sdbus_mock_mode,
836                 sd_bus_emit_properties_changed_strv(
837                     IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
838         .WillOnce(Invoke(
839             [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
840                 [[maybe_unused]] const char* interface, const char** names) {
841                 EXPECT_STREQ("Manual", names[0]);
842                 return 0;
843             }));
844 
845     // Method under test will set the manual mode to true and broadcast this
846     // change on dbus.
847     zone->manual(true);
848     EXPECT_TRUE(zone->getManualMode());
849 }
850 
TEST_F(PidZoneTest,FailsafeDbusTest_VerifiesReturnsExpected)851 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
852 {
853     // This property is implemented by us as read-only, such that trying to
854     // write to it will have no effect.
855 
856     // Disable failsafe logger for the unit test.
857     std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
858     buildFailsafeLoggers(empty_zone_map, 0);
859 
860     EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
861 }
862 
863 } // namespace
864 } // namespace pid_control
865