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 (this assumes STRICT_FAILSAFE_PWM is not set) 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()).WillOnce(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_VerifyFailSafeWhenInputHigher) 227 { 228 // If STRICT_FAILSAFE_PWM flag is NOT defined and the requested output is 229 // higher than the failsafe value, then use the value provided to outputProc 230 // 231 // If STRICT_FAILSAFE_PWM is defined, we expect the FailSafe PWM to be 232 // capped to the failsafe PWM, and not go higher than that. 233 234 ZoneMock z; 235 236 std::vector<std::string> inputs = {"fan0"}; 237 ec::pidinfo initial; 238 const double failsafePWM = 75.0; 239 240 std::unique_ptr<PIDController> p = 241 FanController::createFanPid(&z, "fan1", inputs, initial); 242 EXPECT_FALSE(p == nullptr); 243 244 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true)); 245 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM)); 246 247 int64_t timeout = 0; 248 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 249 // Grab pointer for mocking. 250 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 251 252 double percent = 80; 253 254 EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false)); 255 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 256 #ifdef STRICT_FAILSAFE_PWM 257 double failsafeValue = failsafePWM / 100; 258 EXPECT_CALL(*sm1, write(failsafeValue, false, _)); 259 #else 260 // Converting from double to double for expectation. 261 double value = percent / 100; 262 EXPECT_CALL(*sm1, write(value, false, _)); 263 #endif 264 265 // This is a fan PID, so calling outputProc will try to write this value 266 // to the sensors. 267 p->outputProc(percent); 268 } 269 270 TEST(FanControllerTest, OutputProc_VerifyRedundantWrites) 271 { 272 /* when a zone indicates that redundant writes are enabled 273 * make sure the fan controller honors this by forcing a sensor write 274 */ 275 ZoneMock z; 276 277 std::vector<std::string> inputs = {"fan0", "fan1"}; 278 ec::pidinfo initial; 279 280 std::unique_ptr<PIDController> p = 281 FanController::createFanPid(&z, "fan1", inputs, initial); 282 EXPECT_FALSE(p == nullptr); 283 284 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false)); 285 286 int64_t timeout = 0; 287 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 288 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 289 // Grab pointers for mocking. 290 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 291 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get()); 292 293 EXPECT_CALL(z, getRedundantWrite()) 294 .WillOnce(Return(true)) 295 .WillOnce(Return(true)); 296 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 297 EXPECT_CALL(*sm1, write(0.5, true, _)); 298 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 299 EXPECT_CALL(*sm2, write(0.5, true, _)); 300 301 // This is a fan PID, so calling outputProc will try to write this value 302 // to the sensors. 303 p->outputProc(50.0); 304 } 305 306 } // namespace 307 } // namespace pid_control 308