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