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