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