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 using ::testing::_;
13 using ::testing::DoubleEq;
14 using ::testing::Invoke;
15 using ::testing::Return;
16 using ::testing::StrEq;
17 
18 TEST(FanControllerTest, BoringFactoryTest)
19 {
20     // Verify the factory will properly build the FanPIDController in the
21     // boring (uninteresting) case.
22     ZoneMock z;
23 
24     std::vector<std::string> inputs = {"fan0"};
25     ec::pidinfo initial;
26 
27     std::unique_ptr<PIDController> p =
28         FanController::CreateFanPid(&z, "fan1", inputs, initial);
29     // Success
30     EXPECT_FALSE(p == nullptr);
31 }
32 
33 TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs)
34 {
35     // A fan controller needs at least one input.
36 
37     ZoneMock z;
38 
39     std::vector<std::string> inputs = {};
40     ec::pidinfo initial;
41 
42     std::unique_ptr<PIDController> p =
43         FanController::CreateFanPid(&z, "fan1", inputs, initial);
44     EXPECT_TRUE(p == nullptr);
45 }
46 
47 TEST(FanControllerTest, InputProc_AllSensorsReturnZero)
48 {
49     // If all your inputs are 0, return 0.
50 
51     ZoneMock z;
52 
53     std::vector<std::string> inputs = {"fan0", "fan1"};
54     ec::pidinfo initial;
55 
56     std::unique_ptr<PIDController> p =
57         FanController::CreateFanPid(&z, "fan1", inputs, initial);
58     EXPECT_FALSE(p == nullptr);
59 
60     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0));
61     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0));
62 
63     EXPECT_EQ(0.0, p->input_proc());
64 }
65 
66 TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored)
67 {
68     // A sensor value returning sub-zero is ignored as an error.
69     ZoneMock z;
70 
71     std::vector<std::string> inputs = {"fan0", "fan1"};
72     ec::pidinfo initial;
73 
74     std::unique_ptr<PIDController> p =
75         FanController::CreateFanPid(&z, "fan1", inputs, initial);
76     EXPECT_FALSE(p == nullptr);
77 
78     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1));
79     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1));
80 
81     EXPECT_EQ(0.0, p->input_proc());
82 }
83 
84 TEST(FanControllerTest, InputProc_ChoosesMinimumValue)
85 {
86     // Verify it selects the minimum value from its inputs.
87 
88     ZoneMock z;
89 
90     std::vector<std::string> inputs = {"fan0", "fan1", "fan2"};
91     ec::pidinfo initial;
92 
93     std::unique_ptr<PIDController> p =
94         FanController::CreateFanPid(&z, "fan1", inputs, initial);
95     EXPECT_FALSE(p == nullptr);
96 
97     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0));
98     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0));
99     EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0));
100 
101     EXPECT_EQ(5.0, p->input_proc());
102 }
103 
104 // The direction is unused presently, but these tests validate the logic.
105 TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection)
106 {
107     // The fan direction defaults to neutral, because we have no data.  Verify
108     // that after this point it appropriately will indicate speeding up or
109     // slowing down based on the RPM values specified.
110 
111     ZoneMock z;
112 
113     std::vector<std::string> inputs = {"fan0", "fan1"};
114     ec::pidinfo initial;
115 
116     std::unique_ptr<PIDController> p =
117         FanController::CreateFanPid(&z, "fan1", inputs, initial);
118     EXPECT_FALSE(p == nullptr);
119     // Grab pointer for mocking.
120     FanController* fp = reinterpret_cast<FanController*>(p.get());
121 
122     // Fanspeed starts are Neutral.
123     EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
124 
125     // getMaxRPMRequest returns a higher value than 0, so the fans should be
126     // marked as speeding up.
127     EXPECT_CALL(z, getMaxRPMRequest()).WillOnce(Return(10.0));
128     EXPECT_EQ(10.0, p->setpt_proc());
129     EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection());
130 
131     // getMaxRPMRequest returns a lower value than 10, so the fans should be
132     // marked as slowing down.
133     EXPECT_CALL(z, getMaxRPMRequest()).WillOnce(Return(5.0));
134     EXPECT_EQ(5.0, p->setpt_proc());
135     EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection());
136 
137     // getMaxRPMRequest returns the same value, so the fans should be marked as
138     // neutral.
139     EXPECT_CALL(z, getMaxRPMRequest()).WillOnce(Return(5.0));
140     EXPECT_EQ(5.0, p->setpt_proc());
141     EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
142 }
143 
144 TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)
145 {
146     // Verify that if failsafe mode is enabled and the input value for the fans
147     // is below the failsafe minimum value, the input is not used and the fans
148     // are driven at failsafe RPM.
149 
150     ZoneMock z;
151 
152     std::vector<std::string> inputs = {"fan0", "fan1"};
153     ec::pidinfo initial;
154 
155     std::unique_ptr<PIDController> p =
156         FanController::CreateFanPid(&z, "fan1", inputs, initial);
157     EXPECT_FALSE(p == nullptr);
158 
159     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
160     EXPECT_CALL(z, getFailSafePercent()).Times(2).WillRepeatedly(Return(75.0));
161 
162     int64_t timeout = 0;
163     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
164     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
165     // Grab pointers for mocking.
166     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
167     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
168 
169     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
170     EXPECT_CALL(*sm1, write(0.75));
171     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
172     EXPECT_CALL(*sm2, write(0.75));
173 
174     // This is a fan PID, so calling output_proc will try to write this value
175     // to the sensors.
176 
177     // Setting 50%, will end up being 75% because the sensors are in failsafe
178     // mode.
179     p->output_proc(50.0);
180 }
181 
182 TEST(FanControllerTest, OutputProc_BehavesAsExpected)
183 {
184     // Verifies that when the system is not in failsafe mode, the input value
185     // to output_proc is used to drive the sensors (fans).
186 
187     ZoneMock z;
188 
189     std::vector<std::string> inputs = {"fan0", "fan1"};
190     ec::pidinfo initial;
191 
192     std::unique_ptr<PIDController> p =
193         FanController::CreateFanPid(&z, "fan1", inputs, initial);
194     EXPECT_FALSE(p == nullptr);
195 
196     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
197 
198     int64_t timeout = 0;
199     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
200     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
201     // Grab pointers for mocking.
202     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
203     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
204 
205     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
206     EXPECT_CALL(*sm1, write(0.5));
207     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
208     EXPECT_CALL(*sm2, write(0.5));
209 
210     // This is a fan PID, so calling output_proc will try to write this value
211     // to the sensors.
212     p->output_proc(50.0);
213 }
214 
215 TEST(FanControllerTest, OutputProc_VerifyFailSafeIgnoredIfInputHigher)
216 {
217     // If the requested output is higher than the failsafe value, then use the
218     // value provided to output_proc.
219 
220     ZoneMock z;
221 
222     std::vector<std::string> inputs = {"fan0"};
223     ec::pidinfo initial;
224 
225     std::unique_ptr<PIDController> p =
226         FanController::CreateFanPid(&z, "fan1", inputs, initial);
227     EXPECT_FALSE(p == nullptr);
228 
229     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
230     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
231 
232     int64_t timeout = 0;
233     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
234     // Grab pointer for mocking.
235     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
236 
237     // Converting from float to double for expectation.
238     float percent = 80;
239     double value = static_cast<double>(percent / 100);
240 
241     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
242     EXPECT_CALL(*sm1, write(value));
243 
244     // This is a fan PID, so calling output_proc will try to write this value
245     // to the sensors.
246     p->output_proc(percent);
247 }
248