1 #include "modbus/modbus.hpp"
2 #include "modbus_server_tester.hpp"
3
4 #include <fcntl.h>
5
6 #include <gmock/gmock.h>
7 #include <gtest/gtest.h>
8
9 using namespace std::literals;
10
11 namespace RTUIntf = phosphor::modbus::rtu;
12 using ModbusIntf = RTUIntf::Modbus;
13 namespace TestIntf = phosphor::modbus::test;
14
15 class ModbusTest : public ::testing::Test
16 {
17 public:
18 static constexpr const char* clientDevicePath = "/tmp/ttyV0";
19 static constexpr const char* serverDevicePath = "/tmp/ttyV1";
20 static constexpr const auto defaultBaudeRate = "b115200";
21 int socat_pid = -1;
22 sdbusplus::async::context ctx;
23 std::unique_ptr<ModbusIntf> modbus;
24 int fdClient = -1;
25 std::unique_ptr<TestIntf::ServerTester> serverTester;
26 int fdServer = -1;
27
ModbusTest()28 ModbusTest()
29 {
30 std::string socatCmd = std::format(
31 "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
32 serverDevicePath, defaultBaudeRate, clientDevicePath,
33 defaultBaudeRate);
34
35 // Start socat in the background and capture its PID
36 FILE* fp = popen(socatCmd.c_str(), "r");
37 EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
38 EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
39 pclose(fp);
40
41 // Wait for socat to start up
42 sleep(1);
43
44 fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
45 EXPECT_NE(fdClient, -1)
46 << "Failed to open serial port " << clientDevicePath
47 << " with error: " << strerror(errno);
48
49 modbus = std::make_unique<ModbusIntf>(ctx, fdClient, 115200, 0);
50
51 fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
52 EXPECT_NE(fdServer, -1)
53 << "Failed to open serial port " << serverDevicePath
54 << " with error: " << strerror(errno);
55
56 serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
57 }
58
~ModbusTest()59 ~ModbusTest() noexcept override
60 {
61 if (fdClient != -1)
62 {
63 close(fdClient);
64 fdClient = -1;
65 }
66 if (fdServer != -1)
67 {
68 close(fdServer);
69 fdServer = -1;
70 }
71 kill(socat_pid, SIGTERM);
72 }
73
SetUp()74 void SetUp() override
75 {
76 ctx.spawn(serverTester->processRequests());
77 }
78
TestHoldingRegisters(uint16_t registerOffset,bool res)79 auto TestHoldingRegisters(uint16_t registerOffset, bool res)
80 -> sdbusplus::async::task<void>
81 {
82 std::cout << "TestHoldingRegisters() start" << std::endl;
83
84 std::vector<uint16_t> registers(
85 TestIntf::testSuccessReadHoldingRegisterCount);
86
87 auto ret = co_await modbus->readHoldingRegisters(
88 TestIntf::testDeviceAddress, registerOffset, registers);
89
90 EXPECT_EQ(ret, res) << "Failed to read holding registers";
91
92 if (!res)
93 {
94 co_return;
95 }
96
97 for (auto i = 0; i < TestIntf::testSuccessReadHoldingRegisterCount; i++)
98 {
99 EXPECT_EQ(registers[i],
100 TestIntf::testSuccessReadHoldingRegisterResponse[i]);
101 }
102
103 co_return;
104 }
105 };
106
TEST_F(ModbusTest,TestReadHoldingRegisterSuccess)107 TEST_F(ModbusTest, TestReadHoldingRegisterSuccess)
108 {
109 ctx.spawn(TestHoldingRegisters(
110 TestIntf::testSuccessReadHoldingRegisterOffset, true));
111
112 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
113 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
114
115 ctx.run();
116 }
117
TEST_F(ModbusTest,TestReadHoldingRegisterSegmentedSuccess)118 TEST_F(ModbusTest, TestReadHoldingRegisterSegmentedSuccess)
119 {
120 ctx.spawn(TestHoldingRegisters(
121 TestIntf::testSuccessReadHoldingRegisterSegmentedOffset, true));
122
123 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
124 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
125
126 ctx.run();
127 }
128
TEST_F(ModbusTest,TestReadHoldingRegisterFailure)129 TEST_F(ModbusTest, TestReadHoldingRegisterFailure)
130 {
131 ctx.spawn(
132 TestHoldingRegisters(TestIntf::testFailureReadHoldingRegister, false));
133
134 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
135 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
136
137 ctx.run();
138 }
139