xref: /openbmc/phosphor-modbus/tests/test_port.cpp (revision 7f9d41ddfad8c74d51fa1cf4a591b7f085254396)
1 #include "modbus_server_tester.hpp"
2 #include "port/base_port.hpp"
3 
4 #include <fcntl.h>
5 
6 #include <gtest/gtest.h>
7 
8 using namespace std::literals;
9 
10 namespace TestIntf = phosphor::modbus::test;
11 namespace PortIntf = phosphor::modbus::rtu::port;
12 namespace PortConfigIntf = PortIntf::config;
13 namespace RTUIntf = phosphor::modbus::rtu;
14 
15 struct properties_t
16 {
17     std::string name = {};
18     std::string mode = {};
19     uint64_t baud_rate = {};
20     uint64_t rts_delay = {};
21 };
22 
23 class 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 
32 class 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 
117 TEST_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 
129 TEST_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 
148 TEST_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 }
166