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.
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()).Times(2).WillRepeatedly(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_VerifyFailSafeIgnoredIfInputHigher)
227 {
228     // If the requested output is higher than the failsafe value, then use the
229     // value provided to outputProc.
230 
231     ZoneMock z;
232 
233     std::vector<std::string> inputs = {"fan0"};
234     ec::pidinfo initial;
235 
236     std::unique_ptr<PIDController> p =
237         FanController::createFanPid(&z, "fan1", inputs, initial);
238     EXPECT_FALSE(p == nullptr);
239 
240     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
241     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
242 
243     int64_t timeout = 0;
244     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
245     // Grab pointer for mocking.
246     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
247 
248     // Converting from double to double for expectation.
249     double percent = 80;
250     double value = percent / 100;
251 
252     EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false));
253     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
254     EXPECT_CALL(*sm1, write(value, false, _));
255 
256     // This is a fan PID, so calling outputProc will try to write this value
257     // to the sensors.
258     p->outputProc(percent);
259 }
260 
261 TEST(FanControllerTest, OutputProc_VerifyRedundantWrites)
262 {
263     /* when a zone indicates that redundant writes are enabled
264      * make sure the fan controller honors this by forcing a sensor write
265      */
266     ZoneMock z;
267 
268     std::vector<std::string> inputs = {"fan0", "fan1"};
269     ec::pidinfo initial;
270 
271     std::unique_ptr<PIDController> p =
272         FanController::createFanPid(&z, "fan1", inputs, initial);
273     EXPECT_FALSE(p == nullptr);
274 
275     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
276 
277     int64_t timeout = 0;
278     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
279     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
280     // Grab pointers for mocking.
281     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
282     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
283 
284     EXPECT_CALL(z, getRedundantWrite())
285         .WillOnce(Return(true))
286         .WillOnce(Return(true));
287     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
288     EXPECT_CALL(*sm1, write(0.5, true, _));
289     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
290     EXPECT_CALL(*sm2, write(0.5, true, _));
291 
292     // This is a fan PID, so calling outputProc will try to write this value
293     // to the sensors.
294     p->outputProc(50.0);
295 }
296 
297 } // namespace
298 } // namespace pid_control
299