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