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