1 //===-- EditlineTest.cpp ----------------------------------------*- C++ -*-===//
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 static bool IsInputComplete(lldb_private::Editline
*editline
,
85 lldb_private::StringList
&lines
, void *baton
);
87 std::unique_ptr
<lldb_private::Editline
> _editline_sp
;
93 std::unique_ptr
<FilePointer
> _el_slave_file
;
96 EditlineAdapter::EditlineAdapter()
97 : _editline_sp(), _pty(), _pty_master_fd(-1), _pty_slave_fd(-1),
99 lldb_private::Status error
;
101 // Open the first master pty available.
102 char error_string
[256];
103 error_string
[0] = '\0';
104 if (!_pty
.OpenFirstAvailableMaster(O_RDWR
, error_string
,
105 sizeof(error_string
))) {
106 fprintf(stderr
, "failed to open first available master pty: '%s'\n",
111 // Grab the master fd. This is a file descriptor we will:
112 // (1) write to when we want to send input to editline.
113 // (2) read from when we want to see what editline sends back.
114 _pty_master_fd
= _pty
.GetMasterFileDescriptor();
116 // Open the corresponding slave pty.
117 if (!_pty
.OpenSlave(O_RDWR
, error_string
, sizeof(error_string
))) {
118 fprintf(stderr
, "failed to open slave pty: '%s'\n", error_string
);
121 _pty_slave_fd
= _pty
.GetSlaveFileDescriptor();
123 _el_slave_file
.reset(new FilePointer(fdopen(_pty_slave_fd
, "rw")));
124 EXPECT_FALSE(nullptr == *_el_slave_file
);
125 if (*_el_slave_file
== nullptr)
128 // Create an Editline instance.
129 _editline_sp
.reset(new lldb_private::Editline("gtest editor", *_el_slave_file
,
131 *_el_slave_file
, false));
132 _editline_sp
->SetPrompt("> ");
134 // Hookup our input complete callback.
135 _editline_sp
->SetIsInputCompleteCallback(IsInputComplete
, this);
138 void EditlineAdapter::CloseInput() {
139 if (_el_slave_file
!= nullptr)
140 _el_slave_file
.reset(nullptr);
143 bool EditlineAdapter::SendLine(const std::string
&line
) {
144 // Ensure we're valid before proceeding.
148 // Write the line out to the pipe connected to editline's input.
149 ssize_t input_bytes_written
=
150 ::write(_pty_master_fd
, line
.c_str(),
151 line
.length() * sizeof(std::string::value_type
));
153 const char *eoln
= "\n";
154 const size_t eoln_length
= strlen(eoln
);
155 input_bytes_written
=
156 ::write(_pty_master_fd
, eoln
, eoln_length
* sizeof(char));
158 EXPECT_NE(-1, input_bytes_written
) << strerror(errno
);
159 EXPECT_EQ(eoln_length
* sizeof(char), size_t(input_bytes_written
));
160 return eoln_length
* sizeof(char) == size_t(input_bytes_written
);
163 bool EditlineAdapter::SendLines(const std::vector
<std::string
> &lines
) {
164 for (auto &line
: lines
) {
165 #if EDITLINE_TEST_DUMP_OUTPUT
166 printf("<stdin> sending line \"%s\"\n", line
.c_str());
174 // We ignore the timeout for now.
175 bool EditlineAdapter::GetLine(std::string
&line
, bool &interrupted
,
176 size_t /* timeout_millis */) {
177 // Ensure we're valid before proceeding.
181 _editline_sp
->GetLine(line
, interrupted
);
185 bool EditlineAdapter::GetLines(lldb_private::StringList
&lines
,
186 bool &interrupted
, size_t /* timeout_millis */) {
187 // Ensure we're valid before proceeding.
191 _editline_sp
->GetLines(1, lines
, interrupted
);
195 bool EditlineAdapter::IsInputComplete(lldb_private::Editline
*editline
,
196 lldb_private::StringList
&lines
,
198 // We'll call ourselves complete if we've received a balanced set of braces.
199 int start_block_count
= 0;
200 int brace_balance
= 0;
202 for (const std::string
&line
: lines
) {
203 for (auto ch
: line
) {
207 } else if (ch
== '}')
212 return (start_block_count
> 0) && (brace_balance
== 0);
215 void EditlineAdapter::ConsumeAllOutput() {
216 FilePointer
output_file(fdopen(_pty_master_fd
, "r"));
219 while ((ch
= fgetc(output_file
)) != EOF
) {
220 #if EDITLINE_TEST_DUMP_OUTPUT
221 char display_str
[] = {0, 0, 0};
224 display_str
[0] = '\\';
225 display_str
[1] = 't';
228 display_str
[0] = '\\';
229 display_str
[1] = 'n';
232 display_str
[0] = '\\';
233 display_str
[1] = 'r';
239 printf("<stdout> 0x%02x (%03d) (%s)\n", ch
, ch
, display_str
);
245 class EditlineTestFixture
: public ::testing::Test
{
246 SubsystemRAII
<FileSystem
> subsystems
;
247 EditlineAdapter _el_adapter
;
248 std::shared_ptr
<std::thread
> _sp_output_thread
;
251 static void SetUpTestCase() {
252 // We need a TERM set properly for editline to work as expected.
253 setenv("TERM", "vt100", 1);
256 void SetUp() override
{
257 // Validate the editline adapter.
258 EXPECT_TRUE(_el_adapter
.IsValid());
259 if (!_el_adapter
.IsValid())
264 std::make_shared
<std::thread
>([&] { _el_adapter
.ConsumeAllOutput(); });
267 void TearDown() override
{
268 _el_adapter
.CloseInput();
269 if (_sp_output_thread
)
270 _sp_output_thread
->join();
273 EditlineAdapter
&GetEditlineAdapter() { return _el_adapter
; }
276 TEST_F(EditlineTestFixture
, EditlineReceivesSingleLineText
) {
277 // Send it some text via our virtual keyboard.
278 const std::string
input_text("Hello, world");
279 EXPECT_TRUE(GetEditlineAdapter().SendLine(input_text
));
281 // Verify editline sees what we put in.
282 std::string el_reported_line
;
283 bool input_interrupted
= false;
284 const bool received_line
= GetEditlineAdapter().GetLine(
285 el_reported_line
, input_interrupted
, TIMEOUT_MILLIS
);
287 EXPECT_TRUE(received_line
);
288 EXPECT_FALSE(input_interrupted
);
289 EXPECT_EQ(input_text
, el_reported_line
);
292 TEST_F(EditlineTestFixture
, EditlineReceivesMultiLineText
) {
293 // Send it some text via our virtual keyboard.
294 std::vector
<std::string
> input_lines
;
295 input_lines
.push_back("int foo()");
296 input_lines
.push_back("{");
297 input_lines
.push_back("printf(\"Hello, world\");");
298 input_lines
.push_back("}");
299 input_lines
.push_back("");
301 EXPECT_TRUE(GetEditlineAdapter().SendLines(input_lines
));
303 // Verify editline sees what we put in.
304 lldb_private::StringList el_reported_lines
;
305 bool input_interrupted
= false;
307 EXPECT_TRUE(GetEditlineAdapter().GetLines(el_reported_lines
,
308 input_interrupted
, TIMEOUT_MILLIS
));
309 EXPECT_FALSE(input_interrupted
);
311 // Without any auto indentation support, our output should directly match our
313 std::vector
<std::string
> reported_lines
;
314 for (const std::string
&line
: el_reported_lines
)
315 reported_lines
.push_back(line
);
317 EXPECT_THAT(reported_lines
, testing::ContainerEq(input_lines
));