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     const char* objPath = "/path/";
57     int64_t zone = 1;
58     double minThermalOutput = 1000.0;
59     double failSafePercent = 0;
60     conf::CycleTime cycleTime;
61 
62     double d;
63     std::vector<std::string> properties;
64     SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties,
65                     &d);
66     SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface,
67                     properties, &d);
68 
69     std::string sensorname = "temp1";
70     std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" +
71                                 sensorname;
72 
73     double de;
74     std::vector<std::string> propertiesenable;
75     SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
76                     enableInterface, propertiesenable, &de);
77 
78     EXPECT_CALL(sdbus_mock_enable,
79                 sd_bus_add_object_vtable(
80                     IsNull(), NotNull(), StrEq(pidsensorpath.c_str()),
81                     StrEq(debugThermalPowerInterface), NotNull(), NotNull()))
82         .Times(::testing::AnyNumber())
83         .WillOnce(Return(0));
84 
85     DbusPidZone p(zone, minThermalOutput, failSafePercent, cycleTime, m,
86                   bus_mock_mode, objPath, defer);
87     // Success.
88 }
89 
90 } // namespace
91 
92 class PidZoneTest : public ::testing::Test
93 {
94   protected:
95     PidZoneTest() :
96         property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(),
97         sdbus_mock_mode(), sdbus_mock_enable()
98     {
99         EXPECT_CALL(sdbus_mock_host,
100                     sd_bus_add_object_manager(
101                         IsNull(), _, StrEq("/xyz/openbmc_project/extsensors")))
102             .WillOnce(Return(0));
103 
104         auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
105         auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
106         auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
107         auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
108 
109         // Compiler weirdly not happy about just instantiating mgr(...);
110         SensorManager m(bus_mock_passive, bus_mock_host);
111         mgr = std::move(m);
112 
113         SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface,
114                         properties, &property_index);
115         SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface,
116                         properties, &property_index);
117 
118         SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
119                         enableInterface, propertiesenable,
120                         &propertyenable_index);
121         EXPECT_CALL(sdbus_mock_enable,
122                     sd_bus_add_object_vtable(IsNull(), NotNull(),
123                                              StrEq(pidsensorpath.c_str()),
124                                              StrEq(debugThermalPowerInterface),
125                                              NotNull(), NotNull()))
126             .Times(::testing::AnyNumber())
127             .WillOnce(Return(0));
128 
129         zone = std::make_unique<DbusPidZone>(zoneId, minThermalOutput,
130                                              failSafePercent, cycleTime, mgr,
131                                              bus_mock_mode, objPath, defer);
132     }
133 
134     // unused
135     double property_index;
136     std::vector<std::string> properties;
137     double propertyenable_index;
138     std::vector<std::string> propertiesenable;
139 
140     sdbusplus::SdBusMock sdbus_mock_passive;
141     sdbusplus::SdBusMock sdbus_mock_host;
142     sdbusplus::SdBusMock sdbus_mock_mode;
143     sdbusplus::SdBusMock sdbus_mock_enable;
144     int64_t zoneId = 1;
145     double minThermalOutput = 1000.0;
146     double failSafePercent = 0;
147     double setpoint = 50.0;
148     bool defer = true;
149     const char* objPath = "/path/";
150     SensorManager mgr;
151     conf::CycleTime cycleTime;
152 
153     std::string sensorname = "temp1";
154     std::string sensorType = "temp";
155     std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" +
156                                 sensorname;
157 
158     std::unique_ptr<DbusPidZone> zone;
159 };
160 
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 
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 
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 
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     ec::pid_info_t* info = tpid->getPIDInfo();
212     std::memset(info, 0x00, sizeof(ec::pid_info_t));
213 
214     zone->addFanPID(std::move(tpid));
215 
216     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
217     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
218     EXPECT_CALL(*tmock, outputProc(_));
219 
220     // while zone is in auto mode redundant writes should be disabled
221     EXPECT_FALSE(zone->getRedundantWrite());
222 
223     // but switching from manual to auto enables a single redundant write
224     zone->setManualMode(true);
225     zone->setManualMode(false);
226     EXPECT_TRUE(zone->getRedundantWrite());
227 
228     // after one iteration of a pid loop redundant write should be cleared
229     zone->processFans();
230     EXPECT_FALSE(zone->getRedundantWrite());
231 }
232 
233 TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected)
234 {
235     // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest
236     // and getMinThermalSetPoint.
237 
238     // Need to add pid control process for the zone that can enable
239     // the process and add the set point.
240     auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
241 
242     EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
243                                      IsNull(), StrEq(pidsensorpath.c_str()),
244                                      StrEq(enableInterface), NotNull()))
245         .Times(::testing::AnyNumber())
246         .WillOnce(Invoke(
247             [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
248                 [[maybe_unused]] const char* interface, const char** names) {
249         EXPECT_STREQ("Enable", names[0]);
250         return 0;
251     }));
252 
253     zone->addPidControlProcess(sensorname, sensorType, setpoint,
254                                bus_mock_enable, pidsensorpath.c_str(), defer);
255 
256     // At least one value must be above the minimum thermal setpoint used in
257     // the constructor otherwise it'll choose that value
258     std::vector<double> values = {100, 200, 300, 400, 500, 5000};
259 
260     for (auto v : values)
261     {
262         zone->addSetPoint(v, sensorname);
263     }
264 
265     // This will pull the maximum RPM setpoint request.
266     zone->determineMaxSetPointRequest();
267     EXPECT_EQ(5000, zone->getMaxSetPointRequest());
268 
269     // Clear the values, so it'll choose the minimum thermal setpoint.
270     zone->clearSetPoints();
271 
272     // This will go through the RPM set point values and grab the maximum.
273     zone->determineMaxSetPointRequest();
274     EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest());
275 }
276 
277 TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected)
278 {
279     // Tests adding several RPM setpoints, however, they're all lower than the
280     // configured minimal thermal setpoint RPM value.
281 
282     // Need to add pid control process for the zone that can enable
283     // the process and add the set point.
284     auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
285 
286     EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
287                                      IsNull(), StrEq(pidsensorpath.c_str()),
288                                      StrEq(enableInterface), NotNull()))
289         .Times(::testing::AnyNumber())
290         .WillOnce(Invoke(
291             [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
292                 [[maybe_unused]] const char* interface, const char** names) {
293         EXPECT_STREQ("Enable", names[0]);
294         return 0;
295     }));
296 
297     zone->addPidControlProcess(sensorname, sensorType, setpoint,
298                                bus_mock_enable, pidsensorpath.c_str(), defer);
299 
300     std::vector<double> values = {100, 200, 300, 400, 500};
301 
302     for (auto v : values)
303     {
304         zone->addSetPoint(v, sensorname);
305     }
306 
307     // This will pull the maximum RPM setpoint request.
308     zone->determineMaxSetPointRequest();
309 
310     // Verifies the value returned in the minimal thermal rpm set point.
311     EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest());
312 }
313 
314 TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected)
315 {
316     // Verify the value used to create the object is stored.
317     // when the final failsafe percent is zero , it indicate
318     // no failsafe percent is configured  , set it to 100% as
319     // the default setting.
320 
321     std::vector<double> values = {0, 0, 0};
322     int64_t defaultPercent = 100;
323 
324     zone->addPidFailSafePercent("temp1", values[0]);
325     zone->addPidFailSafePercent("temp2", values[1]);
326     zone->addPidFailSafePercent("temp3", values[2]);
327 
328     zone->initPidFailSafePercent();
329 
330     EXPECT_EQ(defaultPercent, zone->getFailSafePercent());
331 }
332 
333 TEST_F(PidZoneTest, GetFailSafePercent_VerifyReturnsExpected)
334 {
335     // Tests adding PID controller with FailSafePercent to the zone,
336     // and verifies it's returned as expected.
337 
338     std::vector<double> values = {60, 80, 70};
339     double max_value = 0;
340 
341     for (const auto& value : values)
342     {
343         max_value = std::max(max_value, value);
344     }
345 
346     zone->addPidFailSafePercent("temp1", values[0]);
347     zone->addPidFailSafePercent("temp2", values[1]);
348     zone->addPidFailSafePercent("temp3", values[2]);
349 
350     zone->initPidFailSafePercent();
351 
352     EXPECT_EQ(max_value, zone->getFailSafePercent());
353 }
354 
355 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors)
356 {
357     // This test will add a couple thermal inputs, and verify that the zone
358     // initializes into failsafe mode, and will read each sensor.
359 
360     std::string name1 = "temp1";
361     int64_t timeout = 1;
362 
363     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
364                                                                    timeout);
365     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
366 
367     std::string name2 = "temp2";
368     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
369                                                                    timeout);
370     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
371 
372     std::string type = "unchecked";
373     mgr.addSensor(type, name1, std::move(sensor1));
374     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
375     mgr.addSensor(type, name2, std::move(sensor2));
376     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
377 
378     // Now that the sensors exist, add them to the zone.
379     zone->addThermalInput(name1);
380     zone->addThermalInput(name2);
381 
382     // Initialize Zone
383     zone->initializeCache();
384 
385     // Verify now in failsafe mode.
386     EXPECT_TRUE(zone->getFailSafeMode());
387 
388     ReadReturn r1;
389     r1.value = 10.0;
390     r1.updated = std::chrono::high_resolution_clock::now();
391     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
392 
393     ReadReturn r2;
394     r2.value = 11.0;
395     r2.updated = std::chrono::high_resolution_clock::now();
396     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
397 
398     // Read the sensors, this will put the values into the cache.
399     zone->updateSensors();
400 
401     // We should no longer be in failsafe mode.
402     EXPECT_FALSE(zone->getFailSafeMode());
403 
404     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
405     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
406 }
407 
408 TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached)
409 {
410     // This will add a couple fan inputs, and verify the values are cached.
411 
412     std::string name1 = "fan1";
413     int64_t timeout = 2;
414 
415     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
416                                                                    timeout);
417     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
418 
419     std::string name2 = "fan2";
420     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
421                                                                    timeout);
422     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
423 
424     std::string type = "unchecked";
425     mgr.addSensor(type, name1, std::move(sensor1));
426     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
427     mgr.addSensor(type, name2, std::move(sensor2));
428     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
429 
430     // Now that the sensors exist, add them to the zone.
431     zone->addFanInput(name1);
432     zone->addFanInput(name2);
433 
434     // Initialize Zone
435     zone->initializeCache();
436 
437     ReadReturn r1;
438     r1.value = 10.0;
439     r1.updated = std::chrono::high_resolution_clock::now();
440     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
441 
442     ReadReturn r2;
443     r2.value = 11.0;
444     r2.updated = std::chrono::high_resolution_clock::now();
445     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
446 
447     // Method under test will read through each fan sensor for the zone and
448     // cache the values.
449     zone->updateFanTelemetry();
450 
451     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
452     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
453 }
454 
455 TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
456 {
457     // On the second updateSensors call, the updated timestamp will be beyond
458     // the timeout limit.
459 
460     int64_t timeout = 1;
461 
462     std::string name1 = "temp1";
463     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
464                                                                    timeout);
465     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
466 
467     std::string name2 = "temp2";
468     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
469                                                                    timeout);
470     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
471 
472     std::string type = "unchecked";
473     mgr.addSensor(type, name1, std::move(sensor1));
474     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
475     mgr.addSensor(type, name2, std::move(sensor2));
476     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
477 
478     zone->addThermalInput(name1);
479     zone->addThermalInput(name2);
480 
481     // Initialize Zone
482     zone->initializeCache();
483 
484     // Verify now in failsafe mode.
485     EXPECT_TRUE(zone->getFailSafeMode());
486 
487     ReadReturn r1;
488     r1.value = 10.0;
489     r1.updated = std::chrono::high_resolution_clock::now();
490     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
491 
492     ReadReturn r2;
493     r2.value = 11.0;
494     r2.updated = std::chrono::high_resolution_clock::now();
495     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
496 
497     zone->updateSensors();
498     EXPECT_FALSE(zone->getFailSafeMode());
499 
500     // Ok, so we're not in failsafe mode, so let's set updated to the past.
501     // sensor1 will have an updated field older than its timeout value, but
502     // sensor2 will be fine. :D
503     r1.updated -= std::chrono::seconds(3);
504     r2.updated = std::chrono::high_resolution_clock::now();
505 
506     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
507     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
508 
509     // Method under test will read each sensor.  One sensor's value is older
510     // than the timeout for that sensor and this triggers failsafe mode.
511     zone->updateSensors();
512     EXPECT_TRUE(zone->getFailSafeMode());
513 }
514 
515 TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
516 {
517     // This will add a couple fan inputs, and verify the values are cached.
518 
519     std::string name1 = "fan1";
520     int64_t timeout = 2;
521 
522     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
523                                                                    timeout);
524     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
525 
526     std::string name2 = "fan2";
527     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
528                                                                    timeout);
529     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
530 
531     std::string type = "unchecked";
532     mgr.addSensor(type, name1, std::move(sensor1));
533     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
534     mgr.addSensor(type, name2, std::move(sensor2));
535     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
536 
537     // Now that the sensors exist, add them to the zone.
538     zone->addFanInput(name1);
539     zone->addFanInput(name2);
540 
541     // Initialize Zone
542     zone->initializeCache();
543 
544     // Verify now in failsafe mode.
545     EXPECT_TRUE(zone->getFailSafeMode());
546 
547     ReadReturn r1;
548     r1.value = 10.0;
549     r1.updated = std::chrono::high_resolution_clock::now();
550     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
551 
552     ReadReturn r2;
553     r2.value = 11.0;
554     r2.updated = std::chrono::high_resolution_clock::now();
555     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
556 
557     // Method under test will read through each fan sensor for the zone and
558     // cache the values.
559     zone->updateFanTelemetry();
560 
561     // We should no longer be in failsafe mode.
562     EXPECT_FALSE(zone->getFailSafeMode());
563 
564     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
565     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
566 }
567 
568 TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode)
569 {
570     // This will add a couple fan inputs, and verify the values are cached.
571 
572     std::string name1 = "fan1";
573     int64_t timeout = 2;
574 
575     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
576                                                                    timeout);
577     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
578 
579     std::string name2 = "fan2";
580     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
581                                                                    timeout);
582     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
583 
584     std::string type = "unchecked";
585     mgr.addSensor(type, name1, std::move(sensor1));
586     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
587     mgr.addSensor(type, name2, std::move(sensor2));
588     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
589 
590     // Now that the sensors exist, add them to the zone.
591     zone->addFanInput(name1);
592     zone->addFanInput(name2);
593 
594     // Initialize Zone
595     zone->initializeCache();
596 
597     // Verify now in failsafe mode.
598     EXPECT_TRUE(zone->getFailSafeMode());
599 
600     ReadReturn r1;
601     r1.value = 10.0;
602     r1.updated = std::chrono::high_resolution_clock::now();
603     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
604 
605     ReadReturn r2;
606     r2.value = 11.0;
607     r2.updated = std::chrono::high_resolution_clock::now();
608     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
609 
610     // Method under test will read through each fan sensor for the zone and
611     // cache the values.
612     zone->updateFanTelemetry();
613 
614     // We should no longer be in failsafe mode.
615     EXPECT_FALSE(zone->getFailSafeMode());
616 
617     r1.updated -= std::chrono::seconds(3);
618     r2.updated = std::chrono::high_resolution_clock::now();
619 
620     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
621     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
622 
623     zone->updateFanTelemetry();
624     EXPECT_TRUE(zone->getFailSafeMode());
625 }
626 
627 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected)
628 {
629     // One can grab a sensor from the manager through the zone.
630 
631     int64_t timeout = 1;
632 
633     std::string name1 = "temp1";
634     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
635                                                                    timeout);
636     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
637 
638     std::string type = "unchecked";
639     mgr.addSensor(type, name1, std::move(sensor1));
640     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
641 
642     zone->addThermalInput(name1);
643 
644     // Verify method under test returns the pointer we expect.
645     EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
646 }
647 
648 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
649 {
650     // Tests adding a thermal PID controller to the zone, and verifies it's
651     // touched during processing.
652 
653     std::unique_ptr<PIDController> tpid =
654         std::make_unique<ControllerMock>("thermal1", zone.get());
655     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
656 
657     // Access the internal pid configuration to clear it out (unrelated to the
658     // test).
659     ec::pid_info_t* info = tpid->getPIDInfo();
660     std::memset(info, 0x00, sizeof(ec::pid_info_t));
661 
662     zone->addThermalPID(std::move(tpid));
663 
664     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
665     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
666     EXPECT_CALL(*tmock, outputProc(_));
667 
668     // Method under test will, for each thermal PID, call setpt, input, and
669     // output.
670     zone->processThermals();
671 }
672 
673 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
674 {
675     // Tests adding a fan PID controller to the zone, and verifies it's
676     // touched during processing.
677 
678     std::unique_ptr<PIDController> tpid =
679         std::make_unique<ControllerMock>("fan1", zone.get());
680     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
681 
682     // Access the internal pid configuration to clear it out (unrelated to the
683     // test).
684     ec::pid_info_t* info = tpid->getPIDInfo();
685     std::memset(info, 0x00, sizeof(ec::pid_info_t));
686 
687     zone->addFanPID(std::move(tpid));
688 
689     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
690     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
691     EXPECT_CALL(*tmock, outputProc(_));
692 
693     // Method under test will, for each fan PID, call setpt, input, and output.
694     zone->processFans();
695 }
696 
697 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
698 {
699     // The manual(bool) method is inherited from the dbus mode interface.
700 
701     // Verifies that someone doesn't remove the internal call to the dbus
702     // object from which we're inheriting.
703     EXPECT_CALL(sdbus_mock_mode,
704                 sd_bus_emit_properties_changed_strv(
705                     IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
706         .WillOnce(Invoke(
707             [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
708                 [[maybe_unused]] const char* interface, const char** names) {
709         EXPECT_STREQ("Manual", names[0]);
710         return 0;
711     }));
712 
713     // Method under test will set the manual mode to true and broadcast this
714     // change on dbus.
715     zone->manual(true);
716     EXPECT_TRUE(zone->getManualMode());
717 }
718 
719 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
720 {
721     // This property is implemented by us as read-only, such that trying to
722     // write to it will have no effect.
723     EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
724 }
725 
726 } // namespace
727 } // namespace pid_control
728