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(std::move(bus_mock_passive), std::move(bus_mock_host));
41 
42     bool defer = true;
43     const char* objPath = "/path/";
44     int64_t zone = 1;
45     float minThermalRpm = 1000.0;
46     float 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, minThermalRpm, failSafePercent, m, bus_mock_mode, objPath,
54               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(std::move(bus_mock_passive), std::move(bus_mock_host));
78         mgr = std::move(m);
79 
80         SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface,
81                         properties, &property_index);
82 
83         zone = std::make_unique<PIDZone>(zoneId, minThermalRpm, failSafePercent,
84                                          mgr, bus_mock_mode, objPath, defer);
85     }
86 
87     // unused
88     int property_index;
89     std::vector<std::string> properties;
90 
91     sdbusplus::SdBusMock sdbus_mock_passive;
92     sdbusplus::SdBusMock sdbus_mock_host;
93     sdbusplus::SdBusMock sdbus_mock_mode;
94     int64_t zoneId = 1;
95     float minThermalRpm = 1000.0;
96     float failSafePercent = 0.75;
97     bool defer = true;
98     const char* objPath = "/path/";
99     SensorManager mgr;
100 
101     std::unique_ptr<PIDZone> zone;
102 };
103 
104 TEST_F(PidZoneTest, GetZoneId_ReturnsExpected)
105 {
106     // Verifies the zoneId returned is what we expect.
107 
108     EXPECT_EQ(zoneId, zone->getZoneId());
109 }
110 
111 TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected)
112 {
113     // Verifies that the zone starts in manual mode.  Verifies that one can set
114     // the mode.
115     EXPECT_FALSE(zone->getManualMode());
116 
117     zone->setManualMode(true);
118     EXPECT_TRUE(zone->getManualMode());
119 }
120 
121 TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected)
122 {
123     // Tests addRPMSetPoint, clearRPMSetPoints, determineMaxRPMRequest
124     // and getMinThermalRpmSetPt.
125 
126     // At least one value must be above the minimum thermal setpoint used in
127     // the constructor otherwise it'll choose that value
128     std::vector<float> values = {100, 200, 300, 400, 500, 5000};
129     for (auto v : values)
130     {
131         zone->addRPMSetPoint(v);
132     }
133 
134     // This will pull the maximum RPM setpoint request.
135     zone->determineMaxRPMRequest();
136     EXPECT_EQ(5000, zone->getMaxRPMRequest());
137 
138     // Clear the values, so it'll choose the minimum thermal setpoint.
139     zone->clearRPMSetPoints();
140 
141     // This will go through the RPM set point values and grab the maximum.
142     zone->determineMaxRPMRequest();
143     EXPECT_EQ(zone->getMinThermalRpmSetPt(), zone->getMaxRPMRequest());
144 }
145 
146 TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected)
147 {
148     // Tests adding several RPM setpoints, however, they're all lower than the
149     // configured minimal thermal set-point RPM value.
150 
151     std::vector<float> values = {100, 200, 300, 400, 500};
152     for (auto v : values)
153     {
154         zone->addRPMSetPoint(v);
155     }
156 
157     // This will pull the maximum RPM setpoint request.
158     zone->determineMaxRPMRequest();
159 
160     // Verifies the value returned in the minimal thermal rpm set point.
161     EXPECT_EQ(zone->getMinThermalRpmSetPt(), zone->getMaxRPMRequest());
162 }
163 
164 TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected)
165 {
166     // Verify the value used to create the object is stored.
167     EXPECT_EQ(failSafePercent, zone->getFailSafePercent());
168 }
169 
170 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors)
171 {
172     // This test will add a couple thermal inputs, and verify that the zone
173     // initializes into failsafe mode, and will read each sensor.
174 
175     std::string name1 = "temp1";
176     int64_t timeout = 1;
177 
178     std::unique_ptr<Sensor> sensor1 =
179         std::make_unique<SensorMock>(name1, timeout);
180     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
181 
182     std::string name2 = "temp2";
183     std::unique_ptr<Sensor> sensor2 =
184         std::make_unique<SensorMock>(name2, timeout);
185     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
186 
187     std::string type = "unchecked";
188     mgr.addSensor(type, name1, std::move(sensor1));
189     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
190     mgr.addSensor(type, name2, std::move(sensor2));
191     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
192 
193     // Now that the sensors exist, add them to the zone.
194     zone->addThermalInput(name1);
195     zone->addThermalInput(name2);
196 
197     // Initialize Zone
198     zone->initializeCache();
199 
200     // Verify now in failsafe mode.
201     EXPECT_TRUE(zone->getFailSafeMode());
202 
203     ReadReturn r1;
204     r1.value = 10.0;
205     r1.updated = std::chrono::high_resolution_clock::now();
206     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
207 
208     ReadReturn r2;
209     r2.value = 11.0;
210     r2.updated = std::chrono::high_resolution_clock::now();
211     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
212 
213     // Read the sensors, this will put the values into the cache.
214     zone->updateSensors();
215 
216     // We should no longer be in failsafe mode.
217     EXPECT_FALSE(zone->getFailSafeMode());
218 
219     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
220     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
221 }
222 
223 TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached)
224 {
225     // This will add a couple fan inputs, and verify the values are cached.
226 
227     std::string name1 = "fan1";
228     int64_t timeout = 2;
229 
230     std::unique_ptr<Sensor> sensor1 =
231         std::make_unique<SensorMock>(name1, timeout);
232     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
233 
234     std::string name2 = "fan2";
235     std::unique_ptr<Sensor> sensor2 =
236         std::make_unique<SensorMock>(name2, timeout);
237     SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
238 
239     std::string type = "unchecked";
240     mgr.addSensor(type, name1, std::move(sensor1));
241     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
242     mgr.addSensor(type, name2, std::move(sensor2));
243     EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2);
244 
245     // Now that the sensors exist, add them to the zone.
246     zone->addFanInput(name1);
247     zone->addFanInput(name2);
248 
249     // Initialize Zone
250     zone->initializeCache();
251 
252     ReadReturn r1;
253     r1.value = 10.0;
254     r1.updated = std::chrono::high_resolution_clock::now();
255     EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
256 
257     ReadReturn r2;
258     r2.value = 11.0;
259     r2.updated = std::chrono::high_resolution_clock::now();
260     EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
261 
262     // Method under test will read through each fan sensor for the zone and
263     // cache the values.
264     zone->updateFanTelemetry();
265 
266     EXPECT_EQ(r1.value, zone->getCachedValue(name1));
267     EXPECT_EQ(r2.value, zone->getCachedValue(name2));
268 }
269 
270 TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
271 {
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 {
332     // One can grab a sensor from the manager through the zone.
333 
334     int64_t timeout = 1;
335 
336     std::string name1 = "temp1";
337     std::unique_ptr<Sensor> sensor1 =
338         std::make_unique<SensorMock>(name1, timeout);
339     SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
340 
341     std::string type = "unchecked";
342     mgr.addSensor(type, name1, std::move(sensor1));
343     EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1);
344 
345     zone->addThermalInput(name1);
346 
347     // Verify method under test returns the pointer we expect.
348     EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1));
349 }
350 
351 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
352 {
353     // Tests adding a thermal PID controller to the zone, and verifies it's
354     // touched during processing.
355 
356     std::unique_ptr<PIDController> tpid =
357         std::make_unique<ControllerMock>("thermal1", zone.get());
358     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
359 
360     // Access the internal pid configuration to clear it out (unrelated to the
361     // test).
362     ec::pid_info_t* info = tpid->get_pid_info();
363     std::memset(info, 0x00, sizeof(ec::pid_info_t));
364 
365     zone->addThermalPID(std::move(tpid));
366 
367     EXPECT_CALL(*tmock, setpt_proc()).WillOnce(Return(10.0));
368     EXPECT_CALL(*tmock, input_proc()).WillOnce(Return(11.0));
369     EXPECT_CALL(*tmock, output_proc(_));
370 
371     // Method under test will, for each thermal PID, call setpt, input, and
372     // output.
373     zone->process_thermals();
374 }
375 
376 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
377 {
378     // Tests adding a fan PID controller to the zone, and verifies it's
379     // touched during processing.
380 
381     std::unique_ptr<PIDController> tpid =
382         std::make_unique<ControllerMock>("fan1", zone.get());
383     ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
384 
385     // Access the internal pid configuration to clear it out (unrelated to the
386     // test).
387     ec::pid_info_t* info = tpid->get_pid_info();
388     std::memset(info, 0x00, sizeof(ec::pid_info_t));
389 
390     zone->addFanPID(std::move(tpid));
391 
392     EXPECT_CALL(*tmock, setpt_proc()).WillOnce(Return(10.0));
393     EXPECT_CALL(*tmock, input_proc()).WillOnce(Return(11.0));
394     EXPECT_CALL(*tmock, output_proc(_));
395 
396     // Method under test will, for each fan PID, call setpt, input, and output.
397     zone->process_fans();
398 }
399 
400 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
401 {
402     // The manual(bool) method is inherited from the dbus mode interface.
403 
404     // Verifies that someone doesn't remove the internal call to the dbus
405     // object from which we're inheriting.
406     EXPECT_CALL(sdbus_mock_mode,
407                 sd_bus_emit_properties_changed_strv(
408                     IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
409         .WillOnce(Invoke([&](sd_bus* bus, const char* path,
410                              const char* interface, char** names) {
411             EXPECT_STREQ("Manual", names[0]);
412             return 0;
413         }));
414 
415     // Method under test will set the manual mode to true and broadcast this
416     // change on dbus.
417     zone->manual(true);
418     EXPECT_TRUE(zone->getManualMode());
419 }
420 
421 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
422 {
423     // This property is implemented by us as read-only, such that trying to
424     // write to it will have no effect.
425     EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
426 }
427