1 #include "failsafeloggers/builder.hpp"
2 #include "failsafeloggers/failsafe_logger.hpp"
3 #include "failsafeloggers/failsafe_logger_utility.hpp"
4 #include "pid/ec/logging.hpp"
5 #include "pid/ec/pid.hpp"
6 #include "pid/zone.hpp"
7 #include "sensors/manager.hpp"
8 #include "test/controller_mock.hpp"
9 #include "test/helpers.hpp"
10 #include "test/sensor_mock.hpp"
11
12 #include <sdbusplus/test/sdbus_mock.hpp>
13
14 #include <chrono>
15 #include <cstring>
16 #include <optional>
17 #include <unordered_map>
18 #include <vector>
19
20 #include <gmock/gmock.h>
21 #include <gtest/gtest.h>
22
23 namespace pid_control
24 {
25 namespace
26 {
27
28 using ::testing::_;
29 using ::testing::IsNull;
30 using ::testing::Return;
31 using ::testing::StrEq;
32
33 static std::string modeInterface = "xyz.openbmc_project.Control.Mode";
34 static std::string debugZoneInterface = "xyz.openbmc_project.Debug.Pid.Zone";
35 static std::string enableInterface = "xyz.openbmc_project.Object.Enable";
36 static std::string debugThermalPowerInterface =
37 "xyz.openbmc_project.Debug.Pid.ThermalPower";
38
39 namespace
40 {
41
TEST(PidZoneConstructorTest,BoringConstructorTest)42 TEST(PidZoneConstructorTest, BoringConstructorTest)
43 {
44 // Build a PID Zone.
45
46 sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode,
47 sdbus_mock_enable;
48 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
49 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
50 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
51 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
52
53 EXPECT_CALL(sdbus_mock_host,
54 sd_bus_add_object_manager(
55 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors")))
56 .WillOnce(Return(0));
57
58 SensorManager m(bus_mock_passive, bus_mock_host);
59
60 bool defer = true;
61 bool accSetPoint = false;
62 const char* objPath = "/path/";
63 int64_t zone = 1;
64 double minThermalOutput = 1000.0;
65 double failSafePercent = 100;
66 conf::CycleTime cycleTime;
67
68 double d;
69 std::vector<std::string> properties;
70 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties,
71 &d);
72 SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface,
73 properties, &d);
74
75 std::string sensorname = "temp1";
76 std::string pidsensorpath =
77 "/xyz/openbmc_project/settings/fanctrl/zone1/" + sensorname;
78
79 double de;
80 std::vector<std::string> propertiesenable;
81 SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
82 enableInterface, propertiesenable, &de);
83
84 DbusPidZone p(zone, minThermalOutput, failSafePercent, cycleTime, m,
85 bus_mock_mode, objPath, defer, accSetPoint);
86 // Success.
87 }
88
89 } // namespace
90
91 class PidZoneTest : public ::testing::Test
92 {
93 protected:
PidZoneTest()94 PidZoneTest() :
95 property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(),
96 sdbus_mock_mode(), sdbus_mock_enable()
97 {
98 EXPECT_CALL(sdbus_mock_host,
99 sd_bus_add_object_manager(
100 IsNull(), _, StrEq("/xyz/openbmc_project/extsensors")))
101 .WillOnce(Return(0));
102
103 auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
104 auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
105 auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
106 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
107
108 mgr = SensorManager(bus_mock_passive, bus_mock_host);
109
110 SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface,
111 properties, &property_index);
112 SetupDbusObject(&sdbus_mock_mode, defer, objPath, debugZoneInterface,
113 properties, &property_index);
114
115 SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
116 enableInterface, propertiesenable,
117 &propertyenable_index);
118
119 zone = std::make_unique<DbusPidZone>(
120 zoneId, minThermalOutput, failSafePercent, cycleTime, *mgr,
121 bus_mock_mode, objPath, defer, accSetPoint);
122 }
123
124 // unused
125 double property_index;
126 std::vector<std::string> properties;
127 double propertyenable_index;
128 std::vector<std::string> propertiesenable;
129
130 sdbusplus::SdBusMock sdbus_mock_passive;
131 sdbusplus::SdBusMock sdbus_mock_host;
132 sdbusplus::SdBusMock sdbus_mock_mode;
133 sdbusplus::SdBusMock sdbus_mock_enable;
134 int64_t zoneId = 1;
135 double minThermalOutput = 1000.0;
136 double failSafePercent = 100;
137 double setpoint = 50.0;
138 bool defer = true;
139 bool accSetPoint = false;
140 const char* objPath = "/path/";
141 std::optional<SensorManager> mgr;
142 conf::CycleTime cycleTime;
143
144 std::string sensorname = "temp1";
145 std::string sensorType = "temp";
146 std::string pidsensorpath =
147 "/xyz/openbmc_project/settings/fanctrl/zone1/" + sensorname;
148
149 std::unique_ptr<DbusPidZone> zone;
150 };
151
TEST_F(PidZoneTest,GetZoneId_ReturnsExpected)152 TEST_F(PidZoneTest, GetZoneId_ReturnsExpected)
153 {
154 // Verifies the zoneId returned is what we expect.
155
156 EXPECT_EQ(zoneId, zone->getZoneID());
157 }
158
TEST_F(PidZoneTest,GetAndSetManualModeTest_BehavesAsExpected)159 TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected)
160 {
161 // Verifies that the zone starts in manual mode. Verifies that one can set
162 // the mode.
163 EXPECT_FALSE(zone->getManualMode());
164
165 zone->setManualMode(true);
166 EXPECT_TRUE(zone->getManualMode());
167 }
168
TEST_F(PidZoneTest,AddPidControlProcessGetAndSetEnableTest_BehavesAsExpected)169 TEST_F(PidZoneTest, AddPidControlProcessGetAndSetEnableTest_BehavesAsExpected)
170 {
171 // Verifies that the zone starts in enable mode. Verifies that one can set
172 // enable the mode.
173 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
174
175 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
176 IsNull(), StrEq(pidsensorpath.c_str()),
177 StrEq(enableInterface), NotNull()))
178 .Times(::testing::AnyNumber())
179 .WillOnce(Invoke(
180 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
181 [[maybe_unused]] const char* interface, const char** names) {
182 EXPECT_STREQ("Enable", names[0]);
183 return 0;
184 }));
185
186 zone->addPidControlProcess(sensorname, sensorType, setpoint,
187 bus_mock_enable, pidsensorpath.c_str(), defer);
188 EXPECT_TRUE(zone->isPidProcessEnabled(sensorname));
189 }
190
TEST_F(PidZoneTest,SetManualMode_RedundantWritesEnabledOnceAfterManualMode)191 TEST_F(PidZoneTest, SetManualMode_RedundantWritesEnabledOnceAfterManualMode)
192 {
193 // Tests adding a fan PID controller to the zone, and verifies it's
194 // touched during processing.
195
196 std::unique_ptr<PIDController> tpid =
197 std::make_unique<ControllerMock>("fan1", zone.get());
198 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
199
200 // Access the internal pid configuration to clear it out (unrelated to the
201 // test).
202 [[maybe_unused]] ec::pid_info_t* info = tpid->getPIDInfo();
203
204 zone->addFanPID(std::move(tpid));
205
206 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
207 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
208 EXPECT_CALL(*tmock, outputProc(_));
209
210 // while zone is in auto mode redundant writes should be disabled
211 EXPECT_FALSE(zone->getRedundantWrite());
212
213 // but switching from manual to auto enables a single redundant write
214 zone->setManualMode(true);
215 zone->setManualMode(false);
216 EXPECT_TRUE(zone->getRedundantWrite());
217
218 // after one iteration of a pid loop redundant write should be cleared
219 zone->processFans();
220 EXPECT_FALSE(zone->getRedundantWrite());
221 }
222
TEST_F(PidZoneTest,RpmSetPoints_AddMaxClear_BehaveAsExpected)223 TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected)
224 {
225 // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest
226 // and getMinThermalSetPoint.
227
228 // Need to add pid control process for the zone that can enable
229 // the process and add the set point.
230 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
231
232 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
233 IsNull(), StrEq(pidsensorpath.c_str()),
234 StrEq(enableInterface), NotNull()))
235 .Times(::testing::AnyNumber())
236 .WillOnce(Invoke(
237 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
238 [[maybe_unused]] const char* interface, const char** names) {
239 EXPECT_STREQ("Enable", names[0]);
240 return 0;
241 }));
242
243 zone->addPidControlProcess(sensorname, sensorType, setpoint,
244 bus_mock_enable, pidsensorpath.c_str(), defer);
245
246 // At least one value must be above the minimum thermal setpoint used in
247 // the constructor otherwise it'll choose that value
248 std::vector<double> values = {100, 200, 300, 400, 500, 5000};
249
250 for (auto v : values)
251 {
252 zone->addSetPoint(v, sensorname);
253 }
254
255 // This will pull the maximum RPM setpoint request.
256 zone->determineMaxSetPointRequest();
257 EXPECT_EQ(5000, zone->getMaxSetPointRequest());
258
259 // Clear the values, so it'll choose the minimum thermal setpoint.
260 zone->clearSetPoints();
261
262 // This will go through the RPM set point values and grab the maximum.
263 zone->determineMaxSetPointRequest();
264 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest());
265 }
266
TEST_F(PidZoneTest,RpmSetPoints_AddBelowMinimum_BehavesAsExpected)267 TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected)
268 {
269 // Tests adding several RPM setpoints, however, they're all lower than the
270 // configured minimal thermal setpoint RPM value.
271
272 // Need to add pid control process for the zone that can enable
273 // the process and add the set point.
274 auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
275
276 EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
277 IsNull(), StrEq(pidsensorpath.c_str()),
278 StrEq(enableInterface), NotNull()))
279 .Times(::testing::AnyNumber())
280 .WillOnce(Invoke(
281 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
282 [[maybe_unused]] const char* interface, const char** names) {
283 EXPECT_STREQ("Enable", names[0]);
284 return 0;
285 }));
286
287 zone->addPidControlProcess(sensorname, sensorType, setpoint,
288 bus_mock_enable, pidsensorpath.c_str(), defer);
289
290 std::vector<double> values = {100, 200, 300, 400, 500};
291
292 for (auto v : values)
293 {
294 zone->addSetPoint(v, sensorname);
295 }
296
297 // This will pull the maximum RPM setpoint request.
298 zone->determineMaxSetPointRequest();
299
300 // Verifies the value returned in the minimal thermal rpm set point.
301 EXPECT_EQ(zone->getMinThermalSetPoint(), zone->getMaxSetPointRequest());
302 }
303
TEST_F(PidZoneTest,GetFailSafePercent_SingleFailedReturnsExpected)304 TEST_F(PidZoneTest, GetFailSafePercent_SingleFailedReturnsExpected)
305 {
306 // Tests when only one sensor failed and the sensor's failsafe duty is zero,
307 // and verify that the sensor name is empty and failsafe duty is PID zone's
308 // failsafe duty.
309
310 std::vector<std::string> input1 = {"temp1"};
311 std::vector<std::string> input2 = {"temp2"};
312 std::vector<std::string> input3 = {"temp3"};
313 std::vector<double> values = {0, 0, 0};
314
315 zone->addPidFailSafePercent(input1, values[0]);
316 zone->addPidFailSafePercent(input2, values[1]);
317 zone->addPidFailSafePercent(input3, values[2]);
318
319 zone->markSensorMissing("temp1", "Sensor threshold asserted");
320
321 EXPECT_EQ(failSafePercent, zone->getFailSafePercent());
322
323 std::map<std::string, std::pair<std::string, double>> failSensorList =
324 zone->getFailSafeSensors();
325 EXPECT_EQ(1, failSensorList.size());
326 EXPECT_EQ("Sensor threshold asserted", failSensorList["temp1"].first);
327 EXPECT_EQ(failSafePercent, failSensorList["temp1"].second);
328 }
329
TEST_F(PidZoneTest,GetFailSafePercent_MultiFailedReturnsExpected)330 TEST_F(PidZoneTest, GetFailSafePercent_MultiFailedReturnsExpected)
331 {
332 // Tests when multi sensor failed, and verify the final failsafe's sensor
333 // name and duty as expected.
334
335 std::vector<std::string> input1 = {"temp1"};
336 std::vector<std::string> input2 = {"temp2"};
337 std::vector<std::string> input3 = {"temp3"};
338 std::vector<double> values = {60, 80, 70};
339
340 zone->addPidFailSafePercent(input1, values[0]);
341 zone->addPidFailSafePercent(input2, values[1]);
342 zone->addPidFailSafePercent(input3, values[2]);
343
344 zone->markSensorMissing("temp1", "Sensor threshold asserted");
345 zone->markSensorMissing("temp2", "Sensor reading bad");
346 zone->markSensorMissing("temp3", "Sensor unavailable");
347
348 EXPECT_EQ(80, zone->getFailSafePercent());
349
350 std::map<std::string, std::pair<std::string, double>> failSensorList =
351 zone->getFailSafeSensors();
352 EXPECT_EQ(3, failSensorList.size());
353 EXPECT_EQ("Sensor threshold asserted", failSensorList["temp1"].first);
354 EXPECT_EQ(60, failSensorList["temp1"].second);
355 EXPECT_EQ("Sensor reading bad", failSensorList["temp2"].first);
356 EXPECT_EQ(80, failSensorList["temp2"].second);
357 EXPECT_EQ("Sensor unavailable", failSensorList["temp3"].first);
358 EXPECT_EQ(70, failSensorList["temp3"].second);
359 }
360
TEST_F(PidZoneTest,ThermalInputs_FailsafeToValid_ReadsSensors)361 TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors)
362 {
363 // This test will add a couple thermal inputs, and verify that the zone
364 // initializes into failsafe mode, and will read each sensor.
365
366 // Disable failsafe logger for the unit test.
367 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
368 buildFailsafeLoggers(empty_zone_map, 0);
369
370 std::string name1 = "temp1";
371 int64_t timeout = 1;
372
373 std::unique_ptr<Sensor> sensor1 =
374 std::make_unique<SensorMock>(name1, timeout);
375 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
376
377 std::string name2 = "temp2";
378 std::unique_ptr<Sensor> sensor2 =
379 std::make_unique<SensorMock>(name2, timeout);
380 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
381
382 std::string type = "unchecked";
383 mgr->addSensor(type, name1, std::move(sensor1));
384 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
385 mgr->addSensor(type, name2, std::move(sensor2));
386 EXPECT_EQ(mgr->getSensor(name2), sensor_ptr2);
387
388 // Now that the sensors exist, add them to the zone.
389 zone->addThermalInput(name1, false);
390 zone->addThermalInput(name2, false);
391
392 // Initialize Zone
393 zone->initializeCache();
394
395 // Verify now in failsafe mode.
396 EXPECT_TRUE(zone->getFailSafeMode());
397
398 ReadReturn r1;
399 r1.value = 10.0;
400 r1.updated = std::chrono::high_resolution_clock::now();
401 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
402
403 ReadReturn r2;
404 r2.value = 11.0;
405 r2.updated = std::chrono::high_resolution_clock::now();
406 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
407
408 // Read the sensors, this will put the values into the cache.
409 zone->updateSensors();
410
411 // We should no longer be in failsafe mode.
412 EXPECT_FALSE(zone->getFailSafeMode());
413
414 EXPECT_EQ(r1.value, zone->getCachedValue(name1));
415 EXPECT_EQ(r2.value, zone->getCachedValue(name2));
416 }
417
TEST_F(PidZoneTest,FanInputTest_VerifiesFanValuesCached)418 TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached)
419 {
420 // This will add a couple fan inputs, and verify the values are cached.
421
422 // Disable failsafe logger for the unit test.
423 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
424 buildFailsafeLoggers(empty_zone_map, 0);
425
426 std::string name1 = "fan1";
427 int64_t timeout = 2;
428
429 std::unique_ptr<Sensor> sensor1 =
430 std::make_unique<SensorMock>(name1, timeout);
431 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
432
433 std::string name2 = "fan2";
434 std::unique_ptr<Sensor> sensor2 =
435 std::make_unique<SensorMock>(name2, timeout);
436 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
437
438 std::string type = "unchecked";
439 mgr->addSensor(type, name1, std::move(sensor1));
440 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
441 mgr->addSensor(type, name2, std::move(sensor2));
442 EXPECT_EQ(mgr->getSensor(name2), sensor_ptr2);
443
444 // Now that the sensors exist, add them to the zone.
445 zone->addFanInput(name1, false);
446 zone->addFanInput(name2, false);
447
448 // Initialize Zone
449 zone->initializeCache();
450
451 ReadReturn r1;
452 r1.value = 10.0;
453 r1.updated = std::chrono::high_resolution_clock::now();
454 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
455
456 ReadReturn r2;
457 r2.value = 11.0;
458 r2.updated = std::chrono::high_resolution_clock::now();
459 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
460
461 // Method under test will read through each fan sensor for the zone and
462 // cache the values.
463 zone->updateFanTelemetry();
464
465 EXPECT_EQ(r1.value, zone->getCachedValue(name1));
466 EXPECT_EQ(r2.value, zone->getCachedValue(name2));
467 }
468
TEST_F(PidZoneTest,ThermalInput_ValueTimeoutEntersFailSafeMode)469 TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode)
470 {
471 // On the second updateSensors call, the updated timestamp will be beyond
472 // the timeout limit.
473
474 // Disable failsafe logger for the unit test.
475 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
476 buildFailsafeLoggers(empty_zone_map, 0);
477
478 int64_t timeout = 1;
479
480 std::string name1 = "temp1";
481 std::unique_ptr<Sensor> sensor1 =
482 std::make_unique<SensorMock>(name1, timeout);
483 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
484
485 std::string name2 = "temp2";
486 std::unique_ptr<Sensor> sensor2 =
487 std::make_unique<SensorMock>(name2, timeout);
488 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
489
490 std::string type = "unchecked";
491 mgr->addSensor(type, name1, std::move(sensor1));
492 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
493 mgr->addSensor(type, name2, std::move(sensor2));
494 EXPECT_EQ(mgr->getSensor(name2), sensor_ptr2);
495
496 zone->addThermalInput(name1, false);
497 zone->addThermalInput(name2, false);
498
499 // Initialize Zone
500 zone->initializeCache();
501
502 // Verify now in failsafe mode.
503 EXPECT_TRUE(zone->getFailSafeMode());
504
505 ReadReturn r1;
506 r1.value = 10.0;
507 r1.updated = std::chrono::high_resolution_clock::now();
508 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
509
510 ReadReturn r2;
511 r2.value = 11.0;
512 r2.updated = std::chrono::high_resolution_clock::now();
513 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
514
515 zone->updateSensors();
516 EXPECT_FALSE(zone->getFailSafeMode());
517
518 // Ok, so we're not in failsafe mode, so let's set updated to the past.
519 // sensor1 will have an updated field older than its timeout value, but
520 // sensor2 will be fine. :D
521 r1.updated -= std::chrono::seconds(3);
522 r2.updated = std::chrono::high_resolution_clock::now();
523
524 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
525 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
526
527 // Method under test will read each sensor. One sensor's value is older
528 // than the timeout for that sensor and this triggers failsafe mode.
529 zone->updateSensors();
530 EXPECT_TRUE(zone->getFailSafeMode());
531 }
532
TEST_F(PidZoneTest,ThermalInput_MissingIsAcceptableNoFailSafe)533 TEST_F(PidZoneTest, ThermalInput_MissingIsAcceptableNoFailSafe)
534 {
535 // This is similar to the above test, but because missingIsAcceptable
536 // is set for sensor1, the zone should not enter failsafe mode when
537 // only sensor1 goes missing.
538 // However, sensor2 going missing should still trigger failsafe mode.
539
540 // Disable failsafe logger for the unit test.
541 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
542 buildFailsafeLoggers(empty_zone_map, 0);
543
544 int64_t timeout = 1;
545
546 std::string name1 = "temp1";
547 std::unique_ptr<Sensor> sensor1 =
548 std::make_unique<SensorMock>(name1, timeout);
549 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
550
551 std::string name2 = "temp2";
552 std::unique_ptr<Sensor> sensor2 =
553 std::make_unique<SensorMock>(name2, timeout);
554 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
555
556 std::string type = "unchecked";
557 mgr->addSensor(type, name1, std::move(sensor1));
558 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
559 mgr->addSensor(type, name2, std::move(sensor2));
560 EXPECT_EQ(mgr->getSensor(name2), sensor_ptr2);
561
562 // Only sensor1 has MissingIsAcceptable enabled for it
563 zone->addThermalInput(name1, true);
564 zone->addThermalInput(name2, false);
565
566 // Initialize Zone
567 zone->initializeCache();
568
569 // As sensors are not initialized, zone should be in failsafe mode
570 EXPECT_TRUE(zone->getFailSafeMode());
571
572 // r1 not populated here, intentionally, to simulate a sensor that
573 // is not available yet, perhaps takes a long time to start up.
574 ReadReturn r1;
575 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
576
577 ReadReturn r2;
578 r2.value = 11.0;
579 r2.updated = std::chrono::high_resolution_clock::now();
580 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
581
582 zone->updateSensors();
583
584 // Only sensor2 has been initialized here. Failsafe should be false,
585 // because sensor1 MissingIsAcceptable so it is OK for it to go missing.
586 EXPECT_FALSE(zone->getFailSafeMode());
587
588 r1.value = 10.0;
589 r1.updated = std::chrono::high_resolution_clock::now();
590
591 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
592 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
593 zone->updateSensors();
594
595 // Both sensors are now properly initialized
596 EXPECT_FALSE(zone->getFailSafeMode());
597
598 // Ok, so we're not in failsafe mode, so let's set updated to the past.
599 // sensor1 will have an updated field older than its timeout value, but
600 // sensor2 will be fine. :D
601 r1.updated -= std::chrono::seconds(3);
602 r2.updated = std::chrono::high_resolution_clock::now();
603
604 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
605 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
606 zone->updateSensors();
607
608 // MissingIsAcceptable is true for sensor1, so the zone should not be
609 // thrown into failsafe mode.
610 EXPECT_FALSE(zone->getFailSafeMode());
611
612 // Do the same thing, but for the opposite sensors: r1 is good,
613 // but r2 is set to some time in the past.
614 r1.updated = std::chrono::high_resolution_clock::now();
615 r2.updated -= std::chrono::seconds(3);
616
617 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
618 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
619 zone->updateSensors();
620
621 // Now, the zone should be in failsafe mode, because sensor2 does not
622 // have MissingIsAcceptable set true, it is still subject to failsafe.
623 EXPECT_TRUE(zone->getFailSafeMode());
624
625 r1.updated = std::chrono::high_resolution_clock::now();
626 r2.updated = std::chrono::high_resolution_clock::now();
627
628 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
629 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
630 zone->updateSensors();
631
632 // The failsafe mode should cease, as both sensors are good again.
633 EXPECT_FALSE(zone->getFailSafeMode());
634 }
635
TEST_F(PidZoneTest,FanInputTest_FailsafeToValid_ReadsSensors)636 TEST_F(PidZoneTest, FanInputTest_FailsafeToValid_ReadsSensors)
637 {
638 // This will add a couple fan inputs, and verify the values are cached.
639
640 // Disable failsafe logger for the unit test.
641 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
642 buildFailsafeLoggers(empty_zone_map, 0);
643
644 std::string name1 = "fan1";
645 int64_t timeout = 2;
646
647 std::unique_ptr<Sensor> sensor1 =
648 std::make_unique<SensorMock>(name1, timeout);
649 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
650
651 std::string name2 = "fan2";
652 std::unique_ptr<Sensor> sensor2 =
653 std::make_unique<SensorMock>(name2, timeout);
654 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
655
656 std::string type = "unchecked";
657 mgr->addSensor(type, name1, std::move(sensor1));
658 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
659 mgr->addSensor(type, name2, std::move(sensor2));
660 EXPECT_EQ(mgr->getSensor(name2), sensor_ptr2);
661
662 // Now that the sensors exist, add them to the zone.
663 zone->addFanInput(name1, false);
664 zone->addFanInput(name2, false);
665
666 // Initialize Zone
667 zone->initializeCache();
668
669 // Verify now in failsafe mode.
670 EXPECT_TRUE(zone->getFailSafeMode());
671
672 ReadReturn r1;
673 r1.value = 10.0;
674 r1.updated = std::chrono::high_resolution_clock::now();
675 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
676
677 ReadReturn r2;
678 r2.value = 11.0;
679 r2.updated = std::chrono::high_resolution_clock::now();
680 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
681
682 // Method under test will read through each fan sensor for the zone and
683 // cache the values.
684 zone->updateFanTelemetry();
685
686 // We should no longer be in failsafe mode.
687 EXPECT_FALSE(zone->getFailSafeMode());
688
689 EXPECT_EQ(r1.value, zone->getCachedValue(name1));
690 EXPECT_EQ(r2.value, zone->getCachedValue(name2));
691 }
692
TEST_F(PidZoneTest,FanInputTest_ValueTimeoutEntersFailSafeMode)693 TEST_F(PidZoneTest, FanInputTest_ValueTimeoutEntersFailSafeMode)
694 {
695 // This will add a couple fan inputs, and verify the values are cached.
696
697 // Disable failsafe logger for the unit test.
698 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
699 buildFailsafeLoggers(empty_zone_map, 0);
700
701 std::string name1 = "fan1";
702 int64_t timeout = 2;
703
704 std::unique_ptr<Sensor> sensor1 =
705 std::make_unique<SensorMock>(name1, timeout);
706 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
707
708 std::string name2 = "fan2";
709 std::unique_ptr<Sensor> sensor2 =
710 std::make_unique<SensorMock>(name2, timeout);
711 SensorMock* sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get());
712
713 std::string type = "unchecked";
714 mgr->addSensor(type, name1, std::move(sensor1));
715 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
716 mgr->addSensor(type, name2, std::move(sensor2));
717 EXPECT_EQ(mgr->getSensor(name2), sensor_ptr2);
718
719 // Now that the sensors exist, add them to the zone.
720 zone->addFanInput(name1, false);
721 zone->addFanInput(name2, false);
722
723 // Initialize Zone
724 zone->initializeCache();
725
726 // Verify now in failsafe mode.
727 EXPECT_TRUE(zone->getFailSafeMode());
728
729 ReadReturn r1;
730 r1.value = 10.0;
731 r1.updated = std::chrono::high_resolution_clock::now();
732 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
733
734 ReadReturn r2;
735 r2.value = 11.0;
736 r2.updated = std::chrono::high_resolution_clock::now();
737 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
738
739 // Method under test will read through each fan sensor for the zone and
740 // cache the values.
741 zone->updateFanTelemetry();
742
743 // We should no longer be in failsafe mode.
744 EXPECT_FALSE(zone->getFailSafeMode());
745
746 r1.updated -= std::chrono::seconds(3);
747 r2.updated = std::chrono::high_resolution_clock::now();
748
749 EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1));
750 EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2));
751
752 zone->updateFanTelemetry();
753 EXPECT_TRUE(zone->getFailSafeMode());
754 }
755
TEST_F(PidZoneTest,GetSensorTest_ReturnsExpected)756 TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected)
757 {
758 // One can grab a sensor from the manager through the zone.
759
760 // Disable failsafe logger for the unit test.
761 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
762 buildFailsafeLoggers(empty_zone_map, 0);
763
764 int64_t timeout = 1;
765
766 std::string name1 = "temp1";
767 std::unique_ptr<Sensor> sensor1 =
768 std::make_unique<SensorMock>(name1, timeout);
769 SensorMock* sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get());
770
771 std::string type = "unchecked";
772 mgr->addSensor(type, name1, std::move(sensor1));
773 EXPECT_EQ(mgr->getSensor(name1), sensor_ptr1);
774
775 zone->addThermalInput(name1, false);
776
777 // Verify method under test returns the pointer we expect.
778 EXPECT_EQ(mgr->getSensor(name1), zone->getSensor(name1));
779 }
780
TEST_F(PidZoneTest,AddThermalPIDTest_VerifiesThermalPIDsProcessed)781 TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed)
782 {
783 // Tests adding a thermal PID controller to the zone, and verifies it's
784 // touched during processing.
785
786 std::unique_ptr<PIDController> tpid =
787 std::make_unique<ControllerMock>("thermal1", zone.get());
788 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
789
790 // Access the internal pid configuration to clear it out (unrelated to the
791 // test).
792 [[maybe_unused]] ec::pid_info_t* info = tpid->getPIDInfo();
793
794 zone->addThermalPID(std::move(tpid));
795
796 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
797 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
798 EXPECT_CALL(*tmock, outputProc(_));
799
800 // Method under test will, for each thermal PID, call setpt, input, and
801 // output.
802 zone->processThermals();
803 }
804
TEST_F(PidZoneTest,AddFanPIDTest_VerifiesFanPIDsProcessed)805 TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed)
806 {
807 // Tests adding a fan PID controller to the zone, and verifies it's
808 // touched during processing.
809
810 std::unique_ptr<PIDController> tpid =
811 std::make_unique<ControllerMock>("fan1", zone.get());
812 ControllerMock* tmock = reinterpret_cast<ControllerMock*>(tpid.get());
813
814 // Access the internal pid configuration to clear it out (unrelated to the
815 // test).
816 [[maybe_unused]] ec::pid_info_t* info = tpid->getPIDInfo();
817
818 zone->addFanPID(std::move(tpid));
819
820 EXPECT_CALL(*tmock, setptProc()).WillOnce(Return(10.0));
821 EXPECT_CALL(*tmock, inputProc()).WillOnce(Return(11.0));
822 EXPECT_CALL(*tmock, outputProc(_));
823
824 // Method under test will, for each fan PID, call setpt, input, and output.
825 zone->processFans();
826 }
827
TEST_F(PidZoneTest,ManualModeDbusTest_VerifySetManualBehavesAsExpected)828 TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected)
829 {
830 // The manual(bool) method is inherited from the dbus mode interface.
831
832 // Verifies that someone doesn't remove the internal call to the dbus
833 // object from which we're inheriting.
834 EXPECT_CALL(sdbus_mock_mode,
835 sd_bus_emit_properties_changed_strv(
836 IsNull(), StrEq(objPath), StrEq(modeInterface), NotNull()))
837 .WillOnce(Invoke(
838 [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
839 [[maybe_unused]] const char* interface, const char** names) {
840 EXPECT_STREQ("Manual", names[0]);
841 return 0;
842 }));
843
844 // Method under test will set the manual mode to true and broadcast this
845 // change on dbus.
846 zone->manual(true);
847 EXPECT_TRUE(zone->getManualMode());
848 }
849
TEST_F(PidZoneTest,FailsafeDbusTest_VerifiesReturnsExpected)850 TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected)
851 {
852 // This property is implemented by us as read-only, such that trying to
853 // write to it will have no effect.
854
855 // Disable failsafe logger for the unit test.
856 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>> empty_zone_map;
857 buildFailsafeLoggers(empty_zone_map, 0);
858
859 EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode());
860 }
861
862 } // namespace
863 } // namespace pid_control
864