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_EQ (eoln_length * sizeof (char), input_bytes_written); 195 return eoln_length * sizeof (char) == input_bytes_written; 196 } 197 198 bool 199 EditlineAdapter::SendLines (const std::vector<std::string> &lines) 200 { 201 for (auto &line : lines) 202 { 203 #if EDITLINE_TEST_DUMP_OUTPUT 204 printf ("<stdin> sending line \"%s\"\n", line.c_str()); 205 #endif 206 if (!SendLine (line)) 207 return false; 208 } 209 return true; 210 } 211 212 // We ignore the timeout for now. 213 bool 214 EditlineAdapter::GetLine (std::string &line, bool &interrupted, size_t /* timeout_millis */) 215 { 216 // Ensure we're valid before proceeding. 217 if (!IsValid ()) 218 return false; 219 220 _editline_sp->GetLine (line, interrupted); 221 return true; 222 } 223 224 bool 225 EditlineAdapter::GetLines (lldb_private::StringList &lines, bool &interrupted, size_t /* timeout_millis */) 226 { 227 // Ensure we're valid before proceeding. 228 if (!IsValid ()) 229 return false; 230 231 _editline_sp->GetLines (1, lines, interrupted); 232 return true; 233 } 234 235 bool 236 EditlineAdapter::IsInputComplete ( 237 lldb_private::Editline * editline, 238 lldb_private::StringList & lines, 239 void * baton) 240 { 241 // We'll call ourselves complete if we've received a balanced set of braces. 242 int start_block_count = 0; 243 int brace_balance = 0; 244 245 for (size_t i = 0; i < lines.GetSize (); ++i) 246 { 247 for (auto ch : lines[i]) 248 { 249 if (ch == '{') 250 { 251 ++start_block_count; 252 ++brace_balance; 253 } 254 else if (ch == '}') 255 --brace_balance; 256 } 257 } 258 259 return (start_block_count > 0) && (brace_balance == 0); 260 } 261 262 void 263 EditlineAdapter::ConsumeAllOutput () 264 { 265 FilePointer output_file (fdopen (_pty_master_fd, "r")); 266 267 int ch; 268 while ((ch = fgetc(output_file)) != EOF) 269 { 270 #if EDITLINE_TEST_DUMP_OUTPUT 271 char display_str[] = { 0, 0, 0 }; 272 switch (ch) 273 { 274 case '\t': 275 display_str[0] = '\\'; 276 display_str[1] = 't'; 277 break; 278 case '\n': 279 display_str[0] = '\\'; 280 display_str[1] = 'n'; 281 break; 282 case '\r': 283 display_str[0] = '\\'; 284 display_str[1] = 'r'; 285 break; 286 default: 287 display_str[0] = ch; 288 break; 289 } 290 printf ("<stdout> 0x%02x (%03d) (%s)\n", ch, ch, display_str); 291 // putc(ch, stdout); 292 #endif 293 } 294 } 295 296 class EditlineTestFixture : public ::testing::Test 297 { 298 private: 299 EditlineAdapter _el_adapter; 300 std::shared_ptr<std::thread> _sp_output_thread; 301 302 public: 303 void SetUp() 304 { 305 // We need a TERM set properly for editline to work as expected. 306 setenv("TERM", "vt100", 1); 307 308 // Validate the editline adapter. 309 EXPECT_TRUE(_el_adapter.IsValid()); 310 if (!_el_adapter.IsValid()) 311 return; 312 313 // Dump output. 314 _sp_output_thread.reset(new std::thread([&] { _el_adapter.ConsumeAllOutput(); })); 315 } 316 317 void TearDown() 318 { 319 _el_adapter.CloseInput(); 320 if (_sp_output_thread) 321 _sp_output_thread->join(); 322 } 323 324 EditlineAdapter &GetEditlineAdapter() { return _el_adapter; } 325 }; 326 327 TEST_F(EditlineTestFixture, EditlineReceivesSingleLineText) 328 { 329 // Send it some text via our virtual keyboard. 330 const std::string input_text ("Hello, world"); 331 EXPECT_TRUE(GetEditlineAdapter().SendLine(input_text)); 332 333 // Verify editline sees what we put in. 334 std::string el_reported_line; 335 bool input_interrupted = false; 336 const bool received_line = GetEditlineAdapter().GetLine(el_reported_line, input_interrupted, TIMEOUT_MILLIS); 337 338 EXPECT_TRUE (received_line); 339 EXPECT_FALSE (input_interrupted); 340 EXPECT_EQ (input_text, el_reported_line); 341 } 342 343 TEST_F(EditlineTestFixture, EditlineReceivesMultiLineText) 344 { 345 // Send it some text via our virtual keyboard. 346 std::vector<std::string> input_lines; 347 input_lines.push_back ("int foo()"); 348 input_lines.push_back ("{"); 349 input_lines.push_back ("printf(\"Hello, world\");"); 350 input_lines.push_back ("}"); 351 input_lines.push_back (""); 352 353 EXPECT_TRUE(GetEditlineAdapter().SendLines(input_lines)); 354 355 // Verify editline sees what we put in. 356 lldb_private::StringList el_reported_lines; 357 bool input_interrupted = false; 358 359 EXPECT_TRUE(GetEditlineAdapter().GetLines(el_reported_lines, input_interrupted, TIMEOUT_MILLIS)); 360 EXPECT_FALSE (input_interrupted); 361 362 // Without any auto indentation support, our output should directly match our input. 363 EXPECT_EQ (input_lines.size (), el_reported_lines.GetSize ()); 364 if (input_lines.size () == el_reported_lines.GetSize ()) 365 { 366 for (auto i = 0; i < input_lines.size(); ++i) 367 EXPECT_EQ (input_lines[i], el_reported_lines[i]); 368 } 369 } 370 371 #endif 372