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