diff options
Diffstat (limited to 'test/pid_zone_unittest.cpp')
-rw-r--r-- | test/pid_zone_unittest.cpp | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/test/pid_zone_unittest.cpp b/test/pid_zone_unittest.cpp new file mode 100644 index 0000000..c511b3c --- /dev/null +++ b/test/pid_zone_unittest.cpp @@ -0,0 +1,422 @@ +#include "pid/zone.hpp" + +#include <chrono> +#include <cstring> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <sdbusplus/test/sdbus_mock.hpp> +#include <vector> + +#include "pid/ec/pid.hpp" +#include "sensors/manager.hpp" +#include "test/controller_mock.hpp" +#include "test/sensor_mock.hpp" +#include "test/helpers.hpp" + +using ::testing::IsNull; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::_; + +static std::string modeInterface = "xyz.openbmc_project.Control.Mode"; + +namespace { + +TEST(PidZoneConstructorTest, BoringConstructorTest) { + // Build a PID Zone. + + sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode; + auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive); + auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); + auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); + + EXPECT_CALL(sdbus_mock_host, + sd_bus_add_object_manager( + IsNull(), + _, + StrEq("/xyz/openbmc_project/extsensors"))) + .WillOnce(Return(0)); + + SensorManager m(std::move(bus_mock_passive), + std::move(bus_mock_host)); + + bool defer = true; + const char *objPath = "/path/"; + int64_t zone = 1; + float minThermalRpm = 1000.0; + float failSafePercent = 0.75; + + int i; + std::vector<std::string> properties; + SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, + properties, &i); + + PIDZone p(zone, minThermalRpm, failSafePercent, m, bus_mock_mode, objPath, + defer); + // Success. +} + +} + +class PidZoneTest : public ::testing::Test { + protected: + PidZoneTest() + : property_index(), + properties(), + sdbus_mock_passive(), + sdbus_mock_host(), + sdbus_mock_mode() + { + EXPECT_CALL(sdbus_mock_host, + sd_bus_add_object_manager( + IsNull(), + _, + StrEq("/xyz/openbmc_project/extsensors"))) + .WillOnce(Return(0)); + + auto bus_mock_passive = + sdbusplus::get_mocked_new(&sdbus_mock_passive); + auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host); + auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode); + + // Compiler weirdly not happy about just instantiating mgr(...); + SensorManager m(std::move(bus_mock_passive), + std::move(bus_mock_host)); + mgr = std::move(m); + + SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, + properties, &property_index); + + zone = std::make_unique<PIDZone>(zoneId, minThermalRpm, + failSafePercent, mgr, + bus_mock_mode, objPath, defer); + } + + // unused + int property_index; + std::vector<std::string> properties; + + sdbusplus::SdBusMock sdbus_mock_passive; + sdbusplus::SdBusMock sdbus_mock_host; + sdbusplus::SdBusMock sdbus_mock_mode; + int64_t zoneId = 1; + float minThermalRpm = 1000.0; + float failSafePercent = 0.75; + bool defer = true; + const char *objPath = "/path/"; + SensorManager mgr; + + std::unique_ptr<PIDZone> zone; +}; + +TEST_F(PidZoneTest, GetZoneId_ReturnsExpected) { + // Verifies the zoneId returned is what we expect. + + EXPECT_EQ(zoneId, zone->getZoneId()); +} + +TEST_F(PidZoneTest, GetAndSetManualModeTest_BehavesAsExpected) { + // Verifies that the zone starts in manual mode. Verifies that one can set + // the mode. + EXPECT_FALSE(zone->getManualMode()); + + zone->setManualMode(true); + EXPECT_TRUE(zone->getManualMode()); +} + +TEST_F(PidZoneTest, RpmSetPoints_AddMaxClear_BehaveAsExpected) { + // Tests addRPMSetPoint, clearRPMSetPoints, determineMaxRPMRequest + // and getMinThermalRpmSetPt. + + // At least one value must be above the minimum thermal setpoint used in + // the constructor otherwise it'll choose that value + std::vector<float> values = {100, 200, 300, 400, 500, 5000}; + for (auto v : values) + { + zone->addRPMSetPoint(v); + } + + // This will pull the maximum RPM setpoint request. + zone->determineMaxRPMRequest(); + EXPECT_EQ(5000, zone->getMaxRPMRequest()); + + // Clear the values, so it'll choose the minimum thermal setpoint. + zone->clearRPMSetPoints(); + + // This will go through the RPM set point values and grab the maximum. + zone->determineMaxRPMRequest(); + EXPECT_EQ(zone->getMinThermalRpmSetPt(), zone->getMaxRPMRequest()); +} + +TEST_F(PidZoneTest, RpmSetPoints_AddBelowMinimum_BehavesAsExpected) { + // Tests adding several RPM setpoints, however, they're all lower than the + // configured minimal thermal set-point RPM value. + + std::vector<float> values = {100, 200, 300, 400, 500}; + for (auto v : values) + { + zone->addRPMSetPoint(v); + } + + // This will pull the maximum RPM setpoint request. + zone->determineMaxRPMRequest(); + + // Verifies the value returned in the minimal thermal rpm set point. + EXPECT_EQ(zone->getMinThermalRpmSetPt(), zone->getMaxRPMRequest()); +} + +TEST_F(PidZoneTest, GetFailSafePercent_ReturnsExpected) { + // Verify the value used to create the object is stored. + EXPECT_EQ(failSafePercent, zone->getFailSafePercent()); +} + +TEST_F(PidZoneTest, ThermalInputs_FailsafeToValid_ReadsSensors) { + // This test will add a couple thermal inputs, and verify that the zone + // initializes into failsafe mode, and will read each sensor. + + std::string name1 = "temp1"; + int64_t timeout = 1; + + std::unique_ptr<Sensor> sensor1 = + std::make_unique<SensorMock>(name1, timeout); + SensorMock *sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); + + std::string name2 = "temp2"; + std::unique_ptr<Sensor> sensor2 = + std::make_unique<SensorMock>(name2, timeout); + SensorMock *sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); + + std::string type = "unchecked"; + mgr.addSensor(type, name1, std::move(sensor1)); + EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); + mgr.addSensor(type, name2, std::move(sensor2)); + EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); + + // Now that the sensors exist, add them to the zone. + zone->addThermalInput(name1); + zone->addThermalInput(name2); + + // Initialize Zone + zone->initializeCache(); + + // Verify now in failsafe mode. + EXPECT_TRUE(zone->getFailSafeMode()); + + ReadReturn r1; + r1.value = 10.0; + r1.updated = std::chrono::high_resolution_clock::now(); + EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); + + ReadReturn r2; + r2.value = 11.0; + r2.updated = std::chrono::high_resolution_clock::now(); + EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); + + // Read the sensors, this will put the values into the cache. + zone->updateSensors(); + + // We should no longer be in failsafe mode. + EXPECT_FALSE(zone->getFailSafeMode()); + + EXPECT_EQ(r1.value, zone->getCachedValue(name1)); + EXPECT_EQ(r2.value, zone->getCachedValue(name2)); +} + +TEST_F(PidZoneTest, FanInputTest_VerifiesFanValuesCached) { + // This will add a couple fan inputs, and verify the values are cached. + + std::string name1 = "fan1"; + int64_t timeout = 2; + + std::unique_ptr<Sensor> sensor1 = + std::make_unique<SensorMock>(name1, timeout); + SensorMock *sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); + + std::string name2 = "fan2"; + std::unique_ptr<Sensor> sensor2 = + std::make_unique<SensorMock>(name2, timeout); + SensorMock *sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); + + std::string type = "unchecked"; + mgr.addSensor(type, name1, std::move(sensor1)); + EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); + mgr.addSensor(type, name2, std::move(sensor2)); + EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); + + // Now that the sensors exist, add them to the zone. + zone->addFanInput(name1); + zone->addFanInput(name2); + + // Initialize Zone + zone->initializeCache(); + + ReadReturn r1; + r1.value = 10.0; + r1.updated = std::chrono::high_resolution_clock::now(); + EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); + + ReadReturn r2; + r2.value = 11.0; + r2.updated = std::chrono::high_resolution_clock::now(); + EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); + + // Method under test will read through each fan sensor for the zone and + // cache the values. + zone->updateFanTelemetry(); + + EXPECT_EQ(r1.value, zone->getCachedValue(name1)); + EXPECT_EQ(r2.value, zone->getCachedValue(name2)); +} + +TEST_F(PidZoneTest, ThermalInput_ValueTimeoutEntersFailSafeMode) { + // On the second updateSensors call, the updated timestamp will be beyond + // the timeout limit. + + int64_t timeout = 1; + + std::string name1 = "temp1"; + std::unique_ptr<Sensor> sensor1 = + std::make_unique<SensorMock>(name1, timeout); + SensorMock *sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); + + std::string name2 = "temp2"; + std::unique_ptr<Sensor> sensor2 = + std::make_unique<SensorMock>(name2, timeout); + SensorMock *sensor_ptr2 = reinterpret_cast<SensorMock*>(sensor2.get()); + + std::string type = "unchecked"; + mgr.addSensor(type, name1, std::move(sensor1)); + EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); + mgr.addSensor(type, name2, std::move(sensor2)); + EXPECT_EQ(mgr.getSensor(name2), sensor_ptr2); + + zone->addThermalInput(name1); + zone->addThermalInput(name2); + + // Initialize Zone + zone->initializeCache(); + + // Verify now in failsafe mode. + EXPECT_TRUE(zone->getFailSafeMode()); + + ReadReturn r1; + r1.value = 10.0; + r1.updated = std::chrono::high_resolution_clock::now(); + EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); + + ReadReturn r2; + r2.value = 11.0; + r2.updated = std::chrono::high_resolution_clock::now(); + EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); + + zone->updateSensors(); + EXPECT_FALSE(zone->getFailSafeMode()); + + // Ok, so we're not in failsafe mode, so let's set updated to the past. + // sensor1 will have an updated field older than its timeout value, but + // sensor2 will be fine. :D + r1.updated -= std::chrono::seconds(3); + r2.updated = std::chrono::high_resolution_clock::now(); + + EXPECT_CALL(*sensor_ptr1, read()).WillOnce(Return(r1)); + EXPECT_CALL(*sensor_ptr2, read()).WillOnce(Return(r2)); + + // Method under test will read each sensor. One sensor's value is older + // than the timeout for that sensor and this triggers failsafe mode. + zone->updateSensors(); + EXPECT_TRUE(zone->getFailSafeMode()); +} + +TEST_F(PidZoneTest, GetSensorTest_ReturnsExpected) { + // One can grab a sensor from the manager through the zone. + + int64_t timeout = 1; + + std::string name1 = "temp1"; + std::unique_ptr<Sensor> sensor1 = + std::make_unique<SensorMock>(name1, timeout); + SensorMock *sensor_ptr1 = reinterpret_cast<SensorMock*>(sensor1.get()); + + std::string type = "unchecked"; + mgr.addSensor(type, name1, std::move(sensor1)); + EXPECT_EQ(mgr.getSensor(name1), sensor_ptr1); + + zone->addThermalInput(name1); + + // Verify method under test returns the pointer we expect. + EXPECT_EQ(mgr.getSensor(name1), zone->getSensor(name1)); +} + +TEST_F(PidZoneTest, AddThermalPIDTest_VerifiesThermalPIDsProcessed) { + // Tests adding a thermal PID controller to the zone, and verifies it's + // touched during processing. + + std::unique_ptr<PIDController> tpid = + std::make_unique<ControllerMock>("thermal1", zone.get()); + ControllerMock *tmock = reinterpret_cast<ControllerMock*>(tpid.get()); + + // Access the internal pid configuration to clear it out (unrelated to the + // test). + ec::pid_info_t* info = tpid->get_pid_info(); + std::memset(info, 0x00, sizeof(ec::pid_info_t)); + + zone->addThermalPID(std::move(tpid)); + + EXPECT_CALL(*tmock, setpt_proc()).WillOnce(Return(10.0)); + EXPECT_CALL(*tmock, input_proc()).WillOnce(Return(11.0)); + EXPECT_CALL(*tmock, output_proc(_)); + + // Method under test will, for each thermal PID, call setpt, input, and + // output. + zone->process_thermals(); +} + +TEST_F(PidZoneTest, AddFanPIDTest_VerifiesFanPIDsProcessed) { + // Tests adding a fan PID controller to the zone, and verifies it's + // touched during processing. + + std::unique_ptr<PIDController> tpid = + std::make_unique<ControllerMock>("fan1", zone.get()); + ControllerMock *tmock = reinterpret_cast<ControllerMock*>(tpid.get()); + + // Access the internal pid configuration to clear it out (unrelated to the + // test). + ec::pid_info_t* info = tpid->get_pid_info(); + std::memset(info, 0x00, sizeof(ec::pid_info_t)); + + zone->addFanPID(std::move(tpid)); + + EXPECT_CALL(*tmock, setpt_proc()).WillOnce(Return(10.0)); + EXPECT_CALL(*tmock, input_proc()).WillOnce(Return(11.0)); + EXPECT_CALL(*tmock, output_proc(_)); + + // Method under test will, for each fan PID, call setpt, input, and output. + zone->process_fans(); +} + +TEST_F(PidZoneTest, ManualModeDbusTest_VerifySetManualBehavesAsExpected) { + // The manual(bool) method is inherited from the dbus mode interface. + + // Verifies that someone doesn't remove the internal call to the dbus + // object from which we're inheriting. + EXPECT_CALL(sdbus_mock_mode, + sd_bus_emit_properties_changed_strv(IsNull(), StrEq(objPath), + StrEq(modeInterface), + NotNull())) + .WillOnce(Invoke([&](sd_bus *bus, const char *path, + const char *interface, char **names) { + EXPECT_STREQ("Manual", names[0]); + return 0; + })); + + // Method under test will set the manual mode to true and broadcast this + // change on dbus. + zone->manual(true); + EXPECT_TRUE(zone->getManualMode()); +} + +TEST_F(PidZoneTest, FailsafeDbusTest_VerifiesReturnsExpected) { + // This property is implemented by us as read-only, such that trying to + // write to it will have no effect. + EXPECT_EQ(zone->failSafe(), zone->getFailSafeMode()); +} |