1 #include "pid/fancontroller.hpp" 2 3 #include <gmock/gmock.h> 4 #include <gtest/gtest.h> 5 #include <string> 6 #include <vector> 7 8 #include "pid/ec/pid.hpp" 9 #include "test/sensor_mock.hpp" 10 #include "test/zone_mock.hpp" 11 12 using ::testing::DoubleEq; 13 using ::testing::Invoke; 14 using ::testing::Return; 15 using ::testing::StrEq; 16 using ::testing::_; 17 18 TEST(FanControllerTest, BoringFactoryTest) { 19 // Verify the factory will properly build the FanPIDController in the 20 // boring (uninteresting) case. 21 ZoneMock z; 22 23 std::vector<std::string> inputs = {"fan0"}; 24 ec::pidinfo initial; 25 26 std::unique_ptr<PIDController> p = 27 FanController::CreateFanPid(&z, "fan1", inputs, initial); 28 // Success 29 EXPECT_FALSE(p == nullptr); 30 } 31 32 TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs) { 33 // A fan controller needs at least one input. 34 35 ZoneMock z; 36 37 std::vector<std::string> inputs = {}; 38 ec::pidinfo initial; 39 40 std::unique_ptr<PIDController> p = 41 FanController::CreateFanPid(&z, "fan1", inputs, initial); 42 EXPECT_TRUE(p == nullptr); 43 } 44 45 TEST(FanControllerTest, InputProc_AllSensorsReturnZero) { 46 // If all your inputs are 0, return 0. 47 48 ZoneMock z; 49 50 std::vector<std::string> inputs = {"fan0", "fan1"}; 51 ec::pidinfo initial; 52 53 std::unique_ptr<PIDController> p = 54 FanController::CreateFanPid(&z, "fan1", inputs, initial); 55 EXPECT_FALSE(p == nullptr); 56 57 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0)); 58 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0)); 59 60 EXPECT_EQ(0.0, p->input_proc()); 61 } 62 63 TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored) { 64 // A sensor value returning sub-zero is ignored as an error. 65 ZoneMock z; 66 67 std::vector<std::string> inputs = {"fan0", "fan1"}; 68 ec::pidinfo initial; 69 70 std::unique_ptr<PIDController> p = 71 FanController::CreateFanPid(&z, "fan1", inputs, initial); 72 EXPECT_FALSE(p == nullptr); 73 74 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1)); 75 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1)); 76 77 EXPECT_EQ(0.0, p->input_proc()); 78 } 79 80 TEST(FanControllerTest, InputProc_ChoosesMinimumValue) { 81 // Verify it selects the minimum value from its inputs. 82 83 ZoneMock z; 84 85 std::vector<std::string> inputs = {"fan0", "fan1", "fan2"}; 86 ec::pidinfo initial; 87 88 std::unique_ptr<PIDController> p = 89 FanController::CreateFanPid(&z, "fan1", inputs, initial); 90 EXPECT_FALSE(p == nullptr); 91 92 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0)); 93 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0)); 94 EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0)); 95 96 EXPECT_EQ(5.0, p->input_proc()); 97 } 98 99 // The direction is unused presently, but these tests validate the logic. 100 TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection) { 101 // The fan direction defaults to neutral, because we have no data. Verify 102 // that after this point it appropriately will indicate speeding up or 103 // slowing down based on the RPM values specified. 104 105 ZoneMock z; 106 107 std::vector<std::string> inputs = {"fan0", "fan1"}; 108 ec::pidinfo initial; 109 110 std::unique_ptr<PIDController> p = 111 FanController::CreateFanPid(&z, "fan1", inputs, initial); 112 EXPECT_FALSE(p == nullptr); 113 // Grab pointer for mocking. 114 FanController *fp = reinterpret_cast<FanController*>(p.get()); 115 116 // Fanspeed starts are Neutral. 117 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection()); 118 119 // getMaxRPMRequest returns a higher value than 0, so the fans should be 120 // marked as speeding up. 121 EXPECT_CALL(z, getMaxRPMRequest()).WillOnce(Return(10.0)); 122 EXPECT_EQ(10.0, p->setpt_proc()); 123 EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection()); 124 125 // getMaxRPMRequest returns a lower value than 10, so the fans should be 126 // marked as slowing down. 127 EXPECT_CALL(z, getMaxRPMRequest()).WillOnce(Return(5.0)); 128 EXPECT_EQ(5.0, p->setpt_proc()); 129 EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection()); 130 131 // getMaxRPMRequest returns the same value, so the fans should be marked as 132 // neutral. 133 EXPECT_CALL(z, getMaxRPMRequest()).WillOnce(Return(5.0)); 134 EXPECT_EQ(5.0, p->setpt_proc()); 135 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection()); 136 } 137 138 TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored) { 139 // Verify that if failsafe mode is enabled and the input value for the fans 140 // is below the failsafe minimum value, the input is not used and the fans 141 // are driven at failsafe RPM. 142 143 ZoneMock z; 144 145 std::vector<std::string> inputs = {"fan0", "fan1"}; 146 ec::pidinfo initial; 147 148 std::unique_ptr<PIDController> p = 149 FanController::CreateFanPid(&z, "fan1", inputs, initial); 150 EXPECT_FALSE(p == nullptr); 151 152 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true)); 153 EXPECT_CALL(z, getFailSafePercent()).Times(2).WillRepeatedly(Return(75.0)); 154 155 int64_t timeout = 0; 156 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 157 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 158 // Grab pointers for mocking. 159 SensorMock *sm1 = reinterpret_cast<SensorMock*>(s1.get()); 160 SensorMock *sm2 = reinterpret_cast<SensorMock*>(s2.get()); 161 162 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 163 EXPECT_CALL(*sm1, write(0.75)); 164 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 165 EXPECT_CALL(*sm2, write(0.75)); 166 167 // This is a fan PID, so calling output_proc will try to write this value 168 // to the sensors. 169 170 // Setting 50%, will end up being 75% because the sensors are in failsafe mode. 171 p->output_proc(50.0); 172 } 173 174 TEST(FanControllerTest, OutputProc_BehavesAsExpected) { 175 // Verifies that when the system is not in failsafe mode, the input value 176 // to output_proc is used to drive the sensors (fans). 177 178 ZoneMock z; 179 180 std::vector<std::string> inputs = {"fan0", "fan1"}; 181 ec::pidinfo initial; 182 183 std::unique_ptr<PIDController> p = 184 FanController::CreateFanPid(&z, "fan1", inputs, initial); 185 EXPECT_FALSE(p == nullptr); 186 187 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false)); 188 189 int64_t timeout = 0; 190 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 191 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout); 192 // Grab pointers for mocking. 193 SensorMock *sm1 = reinterpret_cast<SensorMock*>(s1.get()); 194 SensorMock *sm2 = reinterpret_cast<SensorMock*>(s2.get()); 195 196 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 197 EXPECT_CALL(*sm1, write(0.5)); 198 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get())); 199 EXPECT_CALL(*sm2, write(0.5)); 200 201 // This is a fan PID, so calling output_proc will try to write this value 202 // to the sensors. 203 p->output_proc(50.0); 204 } 205 206 TEST(FanControllerTest, OutputProc_VerifyFailSafeIgnoredIfInputHigher) { 207 // If the requested output is higher than the failsafe value, then use the 208 // value provided to output_proc. 209 210 ZoneMock z; 211 212 std::vector<std::string> inputs = {"fan0"}; 213 ec::pidinfo initial; 214 215 std::unique_ptr<PIDController> p = 216 FanController::CreateFanPid(&z, "fan1", inputs, initial); 217 EXPECT_FALSE(p == nullptr); 218 219 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true)); 220 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0)); 221 222 int64_t timeout = 0; 223 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout); 224 // Grab pointer for mocking. 225 SensorMock *sm1 = reinterpret_cast<SensorMock*>(s1.get()); 226 227 // Converting from float to double for expectation. 228 float percent = 80; 229 double value = static_cast<double>(percent / 100); 230 231 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get())); 232 EXPECT_CALL(*sm1, write(value)); 233 234 // This is a fan PID, so calling output_proc will try to write this value 235 // to the sensors. 236 p->output_proc(percent); 237 } 238