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