blob: b54b3a741d5dc4cb94970c39d7c73ff713f0bb2d [file] [log] [blame]
Jagpal Singh Gillcad9ecf2025-10-22 19:53:16 -07001#include "inventory/modbus_inventory.hpp"
2#include "modbus_server_tester.hpp"
3#include "port/base_port.hpp"
4
5#include <fcntl.h>
6
7#include <xyz/openbmc_project/Inventory/Source/Modbus/FRU/client.hpp>
8
9#include <gtest/gtest.h>
10
11using namespace std::literals;
12using namespace testing;
13using InventorySourceIntf =
14 sdbusplus::client::xyz::openbmc_project::inventory::source::modbus::FRU<>;
15
16namespace TestIntf = phosphor::modbus::test;
17namespace ModbusIntf = phosphor::modbus::rtu;
18namespace PortIntf = phosphor::modbus::rtu::port;
19namespace PortConfigIntf = PortIntf::config;
20namespace InventoryIntf = phosphor::modbus::rtu::inventory;
21namespace InventoryConfigIntf = InventoryIntf::config;
22
23class MockPort : public PortIntf::BasePort
24{
25 public:
26 MockPort(sdbusplus::async::context& ctx,
27 const PortConfigIntf::Config& config,
28 const std::string& devicePath) : BasePort(ctx, config, devicePath)
29 {}
30};
31
32class InventoryTest : public ::testing::Test
33{
34 public:
35 PortConfigIntf::Config portConfig;
36 static constexpr const char* clientDevicePath =
37 "/tmp/ttyInventoryTestPort0";
38 static constexpr const char* serverDevicePath =
39 "/tmp/ttyInventoryTestPort1";
40 static constexpr const auto defaultBaudeRate = "b115200";
41 static constexpr const auto deviceName = "Test1";
42 static constexpr auto serviceName = "xyz.openbmc_project.TestModbusRTU";
43 int socat_pid = -1;
44 sdbusplus::async::context ctx;
45 int fdClient = -1;
46 std::unique_ptr<TestIntf::ServerTester> serverTester;
47 int fdServer = -1;
48
49 InventoryTest()
50 {
51 portConfig.name = "TestPort1";
52 portConfig.portMode = PortConfigIntf::PortMode::rs485;
53 portConfig.baudRate = 115200;
54 portConfig.rtsDelay = 1;
55
56 std::string socatCmd = std::format(
57 "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
58 serverDevicePath, defaultBaudeRate, clientDevicePath,
59 defaultBaudeRate);
60
61 // Start socat in the background and capture its PID
62 FILE* fp = popen(socatCmd.c_str(), "r");
63 EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
64 EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
65 pclose(fp);
66
67 // Wait for socat to start up
68 sleep(1);
69
70 fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
71 EXPECT_NE(fdClient, -1)
72 << "Failed to open serial port " << clientDevicePath
73 << " with error: " << strerror(errno);
74
75 fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
76 EXPECT_NE(fdServer, -1)
77 << "Failed to open serial port " << serverDevicePath
78 << " with error: " << strerror(errno);
79
80 ctx.request_name(serviceName);
81
82 serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
83 }
84
85 ~InventoryTest() noexcept override
86 {
87 if (fdClient != -1)
88 {
89 close(fdClient);
90 fdClient = -1;
91 }
92 if (fdServer != -1)
93 {
94 close(fdServer);
95 fdServer = -1;
96 }
97 kill(socat_pid, SIGTERM);
98 }
99
100 auto testInventorySourceCreation(std::string objPath)
101 -> sdbusplus::async::task<void>
102 {
103 InventoryConfigIntf::Config::port_address_map_t addressMap;
104 addressMap[portConfig.name] = {{.start = TestIntf::testDeviceAddress,
105 .end = TestIntf::testDeviceAddress}};
106 InventoryConfigIntf::Config deviceConfig = {
107 .name = deviceName,
108 .addressMap = addressMap,
109 .registers = {{"Model",
110 TestIntf::testReadHoldingRegisterModelOffset,
111 TestIntf::testReadHoldingRegisterModelCount}},
112 .parity = ModbusIntf::Parity::none,
113 .baudRate = 115200};
114 InventoryIntf::Device::serial_port_map_t ports;
115 ports[portConfig.name] =
116 std::make_unique<MockPort>(ctx, portConfig, clientDevicePath);
117
118 auto inventoryDevice =
119 std::make_unique<InventoryIntf::Device>(ctx, deviceConfig, ports);
120
121 co_await inventoryDevice->probePorts();
122
123 // Create InventorySource client interface to read back D-Bus properties
124 auto properties = co_await InventorySourceIntf(ctx)
125 .service(serviceName)
126 .path(objPath)
127 .properties();
128
129 constexpr auto defaultInventoryValue = "Unknown";
130
131 EXPECT_EQ(properties.name,
132 std::format("{} {} {}", deviceName,
133 TestIntf::testDeviceAddress, portConfig.name))
134 << "Name mismatch";
135 EXPECT_EQ(properties.address, TestIntf::testDeviceAddress)
136 << "Address mismatch";
137 EXPECT_EQ(properties.link_tty, portConfig.name) << "Link TTY mismatch";
138 EXPECT_EQ(properties.model, TestIntf::testReadHoldingRegisterModelStr)
139 << "Model mismatch";
140 EXPECT_EQ(properties.serial_number, defaultInventoryValue)
141 << "Part Number mismatch";
142
143 co_return;
144 }
145
146 void SetUp() override
147 {
148 // Process request for probe device call
149 ctx.spawn(serverTester->processRequests());
150
151 // Process request to read `Model` holding register call
152 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
153 sdbusplus::async::execution::then([&]() {
154 ctx.spawn(serverTester->processRequests());
155 }));
156 }
157};
158
159TEST_F(InventoryTest, TestAddInventorySource)
160{
161 auto objPath =
162 std::format("{}/{}_{}_{}", InventorySourceIntf::namespace_path,
163 deviceName, TestIntf::testDeviceAddress, portConfig.name);
164
165 ctx.spawn(testInventorySourceCreation(objPath));
166
167 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
168 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
169
170 ctx.run();
171}