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, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 175 EXPECT_CALL(*sm1, write(0.75)); 176 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 177 EXPECT_CALL(*sm2, write(0.75)); 178 179 // This is a fan PID, so calling outputProc will try to write this value 180 // to the sensors. 181 182 // Setting 50%, will end up being 75% because the sensors are in failsafe 183 // mode. 184 p->outputProc(50.0); 185 } 186 187 TEST(FanControllerTest, OutputProc_BehavesAsExpected) 188 { 189 // Verifies that when the system is not in failsafe mode, the input value 190 // to outputProc is used to drive the sensors (fans). 191 192 ZoneMock z; 193 194 std::vector<std::string> inputs = {"fan0", "fan1"}; 195 ec::pidinfo initial; 196 197 std::unique_ptr<PIDController> p = 198 FanController::createFanPid(&z, "fan1", inputs, initial); 199 EXPECT_FALSE(p == nullptr); 200 201 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false)); 202 203 int64_t timeout = 0; 204 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 205 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 206 // Grab pointers for mocking. 207 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 208 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get()); 209 210 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 211 EXPECT_CALL(*sm1, write(0.5)); 212 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 213 EXPECT_CALL(*sm2, write(0.5)); 214 215 // This is a fan PID, so calling outputProc will try to write this value 216 // to the sensors. 217 p->outputProc(50.0); 218 } 219 220 TEST(FanControllerTest, OutputProc_VerifyFailSafeIgnoredIfInputHigher) 221 { 222 // If the requested output is higher than the failsafe value, then use the 223 // value provided to outputProc. 224 225 ZoneMock z; 226 227 std::vector<std::string> inputs = {"fan0"}; 228 ec::pidinfo initial; 229 230 std::unique_ptr<PIDController> p = 231 FanController::createFanPid(&z, "fan1", inputs, initial); 232 EXPECT_FALSE(p == nullptr); 233 234 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true)); 235 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0)); 236 237 int64_t timeout = 0; 238 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 239 // Grab pointer for mocking. 240 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get()); 241 242 // Converting from double to double for expectation. 243 double percent = 80; 244 double value = percent / 100; 245 246 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 247 EXPECT_CALL(*sm1, write(value)); 248 249 // This is a fan PID, so calling outputProc will try to write this value 250 // to the sensors. 251 p->outputProc(percent); 252 } 253 254 } // namespace 255 } // namespace pid_control 256