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