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