xref: /openbmc/phosphor-modbus/tests/test_inventory.cpp (revision cad9ecf69472f03f9ece64eff5d2d94bc51bcf90)
1 #include "inventory/modbus_inventory.hpp"
2 #include "modbus_server_tester.hpp"
3 #include "port/base_port.hpp"
4 
5 #include <fcntl.h>
6 
7 #include <xyz/openbmc_project/Inventory/Source/Modbus/FRU/client.hpp>
8 
9 #include <gtest/gtest.h>
10 
11 using namespace std::literals;
12 using namespace testing;
13 using InventorySourceIntf =
14     sdbusplus::client::xyz::openbmc_project::inventory::source::modbus::FRU<>;
15 
16 namespace TestIntf = phosphor::modbus::test;
17 namespace ModbusIntf = phosphor::modbus::rtu;
18 namespace PortIntf = phosphor::modbus::rtu::port;
19 namespace PortConfigIntf = PortIntf::config;
20 namespace InventoryIntf = phosphor::modbus::rtu::inventory;
21 namespace InventoryConfigIntf = InventoryIntf::config;
22 
23 class MockPort : public PortIntf::BasePort
24 {
25   public:
MockPort(sdbusplus::async::context & ctx,const PortConfigIntf::Config & config,const std::string & devicePath)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 InventoryTest : public ::testing::Test
33 {
34   public:
35     PortConfigIntf::Config portConfig;
36     static constexpr const char* clientDevicePath =
37         "/tmp/ttyInventoryTestPort0";
38     static constexpr const char* serverDevicePath =
39         "/tmp/ttyInventoryTestPort1";
40     static constexpr const auto defaultBaudeRate = "b115200";
41     static constexpr const auto deviceName = "Test1";
42     static constexpr auto serviceName = "xyz.openbmc_project.TestModbusRTU";
43     int socat_pid = -1;
44     sdbusplus::async::context ctx;
45     int fdClient = -1;
46     std::unique_ptr<TestIntf::ServerTester> serverTester;
47     int fdServer = -1;
48 
InventoryTest()49     InventoryTest()
50     {
51         portConfig.name = "TestPort1";
52         portConfig.portMode = PortConfigIntf::PortMode::rs485;
53         portConfig.baudRate = 115200;
54         portConfig.rtsDelay = 1;
55 
56         std::string socatCmd = std::format(
57             "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
58             serverDevicePath, defaultBaudeRate, clientDevicePath,
59             defaultBaudeRate);
60 
61         // Start socat in the background and capture its PID
62         FILE* fp = popen(socatCmd.c_str(), "r");
63         EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
64         EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
65         pclose(fp);
66 
67         // Wait for socat to start up
68         sleep(1);
69 
70         fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
71         EXPECT_NE(fdClient, -1)
72             << "Failed to open serial port " << clientDevicePath
73             << " with error: " << strerror(errno);
74 
75         fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
76         EXPECT_NE(fdServer, -1)
77             << "Failed to open serial port " << serverDevicePath
78             << " with error: " << strerror(errno);
79 
80         ctx.request_name(serviceName);
81 
82         serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
83     }
84 
~InventoryTest()85     ~InventoryTest() noexcept override
86     {
87         if (fdClient != -1)
88         {
89             close(fdClient);
90             fdClient = -1;
91         }
92         if (fdServer != -1)
93         {
94             close(fdServer);
95             fdServer = -1;
96         }
97         kill(socat_pid, SIGTERM);
98     }
99 
testInventorySourceCreation(std::string objPath)100     auto testInventorySourceCreation(std::string objPath)
101         -> sdbusplus::async::task<void>
102     {
103         InventoryConfigIntf::Config::port_address_map_t addressMap;
104         addressMap[portConfig.name] = {{.start = TestIntf::testDeviceAddress,
105                                         .end = TestIntf::testDeviceAddress}};
106         InventoryConfigIntf::Config deviceConfig = {
107             .name = deviceName,
108             .addressMap = addressMap,
109             .registers = {{"Model",
110                            TestIntf::testReadHoldingRegisterModelOffset,
111                            TestIntf::testReadHoldingRegisterModelCount}},
112             .parity = ModbusIntf::Parity::none,
113             .baudRate = 115200};
114         InventoryIntf::Device::serial_port_map_t ports;
115         ports[portConfig.name] =
116             std::make_unique<MockPort>(ctx, portConfig, clientDevicePath);
117 
118         auto inventoryDevice =
119             std::make_unique<InventoryIntf::Device>(ctx, deviceConfig, ports);
120 
121         co_await inventoryDevice->probePorts();
122 
123         // Create InventorySource client interface to read back D-Bus properties
124         auto properties = co_await InventorySourceIntf(ctx)
125                               .service(serviceName)
126                               .path(objPath)
127                               .properties();
128 
129         constexpr auto defaultInventoryValue = "Unknown";
130 
131         EXPECT_EQ(properties.name,
132                   std::format("{} {} {}", deviceName,
133                               TestIntf::testDeviceAddress, portConfig.name))
134             << "Name mismatch";
135         EXPECT_EQ(properties.address, TestIntf::testDeviceAddress)
136             << "Address mismatch";
137         EXPECT_EQ(properties.link_tty, portConfig.name) << "Link TTY mismatch";
138         EXPECT_EQ(properties.model, TestIntf::testReadHoldingRegisterModelStr)
139             << "Model mismatch";
140         EXPECT_EQ(properties.serial_number, defaultInventoryValue)
141             << "Part Number mismatch";
142 
143         co_return;
144     }
145 
SetUp()146     void SetUp() override
147     {
148         // Process request for probe device call
149         ctx.spawn(serverTester->processRequests());
150 
151         // Process request to read `Model` holding register call
152         ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
153                   sdbusplus::async::execution::then([&]() {
154                       ctx.spawn(serverTester->processRequests());
155                   }));
156     }
157 };
158 
TEST_F(InventoryTest,TestAddInventorySource)159 TEST_F(InventoryTest, TestAddInventorySource)
160 {
161     auto objPath =
162         std::format("{}/{}_{}_{}", InventorySourceIntf::namespace_path,
163                     deviceName, TestIntf::testDeviceAddress, portConfig.name);
164 
165     ctx.spawn(testInventorySourceCreation(objPath));
166 
167     ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
168               sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
169 
170     ctx.run();
171 }
172