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