287 lines
9.7 KiB
C++
287 lines
9.7 KiB
C++
//===-- TerminalTest.cpp --------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Host/PseudoTerminal.h"
|
|
#include "lldb/Host/Terminal.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
using namespace lldb_private;
|
|
|
|
class TerminalTest : public ::testing::Test {
|
|
protected:
|
|
PseudoTerminal m_pty;
|
|
int m_fd;
|
|
Terminal m_term;
|
|
|
|
void SetUp() override {
|
|
ASSERT_THAT_ERROR(m_pty.OpenFirstAvailablePrimary(O_RDWR | O_NOCTTY),
|
|
llvm::Succeeded());
|
|
ASSERT_THAT_ERROR(m_pty.OpenSecondary(O_RDWR | O_NOCTTY),
|
|
llvm::Succeeded());
|
|
m_fd = m_pty.GetSecondaryFileDescriptor();
|
|
ASSERT_NE(m_fd, -1);
|
|
m_term.SetFileDescriptor(m_fd);
|
|
}
|
|
};
|
|
|
|
TEST_F(TerminalTest, PtyIsATerminal) {
|
|
EXPECT_EQ(m_term.IsATerminal(), true);
|
|
}
|
|
|
|
TEST_F(TerminalTest, PipeIsNotATerminal) {
|
|
int pipefd[2];
|
|
ASSERT_EQ(pipe(pipefd), 0);
|
|
Terminal pipeterm{pipefd[0]};
|
|
EXPECT_EQ(pipeterm.IsATerminal(), false);
|
|
close(pipefd[0]);
|
|
close(pipefd[1]);
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetEcho) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetEcho(true), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_lflag & ECHO, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetEcho(false), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_lflag & ECHO, 0U);
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetCanonical) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetCanonical(true), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_lflag & ICANON, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetCanonical(false), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_lflag & ICANON, 0U);
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetRaw) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetRaw(), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
// NB: cfmakeraw() on glibc disables IGNBRK, on FreeBSD sets it
|
|
EXPECT_EQ(terminfo.c_iflag &
|
|
(BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON),
|
|
0U);
|
|
EXPECT_EQ(terminfo.c_oflag & OPOST, 0U);
|
|
EXPECT_EQ(terminfo.c_lflag & (ICANON | ECHO | ISIG | IEXTEN), 0U);
|
|
EXPECT_EQ(terminfo.c_cflag & (CSIZE | PARENB), 0U | CS8);
|
|
EXPECT_EQ(terminfo.c_cc[VMIN], 1);
|
|
EXPECT_EQ(terminfo.c_cc[VTIME], 0);
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetBaudRate) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetBaudRate(38400), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(cfgetispeed(&terminfo), static_cast<speed_t>(B38400));
|
|
EXPECT_EQ(cfgetospeed(&terminfo), static_cast<speed_t>(B38400));
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetBaudRate(115200), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(cfgetispeed(&terminfo), static_cast<speed_t>(B115200));
|
|
EXPECT_EQ(cfgetospeed(&terminfo), static_cast<speed_t>(B115200));
|
|
|
|
// uncommon value
|
|
#if defined(B153600)
|
|
ASSERT_THAT_ERROR(m_term.SetBaudRate(153600), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(cfgetispeed(&terminfo), static_cast<speed_t>(B153600));
|
|
EXPECT_EQ(cfgetospeed(&terminfo), static_cast<speed_t>(B153600));
|
|
#else
|
|
ASSERT_THAT_ERROR(m_term.SetBaudRate(153600),
|
|
llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
|
|
&llvm::ErrorInfoBase::message,
|
|
"baud rate 153600 unsupported by the platform")));
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetStopBits) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetStopBits(1), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_cflag & CSTOPB, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetStopBits(2), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_cflag & CSTOPB, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetStopBits(0),
|
|
llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
|
|
&llvm::ErrorInfoBase::message,
|
|
"invalid stop bit count: 0 (must be 1 or 2)")));
|
|
ASSERT_THAT_ERROR(m_term.SetStopBits(3),
|
|
llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
|
|
&llvm::ErrorInfoBase::message,
|
|
"invalid stop bit count: 3 (must be 1 or 2)")));
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetParity) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::No), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_cflag & PARENB, 0U);
|
|
|
|
#if !defined(__linux__) // Linux pty devices do not support setting parity
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Even),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
|
|
EXPECT_EQ(terminfo.c_cflag & PARODD, 0U);
|
|
#if defined(CMSPAR)
|
|
EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U);
|
|
#endif
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Odd), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
|
|
EXPECT_NE(terminfo.c_cflag & PARODD, 0U);
|
|
#if defined(CMSPAR)
|
|
EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U);
|
|
#endif
|
|
|
|
#if defined(CMSPAR)
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Space),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
|
|
EXPECT_EQ(terminfo.c_cflag & PARODD, 0U);
|
|
EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Mark),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_cflag & PARENB, 0U);
|
|
EXPECT_NE(terminfo.c_cflag & PARODD, 0U);
|
|
EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U);
|
|
#endif // defined(CMSPAR)
|
|
#endif // !defined(__linux__)
|
|
|
|
#if !defined(CMSPAR)
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Space),
|
|
llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
|
|
&llvm::ErrorInfoBase::message,
|
|
"space/mark parity is not supported by the platform")));
|
|
ASSERT_THAT_ERROR(m_term.SetParity(Terminal::Parity::Mark),
|
|
llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
|
|
&llvm::ErrorInfoBase::message,
|
|
"space/mark parity is not supported by the platform")));
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetParityCheck) {
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetParityCheck(Terminal::ParityCheck::No),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_iflag & (IGNPAR | PARMRK | INPCK), 0U);
|
|
|
|
ASSERT_THAT_ERROR(
|
|
m_term.SetParityCheck(Terminal::ParityCheck::ReplaceWithNUL),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_iflag & INPCK, 0U);
|
|
EXPECT_EQ(terminfo.c_iflag & (IGNPAR | PARMRK), 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetParityCheck(Terminal::ParityCheck::Ignore),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_iflag & IGNPAR, 0U);
|
|
EXPECT_EQ(terminfo.c_iflag & PARMRK, 0U);
|
|
EXPECT_NE(terminfo.c_iflag & INPCK, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetParityCheck(Terminal::ParityCheck::Mark),
|
|
llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_iflag & IGNPAR, 0U);
|
|
EXPECT_NE(terminfo.c_iflag & PARMRK, 0U);
|
|
EXPECT_NE(terminfo.c_iflag & INPCK, 0U);
|
|
}
|
|
|
|
TEST_F(TerminalTest, SetHardwareFlowControl) {
|
|
#if defined(CRTSCTS)
|
|
struct termios terminfo;
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(true), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_NE(terminfo.c_cflag & CRTSCTS, 0U);
|
|
|
|
ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(false), llvm::Succeeded());
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
EXPECT_EQ(terminfo.c_cflag & CRTSCTS, 0U);
|
|
#else
|
|
ASSERT_THAT_ERROR(
|
|
m_term.SetHardwareFlowControl(true),
|
|
llvm::Failed<llvm::ErrorInfoBase>(testing::Property(
|
|
&llvm::ErrorInfoBase::message,
|
|
"hardware flow control is not supported by the platform")));
|
|
ASSERT_THAT_ERROR(m_term.SetHardwareFlowControl(false), llvm::Succeeded());
|
|
#endif
|
|
}
|
|
|
|
TEST_F(TerminalTest, SaveRestoreRAII) {
|
|
struct termios orig_terminfo;
|
|
struct termios terminfo;
|
|
ASSERT_EQ(tcgetattr(m_fd, &orig_terminfo), 0);
|
|
|
|
{
|
|
TerminalState term_state{m_term};
|
|
terminfo = orig_terminfo;
|
|
|
|
// make an arbitrary change
|
|
cfsetispeed(&terminfo,
|
|
cfgetispeed(&orig_terminfo) == B9600 ? B4800 : B9600);
|
|
cfsetospeed(&terminfo,
|
|
cfgetospeed(&orig_terminfo) == B9600 ? B4800 : B9600);
|
|
|
|
ASSERT_EQ(tcsetattr(m_fd, TCSANOW, &terminfo),
|
|
0);
|
|
}
|
|
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
ASSERT_EQ(memcmp(&terminfo, &orig_terminfo, sizeof(terminfo)), 0);
|
|
}
|
|
|
|
TEST_F(TerminalTest, SaveRestore) {
|
|
TerminalState term_state;
|
|
|
|
struct termios orig_terminfo;
|
|
struct termios terminfo;
|
|
ASSERT_EQ(tcgetattr(m_fd, &orig_terminfo), 0);
|
|
|
|
term_state.Save(m_term, false);
|
|
terminfo = orig_terminfo;
|
|
|
|
// make an arbitrary change
|
|
cfsetispeed(&terminfo, cfgetispeed(&orig_terminfo) == B9600 ? B4800 : B9600);
|
|
cfsetospeed(&terminfo, cfgetospeed(&orig_terminfo) == B9600 ? B4800 : B9600);
|
|
|
|
ASSERT_EQ(tcsetattr(m_fd, TCSANOW, &terminfo), 0);
|
|
|
|
term_state.Restore();
|
|
ASSERT_EQ(tcgetattr(m_fd, &terminfo), 0);
|
|
ASSERT_EQ(memcmp(&terminfo, &orig_terminfo, sizeof(terminfo)), 0);
|
|
}
|