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