#include "modbus/modbus.hpp" #include "modbus_server_tester.hpp" #include #include #include using namespace std::literals; namespace RTUIntf = phosphor::modbus::rtu; using ModbusIntf = RTUIntf::Modbus; namespace TestIntf = phosphor::modbus::test; class ModbusTest : public ::testing::Test { public: static constexpr const char* clientDevicePath = "/tmp/ttyV0"; static constexpr const char* serverDevicePath = "/tmp/ttyV1"; static constexpr const auto defaultBaudeRate = "b115200"; int socat_pid = -1; sdbusplus::async::context ctx; std::unique_ptr modbus; int fdClient = -1; std::unique_ptr serverTester; int fdServer = -1; ModbusTest() { std::string socatCmd = std::format( "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!", serverDevicePath, defaultBaudeRate, clientDevicePath, defaultBaudeRate); // Start socat in the background and capture its PID FILE* fp = popen(socatCmd.c_str(), "r"); EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno); EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0); pclose(fp); // Wait for socat to start up sleep(1); fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); EXPECT_NE(fdClient, -1) << "Failed to open serial port " << clientDevicePath << " with error: " << strerror(errno); modbus = std::make_unique(ctx, fdClient, 115200, 0); fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); EXPECT_NE(fdServer, -1) << "Failed to open serial port " << serverDevicePath << " with error: " << strerror(errno); serverTester = std::make_unique(ctx, fdServer); } ~ModbusTest() noexcept override { if (fdClient != -1) { close(fdClient); fdClient = -1; } if (fdServer != -1) { close(fdServer); fdServer = -1; } kill(socat_pid, SIGTERM); } void SetUp() override { ctx.spawn(serverTester->processRequests()); } auto TestHoldingRegisters(uint16_t registerOffset, bool res) -> sdbusplus::async::task { std::cout << "TestHoldingRegisters() start" << std::endl; std::vector registers( TestIntf::testSuccessReadHoldingRegisterCount); auto ret = co_await modbus->readHoldingRegisters( TestIntf::testDeviceAddress, registerOffset, registers); EXPECT_EQ(ret, res) << "Failed to read holding registers"; if (!res) { co_return; } for (auto i = 0; i < TestIntf::testSuccessReadHoldingRegisterCount; i++) { EXPECT_EQ(registers[i], TestIntf::testSuccessReadHoldingRegisterResponse[i]); } co_return; } }; TEST_F(ModbusTest, TestReadHoldingRegisterSuccess) { ctx.spawn(TestHoldingRegisters( TestIntf::testSuccessReadHoldingRegisterOffset, true)); ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | sdbusplus::async::execution::then([&]() { ctx.request_stop(); })); ctx.run(); } TEST_F(ModbusTest, TestReadHoldingRegisterSegmentedSuccess) { ctx.spawn(TestHoldingRegisters( TestIntf::testSuccessReadHoldingRegisterSegmentedOffset, true)); ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | sdbusplus::async::execution::then([&]() { ctx.request_stop(); })); ctx.run(); } TEST_F(ModbusTest, TestReadHoldingRegisterFailure) { ctx.spawn( TestHoldingRegisters(TestIntf::testFailureReadHoldingRegister, false)); ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) | sdbusplus::async::execution::then([&]() { ctx.request_stop(); })); ctx.run(); }