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