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