]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - unittests/Editline/EditlineTest.cpp
Vendor import of lldb release_39 branch r276489:
[FreeBSD/FreeBSD.git] / unittests / Editline / EditlineTest.cpp
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