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