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, false);
380     zone->addThermalInput(name2, false);
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, false);
432     zone->addFanInput(name2, false);
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, false);
479     zone->addThermalInput(name2, false);
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, ThermalInput_MissingIsAcceptableNoFailSafe)
516 {
517     // This is similar to the above test, but because missingIsAcceptable
518     // is set for sensor1, the zone should not enter failsafe mode when
519     // only sensor1 goes missing.
520     // However, sensor2 going missing should still trigger failsafe mode.
521 
522     int64_t timeout = 1;
523 
524     std::string name1 = "temp1";
525     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
526                                                                    timeout);
527     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
528 
529     std::string name2 = "temp2";
530     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
531                                                                    timeout);
532     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
533 
534     std::string type = "unchecked";
535     mgr.addSensor(type, name1, std::move(sensor1));
536     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
537     mgr.addSensor(type, name2, std::move(sensor2));
538     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
539 
540     // Only sensor1 has MissingIsAcceptable enabled for it
541     zone->addThermalInput(name1, true);
542     zone->addThermalInput(name2, false);
543 
544     // Initialize Zone
545     zone->initializeCache();
546 
547     // As sensors are not initialized, zone should be in failsafe mode
548     EXPECT_TRUE(zone->getFailSafeMode());
549 
550     // r1 not populated here, intentionally, to simulate a sensor that
551     // is not available yet, perhaps takes a long time to start up.
552     ReadReturn r1;
553     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
554 
555     ReadReturn r2;
556     r2.value = 11.0;
557     r2.updated = std::chrono::high_resolution_clock::now();
558     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
559 
560     zone->updateSensors();
561 
562     // Only sensor2 has been initialized here. Failsafe should be false,
563     // because sensor1 MissingIsAcceptable so it is OK for it to go missing.
564     EXPECT_FALSE(zone->getFailSafeMode());
565 
566     r1.value = 10.0;
567     r1.updated = std::chrono::high_resolution_clock::now();
568 
569     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
570     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
571     zone->updateSensors();
572 
573     // Both sensors are now properly initialized
574     EXPECT_FALSE(zone->getFailSafeMode());
575 
576     // Ok, so we're not in failsafe mode, so let's set updated to the past.
577     // sensor1 will have an updated field older than its timeout value, but
578     // sensor2 will be fine. :D
579     r1.updated -= std::chrono::seconds(3);
580     r2.updated = std::chrono::high_resolution_clock::now();
581 
582     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
583     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
584     zone->updateSensors();
585 
586     // MissingIsAcceptable is true for sensor1, so the zone should not be
587     // thrown into failsafe mode.
588     EXPECT_FALSE(zone->getFailSafeMode());
589 
590     // Do the same thing, but for the opposite sensors: r1 is good,
591     // but r2 is set to some time in the past.
592     r1.updated = std::chrono::high_resolution_clock::now();
593     r2.updated -= std::chrono::seconds(3);
594 
595     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
596     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
597     zone->updateSensors();
598 
599     // Now, the zone should be in failsafe mode, because sensor2 does not
600     // have MissingIsAcceptable set true, it is still subject to failsafe.
601     EXPECT_TRUE(zone->getFailSafeMode());
602 
603     r1.updated = std::chrono::high_resolution_clock::now();
604     r2.updated = std::chrono::high_resolution_clock::now();
605 
606     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
607     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
608     zone->updateSensors();
609 
610     // The failsafe mode should cease, as both sensors are good again.
611     EXPECT_FALSE(zone->getFailSafeMode());
612 }
613 
614 TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
615 {
616     // This will add a couple fan inputs, and verify the values are cached.
617 
618     std::string name1 = "fan1";
619     int64_t timeout = 2;
620 
621     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
622                                                                    timeout);
623     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
624 
625     std::string name2 = "fan2";
626     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
627                                                                    timeout);
628     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
629 
630     std::string type = "unchecked";
631     mgr.addSensor(type, name1, std::move(sensor1));
632     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
633     mgr.addSensor(type, name2, std::move(sensor2));
634     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
635 
636     // Now that the sensors exist, add them to the zone.
637     zone->addFanInput(name1, false);
638     zone->addFanInput(name2, false);
639 
640     // Initialize Zone
641     zone->initializeCache();
642 
643     // Verify now in failsafe mode.
644     EXPECT_TRUE(zone->getFailSafeMode());
645 
646     ReadReturn r1;
647     r1.value = 10.0;
648     r1.updated = std::chrono::high_resolution_clock::now();
649     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
650 
651     ReadReturn r2;
652     r2.value = 11.0;
653     r2.updated = std::chrono::high_resolution_clock::now();
654     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
655 
656     // Method under test will read through each fan sensor for the zone and
657     // cache the values.
658     zone->updateFanTelemetry();
659 
660     // We should no longer be in failsafe mode.
661     EXPECT_FALSE(zone->getFailSafeMode());
662 
663     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
664     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
665 }
666 
667 TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode)
668 {
669     // This will add a couple fan inputs, and verify the values are cached.
670 
671     std::string name1 = "fan1";
672     int64_t timeout = 2;
673 
674     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
675                                                                    timeout);
676     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
677 
678     std::string name2 = "fan2";
679     std::unique_ptr<Sensor> sensor2 = std::make_unique<SensorMock>(name2,
680                                                                    timeout);
681     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
682 
683     std::string type = "unchecked";
684     mgr.addSensor(type, name1, std::move(sensor1));
685     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
686     mgr.addSensor(type, name2, std::move(sensor2));
687     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
688 
689     // Now that the sensors exist, add them to the zone.
690     zone->addFanInput(name1, false);
691     zone->addFanInput(name2, false);
692 
693     // Initialize Zone
694     zone->initializeCache();
695 
696     // Verify now in failsafe mode.
697     EXPECT_TRUE(zone->getFailSafeMode());
698 
699     ReadReturn r1;
700     r1.value = 10.0;
701     r1.updated = std::chrono::high_resolution_clock::now();
702     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
703 
704     ReadReturn r2;
705     r2.value = 11.0;
706     r2.updated = std::chrono::high_resolution_clock::now();
707     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
708 
709     // Method under test will read through each fan sensor for the zone and
710     // cache the values.
711     zone->updateFanTelemetry();
712 
713     // We should no longer be in failsafe mode.
714     EXPECT_FALSE(zone->getFailSafeMode());
715 
716     r1.updated -= std::chrono::seconds(3);
717     r2.updated = std::chrono::high_resolution_clock::now();
718 
719     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
720     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
721 
722     zone->updateFanTelemetry();
723     EXPECT_TRUE(zone->getFailSafeMode());
724 }
725 
726 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected)
727 {
728     // One can grab a sensor from the manager through the zone.
729 
730     int64_t timeout = 1;
731 
732     std::string name1 = "temp1";
733     std::unique_ptr<Sensor> sensor1 = std::make_unique<SensorMock>(name1,
734                                                                    timeout);
735     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
736 
737     std::string type = "unchecked";
738     mgr.addSensor(type, name1, std::move(sensor1));
739     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
740 
741     zone->addThermalInput(name1, false);
742 
743     // Verify method under test returns the pointer we expect.
744     EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
745 }
746 
747 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
748 {
749     // Tests adding a thermal PID controller to the zone, and verifies it's
750     // touched during processing.
751 
752     std::unique_ptr<PIDController> tpid =
753         std::make_unique<ControllerMock>("thermal1", zone.get());
754     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
755 
756     // Access the internal pid configuration to clear it out (unrelated to the
757     // test).
758     ec::pid_info_t* info = tpid->getPIDInfo();
759     std::memset(info, 0x00, sizeof(ec::pid_info_t));
760 
761     zone->addThermalPID(std::move(tpid));
762 
763     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
764     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
765     EXPECT_CALL(*tmock, outputProc(_));
766 
767     // Method under test will, for each thermal PID, call setpt, input, and
768     // output.
769     zone->processThermals();
770 }
771 
772 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
773 {
774     // Tests adding a fan PID controller to the zone, and verifies it's
775     // touched during processing.
776 
777     std::unique_ptr<PIDController> tpid =
778         std::make_unique<ControllerMock>("fan1", zone.get());
779     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
780 
781     // Access the internal pid configuration to clear it out (unrelated to the
782     // test).
783     ec::pid_info_t* info = tpid->getPIDInfo();
784     std::memset(info, 0x00, sizeof(ec::pid_info_t));
785 
786     zone->addFanPID(std::move(tpid));
787 
788     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
789     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
790     EXPECT_CALL(*tmock, outputProc(_));
791 
792     // Method under test will, for each fan PID, call setpt, input, and output.
793     zone->processFans();
794 }
795 
796 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
797 {
798     // The manual(bool) method is inherited from the dbus mode interface.
799 
800     // Verifies that someone doesn't remove the internal call to the dbus
801     // object from which we're inheriting.
802     EXPECT_CALL(sdbus_mock_mode,
803                 sd_bus_emit_properties_changed_strv(
804                     IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
805         .WillOnce(Invoke(
806             [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
807                 [[maybe_unused]] const char* interface, const char** names) {
808         EXPECT_STREQ("Manual", names[0]);
809         return 0;
810     }));
811 
812     // Method under test will set the manual mode to true and broadcast this
813     // change on dbus.
814     zone->manual(true);
815     EXPECT_TRUE(zone->getManualMode());
816 }
817 
818 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
819 {
820     // This property is implemented by us as read-only, such that trying to
821     // write to it will have no effect.
822     EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
823 }
824 
825 } // namespace
826 } // namespace pid_control
827