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