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