1 //===-- EditlineTest.cpp --------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "lldb/Host/Config.h"
11 #if LLDB_ENABLE_LIBEDIT
13 #define EDITLINE_TEST_DUMP_OUTPUT 0
18 #include "gmock/gmock.h"
19 #include "gtest/gtest.h"
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"
31 using namespace lldb_private
;
34 const size_t TIMEOUT_MILLIS
= 5000;
39 FilePointer() = delete;
41 FilePointer(const FilePointer
&) = delete;
43 FilePointer(FILE *file_p
) : _file_p(file_p
) {}
46 if (_file_p
!= nullptr) {
47 const int close_result
= fclose(_file_p
);
48 EXPECT_EQ(0, close_result
);
52 operator FILE *() { return _file_p
; }
59 Wraps an Editline class, providing a simple way to feed
60 input (as if from the keyboard) and receive output from Editline.
62 class EditlineAdapter
{
68 bool IsValid() const { return _editline_sp
!= nullptr; }
70 lldb_private::Editline
&GetEditline() { return *_editline_sp
; }
72 bool SendLine(const std::string
&line
);
74 bool SendLines(const std::vector
<std::string
> &lines
);
76 bool GetLine(std::string
&line
, bool &interrupted
, size_t timeout_millis
);
78 bool GetLines(lldb_private::StringList
&lines
, bool &interrupted
,
79 size_t timeout_millis
);
81 void ConsumeAllOutput();
84 bool IsInputComplete(lldb_private::Editline
*editline
,
85 lldb_private::StringList
&lines
);
87 std::recursive_mutex output_mutex
;
88 std::unique_ptr
<lldb_private::Editline
> _editline_sp
;
91 int _pty_primary_fd
= -1;
92 int _pty_secondary_fd
= -1;
94 std::unique_ptr
<FilePointer
> _el_secondary_file
;
97 EditlineAdapter::EditlineAdapter()
98 : _editline_sp(), _pty(), _el_secondary_file() {
99 lldb_private::Status error
;
101 // Open the first primary pty available.
102 EXPECT_THAT_ERROR(_pty
.OpenFirstAvailablePrimary(O_RDWR
), llvm::Succeeded());
104 // Grab the primary 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_primary_fd
= _pty
.GetPrimaryFileDescriptor();
109 // Open the corresponding secondary pty.
110 EXPECT_THAT_ERROR(_pty
.OpenSecondary(O_RDWR
), llvm::Succeeded());
111 _pty_secondary_fd
= _pty
.GetSecondaryFileDescriptor();
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)
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
, output_mutex
));
122 _editline_sp
->SetPrompt("> ");
124 // Hookup our input complete callback.
125 auto input_complete_cb
= [this](Editline
*editline
, StringList
&lines
) {
126 return this->IsInputComplete(editline
, lines
);
128 _editline_sp
->SetIsInputCompleteCallback(input_complete_cb
);
131 void EditlineAdapter::CloseInput() {
132 if (_el_secondary_file
!= nullptr)
133 _el_secondary_file
.reset(nullptr);
136 bool EditlineAdapter::SendLine(const std::string
&line
) {
137 // Ensure we're valid before proceeding.
141 // Write the line out to the pipe connected to editline's input.
142 ssize_t input_bytes_written
=
143 ::write(_pty_primary_fd
, line
.c_str(),
144 line
.length() * sizeof(std::string::value_type
));
146 const char *eoln
= "\n";
147 const size_t eoln_length
= strlen(eoln
);
148 input_bytes_written
=
149 ::write(_pty_primary_fd
, eoln
, eoln_length
* sizeof(char));
151 EXPECT_NE(-1, input_bytes_written
) << strerror(errno
);
152 EXPECT_EQ(eoln_length
* sizeof(char), size_t(input_bytes_written
));
153 return eoln_length
* sizeof(char) == size_t(input_bytes_written
);
156 bool EditlineAdapter::SendLines(const std::vector
<std::string
> &lines
) {
157 for (auto &line
: lines
) {
158 #if EDITLINE_TEST_DUMP_OUTPUT
159 printf("<stdin> sending line \"%s\"\n", line
.c_str());
167 // We ignore the timeout for now.
168 bool EditlineAdapter::GetLine(std::string
&line
, bool &interrupted
,
169 size_t /* timeout_millis */) {
170 // Ensure we're valid before proceeding.
174 _editline_sp
->GetLine(line
, interrupted
);
178 bool EditlineAdapter::GetLines(lldb_private::StringList
&lines
,
179 bool &interrupted
, size_t /* timeout_millis */) {
180 // Ensure we're valid before proceeding.
184 _editline_sp
->GetLines(1, lines
, interrupted
);
188 bool EditlineAdapter::IsInputComplete(lldb_private::Editline
*editline
,
189 lldb_private::StringList
&lines
) {
190 // We'll call ourselves complete if we've received a balanced set of braces.
191 int start_block_count
= 0;
192 int brace_balance
= 0;
194 for (const std::string
&line
: lines
) {
195 for (auto ch
: line
) {
199 } else if (ch
== '}')
204 return (start_block_count
> 0) && (brace_balance
== 0);
207 void EditlineAdapter::ConsumeAllOutput() {
208 FilePointer
output_file(fdopen(_pty_primary_fd
, "r"));
211 while ((ch
= fgetc(output_file
)) != EOF
) {
212 #if EDITLINE_TEST_DUMP_OUTPUT
213 char display_str
[] = {0, 0, 0};
216 display_str
[0] = '\\';
217 display_str
[1] = 't';
220 display_str
[0] = '\\';
221 display_str
[1] = 'n';
224 display_str
[0] = '\\';
225 display_str
[1] = 'r';
231 printf("<stdout> 0x%02x (%03d) (%s)\n", ch
, ch
, display_str
);
237 class EditlineTestFixture
: public ::testing::Test
{
238 SubsystemRAII
<FileSystem
> subsystems
;
239 EditlineAdapter _el_adapter
;
240 std::shared_ptr
<std::thread
> _sp_output_thread
;
243 static void SetUpTestCase() {
244 // We need a TERM set properly for editline to work as expected.
245 setenv("TERM", "vt100", 1);
248 void SetUp() override
{
249 // Validate the editline adapter.
250 EXPECT_TRUE(_el_adapter
.IsValid());
251 if (!_el_adapter
.IsValid())
256 std::make_shared
<std::thread
>([&] { _el_adapter
.ConsumeAllOutput(); });
259 void TearDown() override
{
260 _el_adapter
.CloseInput();
261 if (_sp_output_thread
)
262 _sp_output_thread
->join();
265 EditlineAdapter
&GetEditlineAdapter() { return _el_adapter
; }
268 TEST_F(EditlineTestFixture
, EditlineReceivesSingleLineText
) {
269 // Send it some text via our virtual keyboard.
270 const std::string
input_text("Hello, world");
271 EXPECT_TRUE(GetEditlineAdapter().SendLine(input_text
));
273 // Verify editline sees what we put in.
274 std::string el_reported_line
;
275 bool input_interrupted
= false;
276 const bool received_line
= GetEditlineAdapter().GetLine(
277 el_reported_line
, input_interrupted
, TIMEOUT_MILLIS
);
279 EXPECT_TRUE(received_line
);
280 EXPECT_FALSE(input_interrupted
);
281 EXPECT_EQ(input_text
, el_reported_line
);
284 TEST_F(EditlineTestFixture
, EditlineReceivesMultiLineText
) {
285 // Send it some text via our virtual keyboard.
286 std::vector
<std::string
> input_lines
;
287 input_lines
.push_back("int foo()");
288 input_lines
.push_back("{");
289 input_lines
.push_back("printf(\"Hello, world\");");
290 input_lines
.push_back("}");
291 input_lines
.push_back("");
293 EXPECT_TRUE(GetEditlineAdapter().SendLines(input_lines
));
295 // Verify editline sees what we put in.
296 lldb_private::StringList el_reported_lines
;
297 bool input_interrupted
= false;
299 EXPECT_TRUE(GetEditlineAdapter().GetLines(el_reported_lines
,
300 input_interrupted
, TIMEOUT_MILLIS
));
301 EXPECT_FALSE(input_interrupted
);
303 // Without any auto indentation support, our output should directly match our
305 std::vector
<std::string
> reported_lines
;
306 for (const std::string
&line
: el_reported_lines
)
307 reported_lines
.push_back(line
);
309 EXPECT_THAT(reported_lines
, testing::ContainerEq(input_lines
));