xref: /openbmc/phosphor-pid-control/test/pid_fancontroller_unittest.cpp (revision 765a6d8024061dd9f7a881bac870120886bee9db)
1 #include "config.h"
2 
3 #include "pid/ec/pid.hpp"
4 #include "pid/fan.hpp"
5 #include "pid/fancontroller.hpp"
6 #include "pid/pidcontroller.hpp"
7 #include "sensors/sensor.hpp"
8 #include "test/sensor_mock.hpp"
9 #include "test/zone_mock.hpp"
10 
11 #include <cstdint>
12 #include <memory>
13 #include <string>
14 #include <vector>
15 
16 #include <gmock/gmock.h>
17 #include <gtest/gtest.h>
18 
19 namespace pid_control
20 {
21 namespace
22 {
23 
24 using ::testing::_;
25 using ::testing::Return;
26 using ::testing::StrEq;
27 
TEST(FanControllerTest,BoringFactoryTest)28 TEST(FanControllerTest, BoringFactoryTest)
29 {
30     // Verify the factory will properly build the FanPIDController in the
31     // boring (uninteresting) case.
32     ZoneMock z;
33 
34     std::vector<std::string> inputs = {"fan0"};
35     ec::pidinfo initial;
36 
37     std::unique_ptr<PIDController> p =
38         FanController::createFanPid(&z, "fan1", inputs, initial);
39     // Success
40     EXPECT_FALSE(p == nullptr);
41 }
42 
TEST(FanControllerTest,VerifyFactoryFailsWithZeroInputs)43 TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs)
44 {
45     // A fan controller needs at least one input.
46 
47     ZoneMock z;
48 
49     std::vector<std::string> inputs = {};
50     ec::pidinfo initial;
51 
52     std::unique_ptr<PIDController> p =
53         FanController::createFanPid(&z, "fan1", inputs, initial);
54     EXPECT_TRUE(p == nullptr);
55 }
56 
TEST(FanControllerTest,InputProc_AllSensorsReturnZero)57 TEST(FanControllerTest, InputProc_AllSensorsReturnZero)
58 {
59     // If all your inputs are 0, return 0.
60 
61     ZoneMock z;
62 
63     std::vector<std::string> inputs = {"fan0", "fan1"};
64     ec::pidinfo initial;
65 
66     std::unique_ptr<PIDController> p =
67         FanController::createFanPid(&z, "fan1", inputs, initial);
68     EXPECT_FALSE(p == nullptr);
69 
70     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0));
71     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0));
72 
73     EXPECT_EQ(0.0, p->inputProc());
74 }
75 
TEST(FanControllerTest,InputProc_IfSensorNegativeIsIgnored)76 TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored)
77 {
78     // A sensor value returning sub-zero is ignored as an error.
79     ZoneMock z;
80 
81     std::vector<std::string> inputs = {"fan0", "fan1"};
82     ec::pidinfo initial;
83 
84     std::unique_ptr<PIDController> p =
85         FanController::createFanPid(&z, "fan1", inputs, initial);
86     EXPECT_FALSE(p == nullptr);
87 
88     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1));
89     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1));
90 
91     EXPECT_EQ(0.0, p->inputProc());
92 }
93 
TEST(FanControllerTest,InputProc_ChoosesMinimumValue)94 TEST(FanControllerTest, InputProc_ChoosesMinimumValue)
95 {
96     // Verify it selects the minimum value from its inputs.
97 
98     ZoneMock z;
99 
100     std::vector<std::string> inputs = {"fan0", "fan1", "fan2"};
101     ec::pidinfo initial;
102 
103     std::unique_ptr<PIDController> p =
104         FanController::createFanPid(&z, "fan1", inputs, initial);
105     EXPECT_FALSE(p == nullptr);
106 
107     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0));
108     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0));
109     EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0));
110 
111     EXPECT_EQ(5.0, p->inputProc());
112 }
113 
114 // The direction is unused presently, but these tests validate the logic.
TEST(FanControllerTest,SetPtProc_SpeedChanges_VerifyDirection)115 TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection)
116 {
117     // The fan direction defaults to neutral, because we have no data.  Verify
118     // that after this point it appropriately will indicate speeding up or
119     // slowing down based on the RPM values specified.
120 
121     ZoneMock z;
122 
123     std::vector<std::string> inputs = {"fan0", "fan1"};
124     ec::pidinfo initial;
125 
126     std::unique_ptr<PIDController> p =
127         FanController::createFanPid(&z, "fan1", inputs, initial);
128     EXPECT_FALSE(p == nullptr);
129     // Grab pointer for mocking.
130     FanController* fp = reinterpret_cast<FanController*>(p.get());
131 
132     // Fanspeed starts are Neutral.
133     EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
134 
135     // getMaxSetPointRequest returns a higher value than 0, so the fans should
136     // be marked as speeding up.
137     EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(10.0));
138     EXPECT_EQ(10.0, p->setptProc());
139     EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection());
140 
141     // getMaxSetPointRequest returns a lower value than 10, so the fans should
142     // be marked as slowing down.
143     EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
144     EXPECT_EQ(5.0, p->setptProc());
145     EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection());
146 
147     // getMaxSetPointRequest returns the same value, so the fans should be
148     // marked as neutral.
149     EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
150     EXPECT_EQ(5.0, p->setptProc());
151     EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
152 }
153 
TEST(FanControllerTest,OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)154 TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)
155 {
156     // Verify that if failsafe mode is enabled and the input value for the fans
157     // is below the failsafe minimum value, the input is not used and the fans
158     // are driven at failsafe RPM (this assumes STRICT_FAILSAFE_PWM is not set)
159 
160     ZoneMock z;
161 
162     std::vector<std::string> inputs = {"fan0", "fan1"};
163     ec::pidinfo initial;
164 
165     std::unique_ptr<PIDController> p =
166         FanController::createFanPid(&z, "fan1", inputs, initial);
167     EXPECT_FALSE(p == nullptr);
168 
169     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
170     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
171 
172     int64_t timeout = 0;
173     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
174     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
175     // Grab pointers for mocking.
176     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
177     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
178 
179     EXPECT_CALL(z, getRedundantWrite())
180         .WillOnce(Return(false))
181         .WillOnce(Return(false));
182     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
183     EXPECT_CALL(*sm1, write(0.75, false, _));
184     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
185     EXPECT_CALL(*sm2, write(0.75, false, _));
186 
187     // This is a fan PID, so calling outputProc will try to write this value
188     // to the sensors.
189 
190     // Setting 50%, will end up being 75% because the sensors are in failsafe
191     // mode.
192     p->outputProc(50.0);
193 }
194 
TEST(FanControllerTest,OutputProc_BehavesAsExpected)195 TEST(FanControllerTest, OutputProc_BehavesAsExpected)
196 {
197     // Verifies that when the system is not in failsafe mode, the input value
198     // to outputProc is used to drive the sensors (fans).
199 
200     ZoneMock z;
201 
202     std::vector<std::string> inputs = {"fan0", "fan1"};
203     ec::pidinfo initial;
204 
205     std::unique_ptr<PIDController> p =
206         FanController::createFanPid(&z, "fan1", inputs, initial);
207     EXPECT_FALSE(p == nullptr);
208 
209     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
210 
211     int64_t timeout = 0;
212     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
213     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
214     // Grab pointers for mocking.
215     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
216     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
217 
218     EXPECT_CALL(z, getRedundantWrite())
219         .WillOnce(Return(false))
220         .WillOnce(Return(false));
221     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
222     EXPECT_CALL(*sm1, write(0.5, false, _));
223     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
224     EXPECT_CALL(*sm2, write(0.5, false, _));
225 
226     // This is a fan PID, so calling outputProc will try to write this value
227     // to the sensors.
228     p->outputProc(50.0);
229 }
230 
TEST(FanControllerTest,OutputProc_VerifyFailSafeWhenInputHigher)231 TEST(FanControllerTest, OutputProc_VerifyFailSafeWhenInputHigher)
232 {
233     // If STRICT_FAILSAFE_PWM flag is NOT defined and the requested output is
234     // higher than the failsafe value, then use the value provided to outputProc
235     //
236     // If STRICT_FAILSAFE_PWM is defined, we expect the FailSafe PWM to be
237     // capped to the failsafe PWM, and not go higher than that.
238 
239     ZoneMock z;
240 
241     std::vector<std::string> inputs = {"fan0"};
242     ec::pidinfo initial;
243     const double failsafePWM = 75.0;
244 
245     std::unique_ptr<PIDController> p =
246         FanController::createFanPid(&z, "fan1", inputs, initial);
247     EXPECT_FALSE(p == nullptr);
248 
249     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
250     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM));
251 
252     int64_t timeout = 0;
253     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
254     // Grab pointer for mocking.
255     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
256 
257     double percent = 80;
258 
259     EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false));
260     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
261 #ifdef STRICT_FAILSAFE_PWM
262     double failsafeValue = failsafePWM / 100;
263     EXPECT_CALL(*sm1, write(failsafeValue, false, _));
264 #else
265     // Converting from double to double for expectation.
266     double value = percent / 100;
267     EXPECT_CALL(*sm1, write(value, false, _));
268 #endif
269 
270     // This is a fan PID, so calling outputProc will try to write this value
271     // to the sensors.
272     p->outputProc(percent);
273 }
274 
TEST(FanControllerTest,OutputProc_VerifyRedundantWrites)275 TEST(FanControllerTest, OutputProc_VerifyRedundantWrites)
276 {
277     /* when a zone indicates that redundant writes are enabled
278      * make sure the fan controller honors this by forcing a sensor write
279      */
280     ZoneMock z;
281 
282     std::vector<std::string> inputs = {"fan0", "fan1"};
283     ec::pidinfo initial;
284 
285     std::unique_ptr<PIDController> p =
286         FanController::createFanPid(&z, "fan1", inputs, initial);
287     EXPECT_FALSE(p == nullptr);
288 
289     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
290 
291     int64_t timeout = 0;
292     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
293     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
294     // Grab pointers for mocking.
295     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
296     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
297 
298     EXPECT_CALL(z, getRedundantWrite())
299         .WillOnce(Return(true))
300         .WillOnce(Return(true));
301     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
302     EXPECT_CALL(*sm1, write(0.5, true, _));
303     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
304     EXPECT_CALL(*sm2, write(0.5, true, _));
305 
306     // This is a fan PID, so calling outputProc will try to write this value
307     // to the sensors.
308     p->outputProc(50.0);
309 }
310 
311 } // namespace
312 } // namespace pid_control
313