1 | //===-- CommunicationTest.cpp ---------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "lldb/Core/Communication.h" |
10 | #include "lldb/Core/ThreadedCommunication.h" |
11 | #include "lldb/Host/Config.h" |
12 | #include "lldb/Host/ConnectionFileDescriptor.h" |
13 | #include "lldb/Host/Pipe.h" |
14 | #include "llvm/Testing/Support/Error.h" |
15 | #include "gtest/gtest.h" |
16 | #include "TestingSupport/Host/SocketTestUtilities.h" |
17 | #include "TestingSupport/SubsystemRAII.h" |
18 | |
19 | #include <chrono> |
20 | #include <thread> |
21 | |
22 | #if LLDB_ENABLE_POSIX |
23 | #include <fcntl.h> |
24 | #endif |
25 | |
26 | using namespace lldb_private; |
27 | |
28 | class CommunicationTest : public testing::Test { |
29 | private: |
30 | SubsystemRAII<Socket> m_subsystems; |
31 | }; |
32 | |
33 | static void CommunicationReadTest(bool use_read_thread) { |
34 | std::unique_ptr<TCPSocket> a, b; |
35 | ASSERT_TRUE(CreateTCPConnectedSockets("localhost" , &a, &b)); |
36 | |
37 | size_t num_bytes = 4; |
38 | ASSERT_THAT_ERROR(a->Write("test" , num_bytes).ToError(), llvm::Succeeded()); |
39 | ASSERT_EQ(num_bytes, 4U); |
40 | |
41 | ThreadedCommunication comm("test" ); |
42 | comm.SetConnection(std::make_unique<ConnectionFileDescriptor>(args: b.release())); |
43 | comm.SetCloseOnEOF(true); |
44 | |
45 | if (use_read_thread) { |
46 | ASSERT_TRUE(comm.StartReadThread()); |
47 | } |
48 | |
49 | // This read should wait for the data to become available and return it. |
50 | lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; |
51 | char buf[16]; |
52 | Status error; |
53 | EXPECT_EQ( |
54 | comm.Read(buf, sizeof(buf), std::chrono::seconds(5), status, &error), 4U); |
55 | EXPECT_EQ(status, lldb::eConnectionStatusSuccess); |
56 | EXPECT_THAT_ERROR(error.ToError(), llvm::Succeeded()); |
57 | buf[4] = 0; |
58 | EXPECT_STREQ(buf, "test" ); |
59 | |
60 | // These reads should time out as there is no more data. |
61 | error.Clear(); |
62 | EXPECT_EQ(comm.Read(buf, sizeof(buf), std::chrono::microseconds(10), status, |
63 | &error), |
64 | 0U); |
65 | EXPECT_EQ(status, lldb::eConnectionStatusTimedOut); |
66 | EXPECT_THAT_ERROR(error.ToError(), llvm::Failed()); |
67 | |
68 | // 0 is special-cased, so we test it separately. |
69 | error.Clear(); |
70 | EXPECT_EQ( |
71 | comm.Read(buf, sizeof(buf), std::chrono::seconds(0), status, &error), 0U); |
72 | EXPECT_EQ(status, lldb::eConnectionStatusTimedOut); |
73 | EXPECT_THAT_ERROR(error.ToError(), llvm::Failed()); |
74 | |
75 | // This read should return EOF. |
76 | ASSERT_THAT_ERROR(a->Close().ToError(), llvm::Succeeded()); |
77 | error.Clear(); |
78 | EXPECT_EQ( |
79 | comm.Read(buf, sizeof(buf), std::chrono::seconds(5), status, &error), 0U); |
80 | EXPECT_EQ(status, lldb::eConnectionStatusEndOfFile); |
81 | EXPECT_THAT_ERROR(error.ToError(), llvm::Succeeded()); |
82 | |
83 | // JoinReadThread() should just return immediately if there was no read |
84 | // thread started. |
85 | EXPECT_TRUE(comm.JoinReadThread()); |
86 | |
87 | // Test using Communication that is disconnected. |
88 | ASSERT_EQ(comm.Disconnect(), lldb::eConnectionStatusSuccess); |
89 | if (use_read_thread) { |
90 | ASSERT_TRUE(comm.StartReadThread()); |
91 | } |
92 | error.Clear(); |
93 | EXPECT_EQ( |
94 | comm.Read(buf, sizeof(buf), std::chrono::seconds(5), status, &error), 0U); |
95 | EXPECT_EQ(status, lldb::eConnectionStatusLostConnection); |
96 | EXPECT_THAT_ERROR(error.ToError(), llvm::Failed()); |
97 | EXPECT_TRUE(comm.JoinReadThread()); |
98 | |
99 | // Test using Communication without a connection. |
100 | comm.SetConnection(nullptr); |
101 | if (use_read_thread) { |
102 | ASSERT_TRUE(comm.StartReadThread()); |
103 | } |
104 | error.Clear(); |
105 | EXPECT_EQ( |
106 | comm.Read(buf, sizeof(buf), std::chrono::seconds(5), status, &error), 0U); |
107 | EXPECT_EQ(status, lldb::eConnectionStatusNoConnection); |
108 | EXPECT_THAT_ERROR(error.ToError(), llvm::Failed()); |
109 | EXPECT_TRUE(comm.JoinReadThread()); |
110 | } |
111 | |
112 | TEST_F(CommunicationTest, Read) { |
113 | CommunicationReadTest(/*use_thread=*/use_read_thread: false); |
114 | } |
115 | |
116 | TEST_F(CommunicationTest, ReadThread) { |
117 | CommunicationReadTest(/*use_thread=*/use_read_thread: true); |
118 | } |
119 | |
120 | TEST_F(CommunicationTest, SynchronizeWhileClosing) { |
121 | std::unique_ptr<TCPSocket> a, b; |
122 | ASSERT_TRUE(CreateTCPConnectedSockets("localhost" , &a, &b)); |
123 | |
124 | ThreadedCommunication comm("test" ); |
125 | comm.SetConnection(std::make_unique<ConnectionFileDescriptor>(args: b.release())); |
126 | comm.SetCloseOnEOF(true); |
127 | ASSERT_TRUE(comm.StartReadThread()); |
128 | |
129 | // Ensure that we can safely synchronize with the read thread while it is |
130 | // closing the read end (in response to us closing the write end). |
131 | ASSERT_THAT_ERROR(a->Close().ToError(), llvm::Succeeded()); |
132 | comm.SynchronizeWithReadThread(); |
133 | |
134 | ASSERT_TRUE(comm.StopReadThread()); |
135 | } |
136 | |
137 | #if LLDB_ENABLE_POSIX |
138 | TEST_F(CommunicationTest, WriteAll) { |
139 | Pipe pipe; |
140 | ASSERT_THAT_ERROR(pipe.CreateNew(/*child_process_inherit=*/false).ToError(), |
141 | llvm::Succeeded()); |
142 | |
143 | // Make the write end non-blocking in order to easily reproduce a partial |
144 | // write. |
145 | int write_fd = pipe.ReleaseWriteFileDescriptor(); |
146 | int flags = fcntl(fd: write_fd, F_GETFL); |
147 | ASSERT_NE(flags, -1); |
148 | ASSERT_NE(fcntl(write_fd, F_SETFL, flags | O_NONBLOCK), -1); |
149 | |
150 | ConnectionFileDescriptor read_conn{pipe.ReleaseReadFileDescriptor(), |
151 | /*owns_fd=*/true}; |
152 | Communication write_comm; |
153 | write_comm.SetConnection( |
154 | std::make_unique<ConnectionFileDescriptor>(args&: write_fd, /*owns_fd=*/args: true)); |
155 | |
156 | std::thread read_thread{[&read_conn]() { |
157 | // Read using a smaller buffer to increase chances of partial write. |
158 | char buf[128 * 1024]; |
159 | lldb::ConnectionStatus conn_status; |
160 | |
161 | do { |
162 | read_conn.Read(dst: buf, dst_len: sizeof(buf), timeout: std::chrono::seconds(1), status&: conn_status, |
163 | error_ptr: nullptr); |
164 | } while (conn_status != lldb::eConnectionStatusEndOfFile); |
165 | }}; |
166 | |
167 | // Write 1 MiB of data into the pipe. |
168 | lldb::ConnectionStatus conn_status; |
169 | Status error; |
170 | std::vector<uint8_t> data(1024 * 1024, 0x80); |
171 | EXPECT_EQ(write_comm.WriteAll(data.data(), data.size(), conn_status, &error), |
172 | data.size()); |
173 | EXPECT_EQ(conn_status, lldb::eConnectionStatusSuccess); |
174 | EXPECT_FALSE(error.Fail()); |
175 | |
176 | // Close the write end in order to trigger EOF. |
177 | write_comm.Disconnect(); |
178 | read_thread.join(); |
179 | } |
180 | #endif |
181 | |