1 //===-- TerminalTest.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/Host/PseudoTerminal.h"
10 #include "lldb/Host/Terminal.h"
11 #include "llvm/Testing/Support/Error.h"
12
13 #include "gmock/gmock.h"
14 #include "gtest/gtest.h"
15
16 #include <termios.h>
17 #include <unistd.h>
18
19 using namespace lldb_private;
20
21 class TerminalTest : public ::testing::Test {
22 protected:
23 PseudoTerminal m_pty;
24 int m_fd;
25 Terminal m_term;
26
SetUp()27 void SetUp() override {
28 ASSERT_THAT_ERROR(m_pty.OpenFirstAvailablePrimary(O_RDWR | O_NOCTTY),
29 llvm::Succeeded());
30 ASSERT_THAT_ERROR(m_pty.OpenSecondary(O_RDWR | O_NOCTTY),
31 llvm::Succeeded());
32 m_fd = m_pty.GetSecondaryFileDescriptor();
33 ASSERT_NE(m_fd, -1);
34 m_term.SetFileDescriptor(m_fd);
35 }
36 };
37
TEST_F(TerminalTest,PtyIsATerminal)38 TEST_F(TerminalTest, PtyIsATerminal) {
39 EXPECT_EQ(m_term.IsATerminal(), true);
40 }
41
TEST_F(TerminalTest,PipeIsNotATerminal)42 TEST_F(TerminalTest, PipeIsNotATerminal) {
43 int pipefd[2];
44 ASSERT_EQ(pipe(pipefd), 0);
45 Terminal pipeterm{pipefd[0]};
46 EXPECT_EQ(pipeterm.IsATerminal(), false);
47 close(pipefd[0]);
48 close(pipefd[1]);
49 }
50
TEST_F(TerminalTest,SetEcho)51 TEST_F(TerminalTest, SetEcho) {
52 struct termios terminfo;
53
54 ASSERT_THAT_ERROR(m_term.SetEcho(true), llvm::Succeeded());
55 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
56 EXPECT_NE(terminfo.c_lflag & ECHO, 0U);
57
58 ASSERT_THAT_ERROR(m_term.SetEcho(false), llvm::Succeeded());
59 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
60 EXPECT_EQ(terminfo.c_lflag & ECHO, 0U);
61 }
62
TEST_F(TerminalTest,SetCanonical)63 TEST_F(TerminalTest, SetCanonical) {
64 struct termios terminfo;
65
66 ASSERT_THAT_ERROR(m_term.SetCanonical(true), llvm::Succeeded());
67 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
68 EXPECT_NE(terminfo.c_lflag & ICANON, 0U);
69
70 ASSERT_THAT_ERROR(m_term.SetCanonical(false), llvm::Succeeded());
71 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
72 EXPECT_EQ(terminfo.c_lflag & ICANON, 0U);
73 }
74
TEST_F(TerminalTest,SetRaw)75 TEST_F(TerminalTest, SetRaw) {
76 struct termios terminfo;
77
78 ASSERT_THAT_ERROR(m_term.SetRaw(), llvm::Succeeded());
79 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
80 // NB: cfmakeraw() on glibc disables IGNBRK, on FreeBSD sets it
81 EXPECT_EQ(terminfo.c_iflag &
82 (BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON),
83 0U);
84 EXPECT_EQ(terminfo.c_oflag & OPOST, 0U);
85 EXPECT_EQ(terminfo.c_lflag & (ICANON | ECHO | ISIG | IEXTEN), 0U);
86 EXPECT_EQ(terminfo.c_cflag & (CSIZE | PARENB), 0U | CS8);
87 EXPECT_EQ(terminfo.c_cc[VMIN], 1);
88 EXPECT_EQ(terminfo.c_cc[VTIME], 0);
89 }
90
TEST_F(TerminalTest,SetBaudRate)91 TEST_F(TerminalTest, SetBaudRate) {
92 struct termios terminfo;
93
94 ASSERT_THAT_ERROR(m_term.SetBaudRate(38400), llvm::Succeeded());
95 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
96 EXPECT_EQ(cfgetispeed(&terminfo), static_cast<speed_t>(B38400));
97 EXPECT_EQ(cfgetospeed(&terminfo), static_cast<speed_t>(B38400));
98
99 ASSERT_THAT_ERROR(m_term.SetBaudRate(115200), llvm::Succeeded());
100 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
101 EXPECT_EQ(cfgetispeed(&terminfo), static_cast<speed_t>(B115200));
102 EXPECT_EQ(cfgetospeed(&terminfo), static_cast<speed_t>(B115200));
103
104 // uncommon value
105 #if defined(B153600)
106 ASSERT_THAT_ERROR(m_term.SetBaudRate(153600), llvm::Succeeded());
107 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
108 EXPECT_EQ(cfgetispeed(&terminfo), static_cast<speed_t>(B153600));
109 EXPECT_EQ(cfgetospeed(&terminfo), static_cast<speed_t>(B153600));
110 #else
111 ASSERT_THAT_ERROR(m_term.SetBaudRate(153600),
112 llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
113 &llvm::ErrorInfoBase::message,
114 "baud rate 153600 unsupported by the platform")));
115 #endif
116 }
117
TEST_F(TerminalTest,SetStopBits)118 TEST_F(TerminalTest, SetStopBits) {
119 struct termios terminfo;
120
121 ASSERT_THAT_ERROR(m_term.SetStopBits(1), llvm::Succeeded());
122 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
123 EXPECT_EQ(terminfo.c_cflag & CSTOPB, 0U);
124
125 ASSERT_THAT_ERROR(m_term.SetStopBits(2), llvm::Succeeded());
126 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
127 EXPECT_NE(terminfo.c_cflag & CSTOPB, 0U);
128
129 ASSERT_THAT_ERROR(m_term.SetStopBits(0),
130 llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
131 &llvm::ErrorInfoBase::message,
132 "invalid stop bit count: 0 (must be 1 or 2)")));
133 ASSERT_THAT_ERROR(m_term.SetStopBits(3),
134 llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
135 &llvm::ErrorInfoBase::message,
136 "invalid stop bit count: 3 (must be 1 or 2)")));
137 }
138
TEST_F(TerminalTest,SetParity)139 TEST_F(TerminalTest, SetParity) {
140 struct termios terminfo;
141
142 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::No), llvm::Succeeded());
143 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
144 EXPECT_EQ(terminfo.c_cflag & PARENB, 0U);
145
146 #if !defined(__linux__) // Linux pty devices do not support setting parity
147 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Even),
148 llvm::Succeeded());
149 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
150 EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
151 EXPECT_EQ(terminfo.c_cflag & PARODD, 0U);
152 #if defined(CMSPAR)
153 EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U);
154 #endif
155
156 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Odd), llvm::Succeeded());
157 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
158 EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
159 EXPECT_NE(terminfo.c_cflag & PARODD, 0U);
160 #if defined(CMSPAR)
161 EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U);
162 #endif
163
164 #if defined(CMSPAR)
165 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Space),
166 llvm::Succeeded());
167 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
168 EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
169 EXPECT_EQ(terminfo.c_cflag & PARODD, 0U);
170 EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U);
171
172 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Mark),
173 llvm::Succeeded());
174 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
175 EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
176 EXPECT_NE(terminfo.c_cflag & PARODD, 0U);
177 EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U);
178 #endif // defined(CMSPAR)
179 #endif // !defined(__linux__)
180
181 #if !defined(CMSPAR)
182 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Space),
183 llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
184 &llvm::ErrorInfoBase::message,
185 "space/mark parity is not supported by the platform")));
186 ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Mark),
187 llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
188 &llvm::ErrorInfoBase::message,
189 "space/mark parity is not supported by the platform")));
190 #endif
191 }
192
TEST_F(TerminalTest,SetParityCheck)193 TEST_F(TerminalTest, SetParityCheck) {
194 struct termios terminfo;
195
196 ASSERT_THAT_ERROR(m_term.SetParityCheck(Terminal::ParityCheck::No),
197 llvm::Succeeded());
198 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
199 EXPECT_EQ(terminfo.c_iflag & (IGNPAR | PARMRK | INPCK), 0U);
200
201 ASSERT_THAT_ERROR(
202 m_term.SetParityCheck(Terminal::ParityCheck::ReplaceWithNUL),
203 llvm::Succeeded());
204 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
205 EXPECT_NE(terminfo.c_iflag & INPCK, 0U);
206 EXPECT_EQ(terminfo.c_iflag & (IGNPAR | PARMRK), 0U);
207
208 ASSERT_THAT_ERROR(m_term.SetParityCheck(Terminal::ParityCheck::Ignore),
209 llvm::Succeeded());
210 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
211 EXPECT_NE(terminfo.c_iflag & IGNPAR, 0U);
212 EXPECT_EQ(terminfo.c_iflag & PARMRK, 0U);
213 EXPECT_NE(terminfo.c_iflag & INPCK, 0U);
214
215 ASSERT_THAT_ERROR(m_term.SetParityCheck(Terminal::ParityCheck::Mark),
216 llvm::Succeeded());
217 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
218 EXPECT_EQ(terminfo.c_iflag & IGNPAR, 0U);
219 EXPECT_NE(terminfo.c_iflag & PARMRK, 0U);
220 EXPECT_NE(terminfo.c_iflag & INPCK, 0U);
221 }
222
TEST_F(TerminalTest,SetHardwareFlowControl)223 TEST_F(TerminalTest, SetHardwareFlowControl) {
224 #if defined(CRTSCTS)
225 struct termios terminfo;
226
227 ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(true), llvm::Succeeded());
228 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
229 EXPECT_NE(terminfo.c_cflag & CRTSCTS, 0U);
230
231 ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(false), llvm::Succeeded());
232 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
233 EXPECT_EQ(terminfo.c_cflag & CRTSCTS, 0U);
234 #else
235 ASSERT_THAT_ERROR(
236 m_term.SetHardwareFlowControl(true),
237 llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
238 &llvm::ErrorInfoBase::message,
239 "hardware flow control is not supported by the platform")));
240 ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(false), llvm::Succeeded());
241 #endif
242 }
243
TEST_F(TerminalTest,SaveRestoreRAII)244 TEST_F(TerminalTest, SaveRestoreRAII) {
245 struct termios orig_terminfo;
246 struct termios terminfo;
247 ASSERT_EQ(tcgetattr(m_fd, &orig_terminfo), 0);
248
249 {
250 TerminalState term_state{m_term};
251 terminfo = orig_terminfo;
252
253 // make an arbitrary change
254 cfsetispeed(&terminfo,
255 cfgetispeed(&orig_terminfo) == B9600 ? B4800 : B9600);
256 cfsetospeed(&terminfo,
257 cfgetospeed(&orig_terminfo) == B9600 ? B4800 : B9600);
258
259 ASSERT_EQ(tcsetattr(m_fd, TCSANOW, &terminfo),
260 0);
261 }
262
263 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
264 ASSERT_EQ(memcmp(&terminfo, &orig_terminfo, sizeof(terminfo)), 0);
265 }
266
TEST_F(TerminalTest,SaveRestore)267 TEST_F(TerminalTest, SaveRestore) {
268 TerminalState term_state;
269
270 struct termios orig_terminfo;
271 struct termios terminfo;
272 ASSERT_EQ(tcgetattr(m_fd, &orig_terminfo), 0);
273
274 term_state.Save(m_term, false);
275 terminfo = orig_terminfo;
276
277 // make an arbitrary change
278 cfsetispeed(&terminfo, cfgetispeed(&orig_terminfo) == B9600 ? B4800 : B9600);
279 cfsetospeed(&terminfo, cfgetospeed(&orig_terminfo) == B9600 ? B4800 : B9600);
280
281 ASSERT_EQ(tcsetattr(m_fd, TCSANOW, &terminfo), 0);
282
283 term_state.Restore();
284 ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
285 ASSERT_EQ(memcmp(&terminfo, &orig_terminfo, sizeof(terminfo)), 0);
286 }
287