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