#include "inventory/modbus_inventory.hpp" #include "modbus_server_tester.hpp" #include "port/base_port.hpp" #include #include #include using namespace std::literals; using namespace testing; using InventorySourceIntf = sdbusplus::client::xyz::openbmc_project::inventory::source::modbus::FRU<>; namespace TestIntf = phosphor::modbus::test; namespace ModbusIntf = phosphor::modbus::rtu; namespace PortIntf = phosphor::modbus::rtu::port; namespace PortConfigIntf = PortIntf::config; namespace InventoryIntf = phosphor::modbus::rtu::inventory; namespace InventoryConfigIntf = InventoryIntf::config; class MockPort : public PortIntf::BasePort { public: MockPort(sdbusplus::async::context& ctx, const PortConfigIntf::Config& config, const std::string& devicePath) : BasePort(ctx, config, devicePath) {} }; class InventoryTest : public ::testing::Test { public: PortConfigIntf::Config portConfig; static constexpr const char* clientDevicePath = "/tmp/ttyInventoryTestPort0"; static constexpr const char* serverDevicePath = "/tmp/ttyInventoryTestPort1"; static constexpr const auto defaultBaudeRate = "b115200"; static constexpr const auto deviceName = "Test1"; static constexpr auto serviceName = "xyz.openbmc_project.TestModbusRTU"; int socat_pid = -1; sdbusplus::async::context ctx; int fdClient = -1; std::unique_ptr serverTester; int fdServer = -1; InventoryTest() { portConfig.name = "TestPort1"; portConfig.portMode = PortConfigIntf::PortMode::rs485; portConfig.baudRate = 115200; portConfig.rtsDelay = 1; std::string socatCmd = std::format( "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!", serverDevicePath, defaultBaudeRate, clientDevicePath, defaultBaudeRate); // Start socat in the background and capture its PID FILE* fp = popen(socatCmd.c_str(), "r"); EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno); EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0); pclose(fp); // Wait for socat to start up sleep(1); fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); EXPECT_NE(fdClient, -1) << "Failed to open serial port " << clientDevicePath << " with error: " << strerror(errno); fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); EXPECT_NE(fdServer, -1) << "Failed to open serial port " << serverDevicePath << " with error: " << strerror(errno); ctx.request_name(serviceName); serverTester = std::make_unique(ctx, fdServer); } ~InventoryTest() noexcept override { if (fdClient != -1) { close(fdClient); fdClient = -1; } if (fdServer != -1) { close(fdServer); fdServer = -1; } kill(socat_pid, SIGTERM); } auto testInventorySourceCreation(std::string objPath) -> sdbusplus::async::task { InventoryConfigIntf::Config::port_address_map_t addressMap; addressMap[portConfig.name] = {{.start = TestIntf::testDeviceAddress, .end = TestIntf::testDeviceAddress}}; InventoryConfigIntf::Config deviceConfig = { .name = deviceName, .addressMap = addressMap, .registers = {{"Model", TestIntf::testReadHoldingRegisterModelOffset, TestIntf::testReadHoldingRegisterModelCount}}, .parity = ModbusIntf::Parity::none, .baudRate = 115200}; InventoryIntf::Device::serial_port_map_t ports; ports[portConfig.name] = std::make_unique(ctx, portConfig, clientDevicePath); auto inventoryDevice = std::make_unique(ctx, deviceConfig, ports); co_await inventoryDevice->probePorts(); // Create InventorySource client interface to read back D-Bus properties auto properties = co_await InventorySourceIntf(ctx) .service(serviceName) .path(objPath) .properties(); constexpr auto defaultInventoryValue = "Unknown"; EXPECT_EQ(properties.name, std::format("{} {} {}", deviceName, TestIntf::testDeviceAddress, portConfig.name)) << "Name mismatch"; EXPECT_EQ(properties.address, TestIntf::testDeviceAddress) << "Address mismatch"; EXPECT_EQ(properties.link_tty, portConfig.name) << "Link TTY mismatch"; EXPECT_EQ(properties.model, TestIntf::testReadHoldingRegisterModelStr) << "Model mismatch"; EXPECT_EQ(properties.serial_number, defaultInventoryValue) << "Part Number mismatch"; co_return; } void SetUp() override { // Process request for probe device call ctx.spawn(serverTester->processRequests()); // Process request to read `Model` holding register call ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | sdbusplus::async::execution::then([&]() { ctx.spawn(serverTester->processRequests()); })); } }; TEST_F(InventoryTest, TestAddInventorySource) { auto objPath = std::format("{}/{}_{}_{}", InventorySourceIntf::namespace_path, deviceName, TestIntf::testDeviceAddress, portConfig.name); ctx.spawn(testInventorySourceCreation(objPath)); ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | sdbusplus::async::execution::then([&]() { ctx.request_stop(); })); ctx.run(); }