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