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     int i;
49     std::vector<std::string> properties;
50     SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties,
51                     &i);
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     int 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 addRPMSetPoint, clearRPMSetPoints, determineMaxRPMRequest
125     // and getMinThermalRPMSetpoint.
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->addRPMSetPoint(v);
133     }
134 
135     // This will pull the maximum RPM setpoint request.
136     zone->determineMaxRPMRequest();
137     EXPECT_EQ(5000, zone->getMaxRPMRequest());
138 
139     // Clear the values, so it'll choose the minimum thermal setpoint.
140     zone->clearRPMSetPoints();
141 
142     // This will go through the RPM set point values and grab the maximum.
143     zone->determineMaxRPMRequest();
144     EXPECT_EQ(zone->getMinThermalRPMSetpoint(), zone->getMaxRPMRequest());
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->addRPMSetPoint(v);
156     }
157 
158     // This will pull the maximum RPM setpoint request.
159     zone->determineMaxRPMRequest();
160 
161     // Verifies the value returned in the minimal thermal rpm set point.
162     EXPECT_EQ(zone->getMinThermalRPMSetpoint(), zone->getMaxRPMRequest());
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, GetSensorTest_ReturnsExpected)
332 {
333     // One can grab a sensor from the manager through the zone.
334 
335     int64_t timeout = 1;
336 
337     std::string name1 = "temp1";
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 type = "unchecked";
343     mgr.addSensor(type, name1, std::move(sensor1));
344     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
345 
346     zone->addThermalInput(name1);
347 
348     // Verify method under test returns the pointer we expect.
349     EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
350 }
351 
352 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
353 {
354     // Tests adding a thermal PID controller to the zone, and verifies it's
355     // touched during processing.
356 
357     std::unique_ptr<PIDController> tpid =
358         std::make_unique<ControllerMock>("thermal1", zone.get());
359     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
360 
361     // Access the internal pid configuration to clear it out (unrelated to the
362     // test).
363     ec::pid_info_t* info = tpid->getPIDInfo();
364     std::memset(info, 0x00, sizeof(ec::pid_info_t));
365 
366     zone->addThermalPID(std::move(tpid));
367 
368     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
369     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
370     EXPECT_CALL(*tmock, outputProc(_));
371 
372     // Method under test will, for each thermal PID, call setpt, input, and
373     // output.
374     zone->processThermals();
375 }
376 
377 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
378 {
379     // Tests adding a fan PID controller to the zone, and verifies it's
380     // touched during processing.
381 
382     std::unique_ptr<PIDController> tpid =
383         std::make_unique<ControllerMock>("fan1", zone.get());
384     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
385 
386     // Access the internal pid configuration to clear it out (unrelated to the
387     // test).
388     ec::pid_info_t* info = tpid->getPIDInfo();
389     std::memset(info, 0x00, sizeof(ec::pid_info_t));
390 
391     zone->addFanPID(std::move(tpid));
392 
393     EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
394     EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
395     EXPECT_CALL(*tmock, outputProc(_));
396 
397     // Method under test will, for each fan PID, call setpt, input, and output.
398     zone->processFans();
399 }
400 
401 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
402 {
403     // The manual(bool) method is inherited from the dbus mode interface.
404 
405     // Verifies that someone doesn't remove the internal call to the dbus
406     // object from which we're inheriting.
407     EXPECT_CALL(sdbus_mock_mode,
408                 sd_bus_emit_properties_changed_strv(
409                     IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
410         .WillOnce(Invoke([&](sd_bus* bus, const char* path,
411                              const char* interface, char** names) {
412             EXPECT_STREQ("Manual", names[0]);
413             return 0;
414         }));
415 
416     // Method under test will set the manual mode to true and broadcast this
417     // change on dbus.
418     zone->manual(true);
419     EXPECT_TRUE(zone->getManualMode());
420 }
421 
422 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
423 {
424     // This property is implemented by us as read-only, such that trying to
425     // write to it will have no effect.
426     EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
427 }
428