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

source code of lldb/unittests/Host/posix/TerminalTest.cpp