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