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, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
175     EXPECT_CALL(*sm1, write(0.75));
176     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
177     EXPECT_CALL(*sm2, write(0.75));
178 
179     // This is a fan PID, so calling outputProc will try to write this value
180     // to the sensors.
181 
182     // Setting 50%, will end up being 75% because the sensors are in failsafe
183     // mode.
184     p->outputProc(50.0);
185 }
186 
187 TEST(FanControllerTest, OutputProc_BehavesAsExpected)
188 {
189     // Verifies that when the system is not in failsafe mode, the input value
190     // to outputProc is used to drive the sensors (fans).
191 
192     ZoneMock z;
193 
194     std::vector<std::string> inputs = {"fan0", "fan1"};
195     ec::pidinfo initial;
196 
197     std::unique_ptr<PIDController> p =
198         FanController::createFanPid(&z, "fan1", inputs, initial);
199     EXPECT_FALSE(p == nullptr);
200 
201     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
202 
203     int64_t timeout = 0;
204     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
205     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
206     // Grab pointers for mocking.
207     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
208     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
209 
210     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
211     EXPECT_CALL(*sm1, write(0.5));
212     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
213     EXPECT_CALL(*sm2, write(0.5));
214 
215     // This is a fan PID, so calling outputProc will try to write this value
216     // to the sensors.
217     p->outputProc(50.0);
218 }
219 
220 TEST(FanControllerTest, OutputProc_VerifyFailSafeIgnoredIfInputHigher)
221 {
222     // If the requested output is higher than the failsafe value, then use the
223     // value provided to outputProc.
224 
225     ZoneMock z;
226 
227     std::vector<std::string> inputs = {"fan0"};
228     ec::pidinfo initial;
229 
230     std::unique_ptr<PIDController> p =
231         FanController::createFanPid(&z, "fan1", inputs, initial);
232     EXPECT_FALSE(p == nullptr);
233 
234     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
235     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
236 
237     int64_t timeout = 0;
238     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
239     // Grab pointer for mocking.
240     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
241 
242     // Converting from double to double for expectation.
243     double percent = 80;
244     double value = percent / 100;
245 
246     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
247     EXPECT_CALL(*sm1, write(value));
248 
249     // This is a fan PID, so calling outputProc will try to write this value
250     // to the sensors.
251     p->outputProc(percent);
252 }
253 
254 } // namespace
255 } // namespace pid_control
256