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