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