| Jagpal Singh Gill | acfdd55 | 2025-12-09 06:02:14 -0800 | [diff] [blame] | 1 | #include "common/events.hpp" |
| 2 | #include "device/device_factory.hpp" |
| 3 | #include "modbus_server_tester.hpp" |
| 4 | #include "port/base_port.hpp" |
| 5 | #include "test_base.hpp" |
| 6 | |
| 7 | #include <xyz/openbmc_project/Logging/Create/aserver.hpp> |
| 8 | #include <xyz/openbmc_project/Logging/Entry/aserver.hpp> |
| 9 | #include <xyz/openbmc_project/Sensor/Threshold/Critical/client.hpp> |
| 10 | #include <xyz/openbmc_project/Sensor/Value/client.hpp> |
| 11 | #include <xyz/openbmc_project/State/Decorator/Availability/client.hpp> |
| 12 | #include <xyz/openbmc_project/State/Decorator/OperationalStatus/client.hpp> |
| 13 | |
| 14 | #include <cmath> |
| 15 | #include <string> |
| 16 | |
| 17 | #include <gtest/gtest.h> |
| 18 | |
| 19 | using namespace std::literals; |
| 20 | using namespace testing; |
| 21 | using SensorValueIntf = |
| 22 | sdbusplus::client::xyz::openbmc_project::sensor::Value<>; |
| 23 | using OperationalStatusIntf = sdbusplus::client::xyz::openbmc_project::state:: |
| 24 | decorator::OperationalStatus<>; |
| 25 | using AvailabilityIntf = |
| 26 | sdbusplus::client::xyz::openbmc_project::state::decorator::Availability<>; |
| 27 | using ThresholdCriticalIntf = |
| 28 | sdbusplus::client::xyz::openbmc_project::sensor::threshold::Critical<>; |
| 29 | |
| 30 | class TestEventServer; |
| 31 | class TestEventEntry; |
| 32 | |
| 33 | using EventServerIntf = |
| 34 | sdbusplus::aserver::xyz::openbmc_project::logging::Create<TestEventServer>; |
| 35 | using EventEntryIntf = |
| 36 | sdbusplus::aserver::xyz::openbmc_project::logging::Entry<TestEventEntry>; |
| 37 | |
| 38 | namespace ModbusIntf = phosphor::modbus::rtu; |
| 39 | namespace PortIntf = phosphor::modbus::rtu::port; |
| 40 | namespace PortConfigIntf = PortIntf::config; |
| 41 | namespace DeviceIntf = phosphor::modbus::rtu::device; |
| 42 | namespace DeviceConfigIntf = DeviceIntf::config; |
| 43 | namespace EventIntf = phosphor::modbus::events; |
| 44 | |
| 45 | class MockPort : public PortIntf::BasePort |
| 46 | { |
| 47 | public: |
| 48 | MockPort(sdbusplus::async::context& ctx, |
| 49 | const PortConfigIntf::Config& config, |
| 50 | const std::string& devicePath) : BasePort(ctx, config, devicePath) |
| 51 | {} |
| 52 | }; |
| 53 | |
| 54 | // Test Event Class to mock the EventEntry |
| 55 | class TestEventEntry : public EventEntryIntf |
| 56 | { |
| 57 | public: |
| 58 | TestEventEntry(sdbusplus::async::context& ctx, const char* path) : |
| 59 | EventEntryIntf(ctx, path) |
| 60 | {} |
| 61 | |
| 62 | auto method_call(get_entry_t) |
| 63 | -> sdbusplus::async::task<get_entry_t::return_type> |
| 64 | { |
| 65 | get_entry_t::return_type fd1 = 0; |
| 66 | co_return fd1; |
| 67 | } |
| 68 | }; |
| 69 | |
| 70 | // Test Event Server Class to mock the EventServer |
| 71 | class TestEventServer : public EventServerIntf |
| 72 | { |
| 73 | public: |
| 74 | TestEventServer(sdbusplus::async::context& ctx, const char* path) : |
| 75 | EventServerIntf(ctx, path), ctx(ctx) |
| 76 | {} |
| 77 | |
| 78 | auto method_call(create_t, auto message, auto, auto) |
| 79 | -> sdbusplus::async::task<create_t::return_type> |
| 80 | |
| 81 | { |
| 82 | static int cnt = 100; |
| 83 | cnt++; |
| 84 | |
| 85 | // Append the count to the object path to make it unique for each event |
| 86 | std::string objectPath = |
| 87 | "/xyz/openbmc_project/logging/entry/TestEvent" + |
| 88 | std::to_string(cnt); |
| 89 | EXPECT_EQ(message, expectedEvent) << "Event name mismatch"; |
| 90 | |
| 91 | eventEntries.emplace_back( |
| 92 | std::make_unique<TestEventEntry>(ctx, objectPath.c_str())); |
| 93 | |
| 94 | co_return sdbusplus::message::object_path(objectPath); |
| 95 | } |
| 96 | |
| 97 | auto method_call(create_with_ffdc_files_t, auto, auto, auto, auto) |
| 98 | -> sdbusplus::async::task<create_with_ffdc_files_t::return_type> |
| 99 | |
| 100 | { |
| 101 | co_return; |
| 102 | } |
| 103 | |
| 104 | std::string expectedEvent; |
| 105 | |
| 106 | private: |
| 107 | sdbusplus::async::context& ctx; |
| 108 | std::vector<std::unique_ptr<TestEventEntry>> eventEntries; |
| 109 | }; |
| 110 | |
| 111 | class DeviceEventsTest : public BaseTest |
| 112 | { |
| 113 | public: |
| 114 | PortConfigIntf::Config portConfig; |
| 115 | static constexpr const char* clientDevicePath = |
| 116 | "/tmp/ttyDeviceEventsTestPort0"; |
| 117 | static constexpr const char* serverDevicePath = |
| 118 | "/tmp/ttyDeviceEventsTestPort1"; |
| 119 | static constexpr auto portName = "TestPort0"; |
| 120 | std::string deviceName; |
| 121 | std::string fullSensorName; |
| 122 | std::string objectPath; |
| 123 | const char* loggingObjectPath = "/xyz/openbmc_project/logging"; |
| 124 | static constexpr auto serviceName = "xyz.openbmc_project.Logging"; |
| 125 | static constexpr auto sensorName = "OutletTemperature"; |
| 126 | TestEventServer eventServer; |
| 127 | sdbusplus::server::manager_t manager; |
| 128 | |
| 129 | DeviceEventsTest() : |
| 130 | BaseTest(clientDevicePath, serverDevicePath, serviceName), |
| 131 | eventServer(ctx, loggingObjectPath), manager(ctx, loggingObjectPath) |
| 132 | { |
| 133 | portConfig.name = portName; |
| 134 | portConfig.portMode = PortConfigIntf::PortMode::rs485; |
| 135 | portConfig.baudRate = baudRate; |
| 136 | portConfig.rtsDelay = 1; |
| 137 | |
| 138 | deviceName = std::format("ResorviorPumpUnit_{}_{}", |
| 139 | TestIntf::testDeviceAddress, portName); |
| 140 | |
| 141 | fullSensorName = std::format("{}_{}", deviceName, sensorName); |
| 142 | |
| 143 | objectPath = std::format( |
| 144 | "{}/{}/{}", SensorValueIntf::namespace_path::value, |
| 145 | SensorValueIntf::namespace_path::temperature, fullSensorName); |
| 146 | } |
| 147 | |
| 148 | auto verifyValue(bool currentValue, bool expectedValue, |
| 149 | const std::string& failureStr) -> void |
| 150 | { |
| 151 | EXPECT_EQ(currentValue, expectedValue) << failureStr; |
| 152 | } |
| 153 | |
| 154 | auto verifyValue(double currentValue, double expectedValue, |
| 155 | const std::string& failureStr) -> void |
| 156 | { |
| 157 | EXPECT_EQ(currentValue, expectedValue) << failureStr; |
| 158 | } |
| 159 | |
| 160 | auto verifyResult( |
| 161 | SensorValueIntf::properties_t& properties, |
| 162 | OperationalStatusIntf::properties_t& operationalProperties, |
| 163 | AvailabilityIntf::properties_t& availabilityProperties, |
| 164 | ThresholdCriticalIntf::properties_t& thresholdProperties, |
| 165 | double expectedValue, SensorValueIntf::Unit expectedUnit) -> void |
| 166 | { |
| 167 | if (std::isnan(expectedValue)) |
| 168 | { |
| 169 | EXPECT_TRUE(std::isnan(properties.value)) |
| 170 | << "Sensor value should be Nan"; |
| 171 | verifyValue(operationalProperties.functional, false, |
| 172 | "Operational status mismatch"); |
| 173 | verifyValue(availabilityProperties.available, false, |
| 174 | "Availability mismatch"); |
| 175 | verifyValue(thresholdProperties.critical_alarm_high, false, |
| 176 | "Critical Alarm mismatch"); |
| 177 | } |
| 178 | else |
| 179 | { |
| 180 | verifyValue(properties.value, expectedValue, |
| 181 | "Sensor value mismatch"); |
| 182 | verifyValue(operationalProperties.functional, true, |
| 183 | "Operational status mismatch"); |
| 184 | verifyValue(availabilityProperties.available, true, |
| 185 | "Availability mismatch"); |
| 186 | verifyValue(thresholdProperties.critical_alarm_high, true, |
| 187 | "Critical Alarm mismatch"); |
| 188 | } |
| 189 | |
| 190 | EXPECT_EQ(properties.unit, expectedUnit) << "Sensor unit mismatch"; |
| 191 | EXPECT_TRUE(std::isnan(properties.min_value)) << "Min value mismatch"; |
| 192 | EXPECT_TRUE(std::isnan(properties.max_value)) << "Max value mismatch"; |
| 193 | } |
| 194 | |
| 195 | auto testSensorCreation(std::string objectPath, |
| 196 | DeviceConfigIntf::StatusType statusType, |
| 197 | double expectedValue) |
| 198 | -> sdbusplus::async::task<void> |
| 199 | { |
| 200 | DeviceConfigIntf::StatusBit statusBit = { |
| 201 | .name = sensorName, |
| 202 | .type = statusType, |
| 203 | .bitPosition = 0, |
| 204 | .value = true}; |
| 205 | DeviceConfigIntf::Config::status_registers_t statusRegisters = { |
| 206 | {TestIntf::testReadHoldingRegisterEventOffset, {statusBit}}}; |
| 207 | DeviceConfigIntf::Config::sensor_registers_t sensorRegisters = {{ |
| 208 | .name = sensorName, |
| 209 | .pathSuffix = SensorValueIntf::namespace_path::temperature, |
| 210 | .unit = SensorValueIntf::Unit::DegreesC, |
| 211 | .offset = TestIntf::testReadHoldingRegisterTempUnsignedOffset, |
| 212 | .size = TestIntf::testReadHoldingRegisterTempCount, |
| 213 | .format = DeviceConfigIntf::SensorFormat::floatingPoint, |
| 214 | }}; |
| 215 | DeviceConfigIntf::DeviceFactoryConfig deviceFactoryConfig = { |
| 216 | { |
| 217 | .address = TestIntf::testDeviceAddress, |
| 218 | .parity = ModbusIntf::Parity::none, |
| 219 | .baudRate = baudRate, |
| 220 | .name = deviceName, |
| 221 | .portName = portConfig.name, |
| 222 | .inventoryPath = sdbusplus::message::object_path( |
| 223 | "xyz/openbmc_project/Inventory/ResorviorPumpUnit"), |
| 224 | .sensorRegisters = sensorRegisters, |
| 225 | .statusRegisters = statusRegisters, |
| 226 | .firmwareRegisters = {}, |
| 227 | }, |
| 228 | DeviceConfigIntf::DeviceType::reservoirPumpUnit, |
| 229 | DeviceConfigIntf::DeviceModel::RDF040DSS5193E0, |
| 230 | }; |
| 231 | EventIntf::Events events{ctx}; |
| 232 | MockPort mockPort(ctx, portConfig, clientDevicePath); |
| 233 | auto device = DeviceIntf::DeviceFactory::create( |
| 234 | ctx, deviceFactoryConfig, mockPort, events); |
| 235 | co_await device->readSensorRegisters(); |
| 236 | auto properties = co_await SensorValueIntf(ctx) |
| 237 | .service(serviceName) |
| 238 | .path(objectPath) |
| 239 | .properties(); |
| 240 | auto operationalProperties = |
| 241 | co_await OperationalStatusIntf(ctx) |
| 242 | .service(serviceName) |
| 243 | .path(objectPath) |
| 244 | .properties(); |
| 245 | auto availabilityProperties = |
| 246 | co_await AvailabilityIntf(ctx) |
| 247 | .service(serviceName) |
| 248 | .path(objectPath) |
| 249 | .properties(); |
| 250 | auto thresholdProperties = |
| 251 | co_await ThresholdCriticalIntf(ctx) |
| 252 | .service(serviceName) |
| 253 | .path(objectPath) |
| 254 | .properties(); |
| 255 | verifyResult(properties, operationalProperties, availabilityProperties, |
| 256 | thresholdProperties, expectedValue, |
| 257 | sensorRegisters[0].unit); |
| 258 | co_return; |
| 259 | } |
| 260 | }; |
| 261 | |
| 262 | TEST_F(DeviceEventsTest, TestSensorReadingCritical) |
| 263 | { |
| 264 | eventServer.expectedEvent = |
| 265 | "xyz.openbmc_project.Sensor.Threshold.ReadingCritical"; |
| 266 | |
| 267 | ctx.spawn(testSensorCreation( |
| 268 | objectPath, DeviceConfigIntf::StatusType::sensorReadingCritical, |
| 269 | TestIntf::testReadHoldingRegisterTempUnsigned[0])); |
| 270 | |
| 271 | ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | |
| 272 | sdbusplus::async::execution::then([&]() { ctx.request_stop(); })); |
| 273 | |
| 274 | ctx.run(); |
| 275 | } |
| 276 | |
| 277 | TEST_F(DeviceEventsTest, TestSensorFailure) |
| 278 | { |
| 279 | eventServer.expectedEvent = "xyz.openbmc_project.Sensor.SensorFailure"; |
| 280 | |
| 281 | ctx.spawn(testSensorCreation(objectPath, |
| 282 | DeviceConfigIntf::StatusType::sensorFailure, |
| 283 | std::numeric_limits<double>::quiet_NaN())); |
| 284 | |
| 285 | ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | |
| 286 | sdbusplus::async::execution::then([&]() { ctx.request_stop(); })); |
| 287 | |
| 288 | ctx.run(); |
| 289 | } |