1 //===-- EditlineTest.cpp ----------------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #ifndef LLDB_DISABLE_LIBEDIT 11 12 #define EDITLINE_TEST_DUMP_OUTPUT 0 13 14 #include <stdio.h> 15 #include <unistd.h> 16 17 #include <memory> 18 #include <thread> 19 20 #include "gtest/gtest.h" 21 22 #include "lldb/Core/Error.h" 23 #include "lldb/Core/StringList.h" 24 #include "lldb/Host/Editline.h" 25 #include "lldb/Host/Pipe.h" 26 #include "lldb/Utility/PseudoTerminal.h" 27 28 namespace { 29 const size_t TIMEOUT_MILLIS = 5000; 30 } 31 32 class FilePointer { 33 public: 34 FilePointer() = delete; 35 36 FilePointer(const FilePointer &) = delete; 37 38 FilePointer(FILE *file_p) : _file_p(file_p) {} 39 40 ~FilePointer() { 41 if (_file_p != nullptr) { 42 const int close_result = fclose(_file_p); 43 EXPECT_EQ(0, close_result); 44 } 45 } 46 47 operator FILE *() { return _file_p; } 48 49 private: 50 FILE *_file_p; 51 }; 52 53 /** 54 Wraps an Editline class, providing a simple way to feed 55 input (as if from the keyboard) and receive output from Editline. 56 */ 57 class EditlineAdapter { 58 public: 59 EditlineAdapter(); 60 61 void CloseInput(); 62 63 bool IsValid() const { return _editline_sp.get() != nullptr; } 64 65 lldb_private::Editline &GetEditline() { return *_editline_sp; } 66 67 bool SendLine(const std::string &line); 68 69 bool SendLines(const std::vector<std::string> &lines); 70 71 bool GetLine(std::string &line, bool &interrupted, size_t timeout_millis); 72 73 bool GetLines(lldb_private::StringList &lines, bool &interrupted, 74 size_t timeout_millis); 75 76 void ConsumeAllOutput(); 77 78 private: 79 static bool IsInputComplete(lldb_private::Editline *editline, 80 lldb_private::StringList &lines, void *baton); 81 82 std::unique_ptr<lldb_private::Editline> _editline_sp; 83 84 lldb_utility::PseudoTerminal _pty; 85 int _pty_master_fd; 86 int _pty_slave_fd; 87 88 std::unique_ptr<FilePointer> _el_slave_file; 89 }; 90 91 EditlineAdapter::EditlineAdapter() 92 : _editline_sp(), _pty(), _pty_master_fd(-1), _pty_slave_fd(-1), 93 _el_slave_file() { 94 lldb_private::Error error; 95 96 // Open the first master pty available. 97 char error_string[256]; 98 error_string[0] = '\0'; 99 if (!_pty.OpenFirstAvailableMaster(O_RDWR, error_string, 100 sizeof(error_string))) { 101 fprintf(stderr, "failed to open first available master pty: '%s'\n", 102 error_string); 103 return; 104 } 105 106 // Grab the master fd. This is a file descriptor we will: 107 // (1) write to when we want to send input to editline. 108 // (2) read from when we want to see what editline sends back. 109 _pty_master_fd = _pty.GetMasterFileDescriptor(); 110 111 // Open the corresponding slave pty. 112 if (!_pty.OpenSlave(O_RDWR, error_string, sizeof(error_string))) { 113 fprintf(stderr, "failed to open slave pty: '%s'\n", error_string); 114 return; 115 } 116 _pty_slave_fd = _pty.GetSlaveFileDescriptor(); 117 118 _el_slave_file.reset(new FilePointer(fdopen(_pty_slave_fd, "rw"))); 119 EXPECT_FALSE(nullptr == *_el_slave_file); 120 if (*_el_slave_file == nullptr) 121 return; 122 123 // Create an Editline instance. 124 _editline_sp.reset(new lldb_private::Editline("gtest editor", *_el_slave_file, 125 *_el_slave_file, 126 *_el_slave_file, false)); 127 _editline_sp->SetPrompt("> "); 128 129 // Hookup our input complete callback. 130 _editline_sp->SetIsInputCompleteCallback(IsInputComplete, this); 131 } 132 133 void EditlineAdapter::CloseInput() { 134 if (_el_slave_file != nullptr) 135 _el_slave_file.reset(nullptr); 136 } 137 138 bool EditlineAdapter::SendLine(const std::string &line) { 139 // Ensure we're valid before proceeding. 140 if (!IsValid()) 141 return false; 142 143 // Write the line out to the pipe connected to editline's input. 144 ssize_t input_bytes_written = 145 ::write(_pty_master_fd, line.c_str(), 146 line.length() * sizeof(std::string::value_type)); 147 148 const char *eoln = "\n"; 149 const size_t eoln_length = strlen(eoln); 150 input_bytes_written = 151 ::write(_pty_master_fd, eoln, eoln_length * sizeof(char)); 152 153 EXPECT_NE(-1, input_bytes_written) << strerror(errno); 154 EXPECT_EQ(eoln_length * sizeof(char), size_t(input_bytes_written)); 155 return eoln_length * sizeof(char) == size_t(input_bytes_written); 156 } 157 158 bool EditlineAdapter::SendLines(const std::vector<std::string> &lines) { 159 for (auto &line : lines) { 160 #if EDITLINE_TEST_DUMP_OUTPUT 161 printf("<stdin> sending line \"%s\"\n", line.c_str()); 162 #endif 163 if (!SendLine(line)) 164 return false; 165 } 166 return true; 167 } 168 169 // We ignore the timeout for now. 170 bool EditlineAdapter::GetLine(std::string &line, bool &interrupted, 171 size_t /* timeout_millis */) { 172 // Ensure we're valid before proceeding. 173 if (!IsValid()) 174 return false; 175 176 _editline_sp->GetLine(line, interrupted); 177 return true; 178 } 179 180 bool EditlineAdapter::GetLines(lldb_private::StringList &lines, 181 bool &interrupted, size_t /* timeout_millis */) { 182 // Ensure we're valid before proceeding. 183 if (!IsValid()) 184 return false; 185 186 _editline_sp->GetLines(1, lines, interrupted); 187 return true; 188 } 189 190 bool EditlineAdapter::IsInputComplete(lldb_private::Editline *editline, 191 lldb_private::StringList &lines, 192 void *baton) { 193 // We'll call ourselves complete if we've received a balanced set of braces. 194 int start_block_count = 0; 195 int brace_balance = 0; 196 197 for (size_t i = 0; i < lines.GetSize(); ++i) { 198 for (auto ch : lines[i]) { 199 if (ch == '{') { 200 ++start_block_count; 201 ++brace_balance; 202 } else if (ch == '}') 203 --brace_balance; 204 } 205 } 206 207 return (start_block_count > 0) && (brace_balance == 0); 208 } 209 210 void EditlineAdapter::ConsumeAllOutput() { 211 FilePointer output_file(fdopen(_pty_master_fd, "r")); 212 213 int ch; 214 while ((ch = fgetc(output_file)) != EOF) { 215 #if EDITLINE_TEST_DUMP_OUTPUT 216 char display_str[] = {0, 0, 0}; 217 switch (ch) { 218 case '\t': 219 display_str[0] = '\\'; 220 display_str[1] = 't'; 221 break; 222 case '\n': 223 display_str[0] = '\\'; 224 display_str[1] = 'n'; 225 break; 226 case '\r': 227 display_str[0] = '\\'; 228 display_str[1] = 'r'; 229 break; 230 default: 231 display_str[0] = ch; 232 break; 233 } 234 printf("<stdout> 0x%02x (%03d) (%s)\n", ch, ch, display_str); 235 // putc(ch, stdout); 236 #endif 237 } 238 } 239 240 class EditlineTestFixture : public ::testing::Test { 241 private: 242 EditlineAdapter _el_adapter; 243 std::shared_ptr<std::thread> _sp_output_thread; 244 245 public: 246 void SetUp() { 247 // We need a TERM set properly for editline to work as expected. 248 setenv("TERM", "vt100", 1); 249 250 // Validate the editline adapter. 251 EXPECT_TRUE(_el_adapter.IsValid()); 252 if (!_el_adapter.IsValid()) 253 return; 254 255 // Dump output. 256 _sp_output_thread.reset( 257 new std::thread([&] { _el_adapter.ConsumeAllOutput(); })); 258 } 259 260 void TearDown() { 261 _el_adapter.CloseInput(); 262 if (_sp_output_thread) 263 _sp_output_thread->join(); 264 } 265 266 EditlineAdapter &GetEditlineAdapter() { return _el_adapter; } 267 }; 268 269 TEST_F(EditlineTestFixture, EditlineReceivesSingleLineText) { 270 // Send it some text via our virtual keyboard. 271 const std::string input_text("Hello, world"); 272 EXPECT_TRUE(GetEditlineAdapter().SendLine(input_text)); 273 274 // Verify editline sees what we put in. 275 std::string el_reported_line; 276 bool input_interrupted = false; 277 const bool received_line = GetEditlineAdapter().GetLine( 278 el_reported_line, input_interrupted, TIMEOUT_MILLIS); 279 280 EXPECT_TRUE(received_line); 281 EXPECT_FALSE(input_interrupted); 282 EXPECT_EQ(input_text, el_reported_line); 283 } 284 285 TEST_F(EditlineTestFixture, EditlineReceivesMultiLineText) { 286 // Send it some text via our virtual keyboard. 287 std::vector<std::string> input_lines; 288 input_lines.push_back("int foo()"); 289 input_lines.push_back("{"); 290 input_lines.push_back("printf(\"Hello, world\");"); 291 input_lines.push_back("}"); 292 input_lines.push_back(""); 293 294 EXPECT_TRUE(GetEditlineAdapter().SendLines(input_lines)); 295 296 // Verify editline sees what we put in. 297 lldb_private::StringList el_reported_lines; 298 bool input_interrupted = false; 299 300 EXPECT_TRUE(GetEditlineAdapter().GetLines(el_reported_lines, 301 input_interrupted, TIMEOUT_MILLIS)); 302 EXPECT_FALSE(input_interrupted); 303 304 // Without any auto indentation support, our output should directly match our 305 // input. 306 EXPECT_EQ(input_lines.size(), el_reported_lines.GetSize()); 307 if (input_lines.size() == el_reported_lines.GetSize()) { 308 for (size_t i = 0; i < input_lines.size(); ++i) 309 EXPECT_EQ(input_lines[i], el_reported_lines[i]); 310 } 311 } 312 313 #endif 314