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