1 #include "pid/zone.hpp"
2 
3 #include <chrono>
4 #include <cstring>
5 #include <gmock/gmock.h>
6 #include <gtest/gtest.h>
7 #include <sdbusplus/test/sdbus_mock.hpp>
8 #include <vector>
9 
10 #include "pid/ec/pid.hpp"
11 #include "sensors/manager.hpp"
12 #include "test/controller_mock.hpp"
13 #include "test/sensor_mock.hpp"
14 #include "test/helpers.hpp"
15 
16 using ::testing::IsNull;
17 using ::testing::Return;
18 using ::testing::StrEq;
19 using ::testing::_;
20 
21 static std::string modeInterface = "xyz.openbmc_project.Control.Mode";
22 
23 namespace {
24 
25 TEST(PidZoneConstructorTest, BoringConstructorTest) {
26     // Build a PID Zone.
27 
28     sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode;
29     auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
30     auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
31     auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
32 
33     EXPECT_CALL(sdbus_mock_host,
34                 sd_bus_add_object_manager(
35                     IsNull(),
36                     _,
37                     StrEq("/xyz/openbmc_project/extsensors")))
38         .WillOnce(Return(0));
39 
40     SensorManager m(std::move(bus_mock_passive),
41                      std::move(bus_mock_host));
42 
43     bool defer = true;
44     const char *objPath = "/path/";
45     int64_t zone = 1;
46     float minThermalRpm = 1000.0;
47     float failSafePercent = 0.75;
48 
49     int i;
50     std::vector<std::string> properties;
51     SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface,
52                     properties, &i);
53 
54     PIDZone p(zone, minThermalRpm, failSafePercent, m, bus_mock_mode, objPath,
55               defer);
56     // Success.
57 }
58 
59 }
60 
61 class PidZoneTest : public ::testing::Test {
62     protected:
63         PidZoneTest()
64         : property_index(),
65           properties(),
66           sdbus_mock_passive(),
67           sdbus_mock_host(),
68           sdbus_mock_mode()
69         {
70             EXPECT_CALL(sdbus_mock_host,
71                 sd_bus_add_object_manager(
72                     IsNull(),
73                     _,
74                     StrEq("/xyz/openbmc_project/extsensors")))
75                 .WillOnce(Return(0));
76 
77             auto bus_mock_passive =
78                 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(std::move(bus_mock_passive),
84                             std::move(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<PIDZone>(zoneId, minThermalRpm,
91                                              failSafePercent, mgr,
92                                              bus_mock_mode, objPath, defer);
93         }
94 
95         // unused
96         int 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         float minThermalRpm = 1000.0;
104         float failSafePercent = 0.75;
105         bool defer = true;
106         const char *objPath = "/path/";
107         SensorManager mgr;
108 
109         std::unique_ptr<PIDZone> zone;
110 };
111 
112 TEST_F(PidZoneTest, GetZoneId_ReturnsExpected) {
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     // Verifies that the zone starts in manual mode.  Verifies that one can set
120     // the mode.
121     EXPECT_FALSE(zone->getManualMode());
122 
123     zone->setManualMode(true);
124     EXPECT_TRUE(zone->getManualMode());
125 }
126 
127 TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected) {
128     // Tests addRPMSetPoint, clearRPMSetPoints, determineMaxRPMRequest
129     // and getMinThermalRpmSetPt.
130 
131     // At least one value must be above the minimum thermal setpoint used in
132     // the constructor otherwise it'll choose that value
133     std::vector<float> values = {100, 200, 300, 400, 500, 5000};
134     for (auto v : values)
135     {
136         zone->addRPMSetPoint(v);
137     }
138 
139     // This will pull the maximum RPM setpoint request.
140     zone->determineMaxRPMRequest();
141     EXPECT_EQ(5000, zone->getMaxRPMRequest());
142 
143     // Clear the values, so it'll choose the minimum thermal setpoint.
144     zone->clearRPMSetPoints();
145 
146     // This will go through the RPM set point values and grab the maximum.
147     zone->determineMaxRPMRequest();
148     EXPECT_EQ(zone->getMinThermalRpmSetPt(), zone->getMaxRPMRequest());
149 }
150 
151 TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected) {
152     // Tests adding several RPM setpoints, however, they're all lower than the
153     // configured minimal thermal set-point RPM value.
154 
155     std::vector<float> values = {100, 200, 300, 400, 500};
156     for (auto v : values)
157     {
158         zone->addRPMSetPoint(v);
159     }
160 
161     // This will pull the maximum RPM setpoint request.
162     zone->determineMaxRPMRequest();
163 
164     // Verifies the value returned in the minimal thermal rpm set point.
165     EXPECT_EQ(zone->getMinThermalRpmSetPt(), zone->getMaxRPMRequest());
166 }
167 
168 TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected) {
169     // Verify the value used to create the object is stored.
170     EXPECT_EQ(failSafePercent, zone->getFailSafePercent());
171 }
172 
173 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors) {
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     // 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     // On the second updateSensors call, the updated timestamp will be beyond
273     // the timeout limit.
274 
275     int64_t timeout = 1;
276 
277     std::string name1 = "temp1";
278     std::unique_ptr<Sensor> sensor1 =
279         std::make_unique<SensorMock>(name1, timeout);
280     SensorMock *sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
281 
282     std::string name2 = "temp2";
283     std::unique_ptr<Sensor> sensor2 =
284         std::make_unique<SensorMock>(name2, timeout);
285     SensorMock *sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
286 
287     std::string type = "unchecked";
288     mgr.addSensor(type, name1, std::move(sensor1));
289     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
290     mgr.addSensor(type, name2, std::move(sensor2));
291     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
292 
293     zone->addThermalInput(name1);
294     zone->addThermalInput(name2);
295 
296     // Initialize Zone
297     zone->initializeCache();
298 
299     // Verify now in failsafe mode.
300     EXPECT_TRUE(zone->getFailSafeMode());
301 
302     ReadReturn r1;
303     r1.value = 10.0;
304     r1.updated = std::chrono::high_resolution_clock::now();
305     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
306 
307     ReadReturn r2;
308     r2.value = 11.0;
309     r2.updated = std::chrono::high_resolution_clock::now();
310     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
311 
312     zone->updateSensors();
313     EXPECT_FALSE(zone->getFailSafeMode());
314 
315     // Ok, so we're not in failsafe mode, so let's set updated to the past.
316     // sensor1 will have an updated field older than its timeout value, but
317     // sensor2 will be fine. :D
318     r1.updated -= std::chrono::seconds(3);
319     r2.updated = std::chrono::high_resolution_clock::now();
320 
321     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
322     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
323 
324     // Method under test will read each sensor.  One sensor's value is older
325     // than the timeout for that sensor and this triggers failsafe mode.
326     zone->updateSensors();
327     EXPECT_TRUE(zone->getFailSafeMode());
328 }
329 
330 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected) {
331     // One can grab a sensor from the manager through the zone.
332 
333     int64_t timeout = 1;
334 
335     std::string name1 = "temp1";
336     std::unique_ptr<Sensor> sensor1 =
337         std::make_unique<SensorMock>(name1, timeout);
338     SensorMock *sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
339 
340     std::string type = "unchecked";
341     mgr.addSensor(type, name1, std::move(sensor1));
342     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
343 
344     zone->addThermalInput(name1);
345 
346     // Verify method under test returns the pointer we expect.
347     EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
348 }
349 
350 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed) {
351     // Tests adding a thermal PID controller to the zone, and verifies it's
352     // touched during processing.
353 
354     std::unique_ptr<PIDController> tpid =
355         std::make_unique<ControllerMock>("thermal1", zone.get());
356     ControllerMock *tmock = reinterpret_cast<ControllerMock*>(tpid.get());
357 
358     // Access the internal pid configuration to clear it out (unrelated to the
359     // test).
360     ec::pid_info_t* info = tpid->get_pid_info();
361     std::memset(info, 0x00, sizeof(ec::pid_info_t));
362 
363     zone->addThermalPID(std::move(tpid));
364 
365     EXPECT_CALL(*tmock, setpt_proc()).WillOnce(Return(10.0));
366     EXPECT_CALL(*tmock, input_proc()).WillOnce(Return(11.0));
367     EXPECT_CALL(*tmock, output_proc(_));
368 
369     // Method under test will, for each thermal PID, call setpt, input, and
370     // output.
371     zone->process_thermals();
372 }
373 
374 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed) {
375     // Tests adding a fan PID controller to the zone, and verifies it's
376     // touched during processing.
377 
378     std::unique_ptr<PIDController> tpid =
379         std::make_unique<ControllerMock>("fan1", zone.get());
380     ControllerMock *tmock = reinterpret_cast<ControllerMock*>(tpid.get());
381 
382     // Access the internal pid configuration to clear it out (unrelated to the
383     // test).
384     ec::pid_info_t* info = tpid->get_pid_info();
385     std::memset(info, 0x00, sizeof(ec::pid_info_t));
386 
387     zone->addFanPID(std::move(tpid));
388 
389     EXPECT_CALL(*tmock, setpt_proc()).WillOnce(Return(10.0));
390     EXPECT_CALL(*tmock, input_proc()).WillOnce(Return(11.0));
391     EXPECT_CALL(*tmock, output_proc(_));
392 
393     // Method under test will, for each fan PID, call setpt, input, and output.
394     zone->process_fans();
395 }
396 
397 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected) {
398     // The manual(bool) method is inherited from the dbus mode interface.
399 
400     // Verifies that someone doesn't remove the internal call to the dbus
401     // object from which we're inheriting.
402     EXPECT_CALL(sdbus_mock_mode,
403                 sd_bus_emit_properties_changed_strv(IsNull(), StrEq(objPath),
404                                                     StrEq(modeInterface),
405                                                     NotNull()))
406         .WillOnce(Invoke([&](sd_bus *bus, const char *path,
407                              const char *interface, char **names) {
408             EXPECT_STREQ("Manual", names[0]);
409             return 0;
410         }));
411 
412     // Method under test will set the manual mode to true and broadcast this
413     // change on dbus.
414     zone->manual(true);
415     EXPECT_TRUE(zone->getManualMode());
416 }
417 
418 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected) {
419     // This property is implemented by us as read-only, such that trying to
420     // write to it will have no effect.
421     EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
422 }
423