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