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