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