blob: 32b9b49e5ec462cbe4528b51c734404fe1ad10cd [file] [log] [blame]
Jagpal Singh Gill7f9d41d2025-10-16 09:42:18 -07001#include "modbus_server_tester.hpp"
2#include "port/base_port.hpp"
3
4#include <fcntl.h>
5
6#include <gtest/gtest.h>
7
8using namespace std::literals;
9
10namespace TestIntf = phosphor::modbus::test;
11namespace PortIntf = phosphor::modbus::rtu::port;
12namespace PortConfigIntf = PortIntf::config;
13namespace RTUIntf = phosphor::modbus::rtu;
14
15struct properties_t
16{
17 std::string name = {};
18 std::string mode = {};
19 uint64_t baud_rate = {};
20 uint64_t rts_delay = {};
21};
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 PortTest : public ::testing::Test
33{
34 public:
35 static constexpr properties_t properties = {"TestPort", "RS485", 115200, 1};
36 static constexpr const char* clientDevicePath = "/tmp/ttyPortV0";
37 static constexpr const char* serverDevicePath = "/tmp/ttyPortV1";
38 static constexpr const auto defaultBaudeRate = "b115200";
39 int socat_pid = -1;
40 sdbusplus::async::context ctx;
41 int fdClient = -1;
42 std::unique_ptr<TestIntf::ServerTester> serverTester;
43 int fdServer = -1;
44
45 PortTest()
46 {
47 std::string socatCmd = std::format(
48 "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
49 serverDevicePath, defaultBaudeRate, clientDevicePath,
50 defaultBaudeRate);
51
52 // Start socat in the background and capture its PID
53 FILE* fp = popen(socatCmd.c_str(), "r");
54 EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
55 EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
56 pclose(fp);
57
58 // Wait for socat to start up
59 sleep(1);
60
61 fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
62 EXPECT_NE(fdClient, -1)
63 << "Failed to open serial port " << clientDevicePath
64 << " with error: " << strerror(errno);
65
66 fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
67 EXPECT_NE(fdServer, -1)
68 << "Failed to open serial port " << serverDevicePath
69 << " with error: " << strerror(errno);
70
71 serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
72 }
73
74 ~PortTest() noexcept override
75 {
76 if (fdClient != -1)
77 {
78 close(fdClient);
79 fdClient = -1;
80 }
81 if (fdServer != -1)
82 {
83 close(fdServer);
84 fdServer = -1;
85 }
86 kill(socat_pid, SIGTERM);
87 }
88
89 auto TestHoldingRegisters(PortConfigIntf::Config& config, MockPort& port,
90 uint16_t registerOffset, bool res)
91 -> sdbusplus::async::task<void>
92 {
93 std::vector<uint16_t> registers(
94 TestIntf::testSuccessReadHoldingRegisterCount);
95
96 auto ret = co_await port.readHoldingRegisters(
97 TestIntf::testDeviceAddress, registerOffset, config.baudRate,
98 RTUIntf::Parity::none, registers);
99
100 EXPECT_EQ(ret, res) << "Failed to read holding registers";
101
102 if (!res)
103 {
104 co_return;
105 }
106
107 for (auto i = 0; i < TestIntf::testSuccessReadHoldingRegisterCount; i++)
108 {
109 EXPECT_EQ(registers[i],
110 TestIntf::testSuccessReadHoldingRegisterResponse[i]);
111 }
112
113 co_return;
114 }
115};
116
117TEST_F(PortTest, TestUpdateConfig)
118{
119 PortConfigIntf::Config config = {};
120 auto res = PortConfigIntf::updateBaseConfig(config, properties);
121 EXPECT_TRUE(res) << "Failed to update config";
122
123 EXPECT_EQ(config.name, properties.name);
124 EXPECT_EQ(config.portMode, PortConfigIntf::PortMode::rs485);
125 EXPECT_EQ(config.baudRate, properties.baud_rate);
126 EXPECT_EQ(config.rtsDelay, properties.rts_delay);
127}
128
129TEST_F(PortTest, TestReadHoldingRegisterSuccess)
130{
131 PortConfigIntf::Config config = {};
132 auto res = PortConfigIntf::updateBaseConfig(config, properties);
133 EXPECT_TRUE(res) << "Failed to update config";
134
135 MockPort port(ctx, config, clientDevicePath);
136
137 ctx.spawn(serverTester->processRequests());
138
139 ctx.spawn(TestHoldingRegisters(
140 config, port, TestIntf::testSuccessReadHoldingRegisterOffset, true));
141
142 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
143 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
144
145 ctx.run();
146}
147
148TEST_F(PortTest, TestReadHoldingRegisterFailure)
149{
150 PortConfigIntf::Config config = {};
151 auto res = PortConfigIntf::updateBaseConfig(config, properties);
152 EXPECT_TRUE(res) << "Failed to update config";
153
154 MockPort port(ctx, config, clientDevicePath);
155
156 ctx.spawn(serverTester->processRequests());
157
158 ctx.spawn(TestHoldingRegisters(
159 config, port, TestIntf::testFailureReadHoldingRegister, false));
160
161 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
162 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
163
164 ctx.run();
165}