1 #include "config.h" 2 3 #include "pid/ec/logging.hpp" 4 #include "pid/ec/pid.hpp" 5 #include "pid/fancontroller.hpp" 6 #include "test/sensor_mock.hpp" 7 #include "test/zone_mock.hpp" 8 9 #include <string> 10 #include <vector> 11 12 #include <gmock/gmock.h> 13 #include <gtest/gtest.h> 14 15 namespace pid_control 16 { 17 namespace 18 { 19 20 using ::testing::_; 21 using ::testing::DoubleEq; 22 using ::testing::Invoke; 23 using ::testing::Return; 24 using ::testing::StrEq; 25 26 TEST(FanControllerTest, BoringFactoryTest) 27 { 28 // Verify the factory will properly build the FanPIDController in the 29 // boring (uninteresting) case. 30 ZoneMock z; 31 32 std::vector<std::string> inputs = {"fan0"}; 33 ec::pidinfo initial; 34 35 std::unique_ptr<PIDController> p = 36 FanController::createFanPid(&z, "fan1", inputs, initial); 37 // Success 38 EXPECT_FALSE(p == nullptr); 39 } 40 41 TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs) 42 { 43 // A fan controller needs at least one input. 44 45 ZoneMock z; 46 47 std::vector<std::string> inputs = {}; 48 ec::pidinfo initial; 49 50 std::unique_ptr<PIDController> p = 51 FanController::createFanPid(&z, "fan1", inputs, initial); 52 EXPECT_TRUE(p == nullptr); 53 } 54 55 TEST(FanControllerTest, InputProc_AllSensorsReturnZero) 56 { 57 // If all your inputs are 0, return 0. 58 59 ZoneMock z; 60 61 std::vector<std::string> inputs = {"fan0", "fan1"}; 62 ec::pidinfo initial; 63 64 std::unique_ptr<PIDController> p = 65 FanController::createFanPid(&z, "fan1", inputs, initial); 66 EXPECT_FALSE(p == nullptr); 67 68 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0)); 69 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0)); 70 71 EXPECT_EQ(0.0, p->inputProc()); 72 } 73 74 TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored) 75 { 76 // A sensor value returning sub-zero is ignored as an error. 77 ZoneMock z; 78 79 std::vector<std::string> inputs = {"fan0", "fan1"}; 80 ec::pidinfo initial; 81 82 std::unique_ptr<PIDController> p = 83 FanController::createFanPid(&z, "fan1", inputs, initial); 84 EXPECT_FALSE(p == nullptr); 85 86 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1)); 87 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1)); 88 89 EXPECT_EQ(0.0, p->inputProc()); 90 } 91 92 TEST(FanControllerTest, InputProc_ChoosesMinimumValue) 93 { 94 // Verify it selects the minimum value from its inputs. 95 96 ZoneMock z; 97 98 std::vector<std::string> inputs = {"fan0", "fan1", "fan2"}; 99 ec::pidinfo initial; 100 101 std::unique_ptr<PIDController> p = 102 FanController::createFanPid(&z, "fan1", inputs, initial); 103 EXPECT_FALSE(p == nullptr); 104 105 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0)); 106 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0)); 107 EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0)); 108 109 EXPECT_EQ(5.0, p->inputProc()); 110 } 111 112 // The direction is unused presently, but these tests validate the logic. 113 TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection) 114 { 115 // The fan direction defaults to neutral, because we have no data. Verify 116 // that after this point it appropriately will indicate speeding up or 117 // slowing down based on the RPM values specified. 118 119 ZoneMock z; 120 121 std::vector<std::string> inputs = {"fan0", "fan1"}; 122 ec::pidinfo initial; 123 124 std::unique_ptr<PIDController> p = 125 FanController::createFanPid(&z, "fan1", inputs, initial); 126 EXPECT_FALSE(p == nullptr); 127 // Grab pointer for mocking. 128 FanController* fp = reinterpret_cast<FanController*>(p.get()); 129 130 // Fanspeed starts are Neutral. 131 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection()); 132 133 // getMaxSetPointRequest returns a higher value than 0, so the fans should 134 // be marked as speeding up. 135 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(10.0)); 136 EXPECT_EQ(10.0, p->setptProc()); 137 EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection()); 138 139 // getMaxSetPointRequest returns a lower value than 10, so the fans should 140 // be marked as slowing down. 141 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0)); 142 EXPECT_EQ(5.0, p->setptProc()); 143 EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection()); 144 145 // getMaxSetPointRequest returns the same value, so the fans should be 146 // marked as neutral. 147 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0)); 148 EXPECT_EQ(5.0, p->setptProc()); 149 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection()); 150 } 151 152 TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored) 153 { 154 // Verify that if failsafe mode is enabled and the input value for the fans 155 // is below the failsafe minimum value, the input is not used and the fans 156 // are driven at failsafe RPM (this assumes STRICT_FAILSAFE_PWM is not set) 157 158 ZoneMock z; 159 160 std::vector<std::string> inputs = {"fan0", "fan1"}; 161 ec::pidinfo initial; 162 163 std::unique_ptr<PIDController> p = 164 FanController::createFanPid(&z, "fan1", inputs, initial); 165 EXPECT_FALSE(p == nullptr); 166 167 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true)); 168 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0)); 169 170 int64_t timeout = 0; 171 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 172 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 173 // Grab pointers for mocking. 174 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 175 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get()); 176 177 EXPECT_CALL(z, getRedundantWrite()) 178 .WillOnce(Return(false)) 179 .WillOnce(Return(false)); 180 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 181 EXPECT_CALL(*sm1, write(0.75, false, _)); 182 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 183 EXPECT_CALL(*sm2, write(0.75, false, _)); 184 185 // This is a fan PID, so calling outputProc will try to write this value 186 // to the sensors. 187 188 // Setting 50%, will end up being 75% because the sensors are in failsafe 189 // mode. 190 p->outputProc(50.0); 191 } 192 193 TEST(FanControllerTest, OutputProc_BehavesAsExpected) 194 { 195 // Verifies that when the system is not in failsafe mode, the input value 196 // to outputProc is used to drive the sensors (fans). 197 198 ZoneMock z; 199 200 std::vector<std::string> inputs = {"fan0", "fan1"}; 201 ec::pidinfo initial; 202 203 std::unique_ptr<PIDController> p = 204 FanController::createFanPid(&z, "fan1", inputs, initial); 205 EXPECT_FALSE(p == nullptr); 206 207 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false)); 208 209 int64_t timeout = 0; 210 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 211 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 212 // Grab pointers for mocking. 213 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 214 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get()); 215 216 EXPECT_CALL(z, getRedundantWrite()) 217 .WillOnce(Return(false)) 218 .WillOnce(Return(false)); 219 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 220 EXPECT_CALL(*sm1, write(0.5, false, _)); 221 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 222 EXPECT_CALL(*sm2, write(0.5, false, _)); 223 224 // This is a fan PID, so calling outputProc will try to write this value 225 // to the sensors. 226 p->outputProc(50.0); 227 } 228 229 TEST(FanControllerTest, OutputProc_VerifyFailSafeWhenInputHigher) 230 { 231 // If STRICT_FAILSAFE_PWM flag is NOT defined and the requested output is 232 // higher than the failsafe value, then use the value provided to outputProc 233 // 234 // If STRICT_FAILSAFE_PWM is defined, we expect the FailSafe PWM to be 235 // capped to the failsafe PWM, and not go higher than that. 236 237 ZoneMock z; 238 239 std::vector<std::string> inputs = {"fan0"}; 240 ec::pidinfo initial; 241 const double failsafePWM = 75.0; 242 243 std::unique_ptr<PIDController> p = 244 FanController::createFanPid(&z, "fan1", inputs, initial); 245 EXPECT_FALSE(p == nullptr); 246 247 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true)); 248 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM)); 249 250 int64_t timeout = 0; 251 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 252 // Grab pointer for mocking. 253 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 254 255 double percent = 80; 256 257 EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false)); 258 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 259 #ifdef STRICT_FAILSAFE_PWM 260 double failsafeValue = failsafePWM / 100; 261 EXPECT_CALL(*sm1, write(failsafeValue, false, _)); 262 #else 263 // Converting from double to double for expectation. 264 double value = percent / 100; 265 EXPECT_CALL(*sm1, write(value, false, _)); 266 #endif 267 268 // This is a fan PID, so calling outputProc will try to write this value 269 // to the sensors. 270 p->outputProc(percent); 271 } 272 273 TEST(FanControllerTest, OutputProc_VerifyRedundantWrites) 274 { 275 /* when a zone indicates that redundant writes are enabled 276 * make sure the fan controller honors this by forcing a sensor write 277 */ 278 ZoneMock z; 279 280 std::vector<std::string> inputs = {"fan0", "fan1"}; 281 ec::pidinfo initial; 282 283 std::unique_ptr<PIDController> p = 284 FanController::createFanPid(&z, "fan1", inputs, initial); 285 EXPECT_FALSE(p == nullptr); 286 287 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false)); 288 289 int64_t timeout = 0; 290 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 291 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 292 // Grab pointers for mocking. 293 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 294 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get()); 295 296 EXPECT_CALL(z, getRedundantWrite()) 297 .WillOnce(Return(true)) 298 .WillOnce(Return(true)); 299 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 300 EXPECT_CALL(*sm1, write(0.5, true, _)); 301 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 302 EXPECT_CALL(*sm2, write(0.5, true, _)); 303 304 // This is a fan PID, so calling outputProc will try to write this value 305 // to the sensors. 306 p->outputProc(50.0); 307 } 308 309 } // namespace 310 } // namespace pid_control 311