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