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