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