xref: /llvm-project/lldb/unittests/Host/posix/TerminalTest.cpp (revision 21bb808eb4867b35b08fa962c7c25e812fcc8836)
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 
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 
38 TEST_F(TerminalTest, PtyIsATerminal) {
39   EXPECT_EQ(m_term.IsATerminal(), true);
40 }
41 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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