2016-01-23 05:58:55 +08:00
|
|
|
//===-- EditlineTest.cpp ----------------------------------------*- C++ -*-===//
|
2015-10-26 05:42:35 +08:00
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2015-10-30 10:54:52 +08:00
|
|
|
#ifndef LLDB_DISABLE_LIBEDIT
|
|
|
|
|
|
|
|
#define EDITLINE_TEST_DUMP_OUTPUT 0
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
#include <thread>
|
|
|
|
|
2015-10-26 05:42:35 +08:00
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
2015-10-30 10:54:52 +08:00
|
|
|
#include "lldb/Core/Error.h"
|
|
|
|
#include "lldb/Core/StringList.h"
|
|
|
|
#include "lldb/Host/Editline.h"
|
|
|
|
#include "lldb/Host/Pipe.h"
|
|
|
|
#include "lldb/Utility/PseudoTerminal.h"
|
|
|
|
|
|
|
|
namespace
|
2015-10-26 05:42:35 +08:00
|
|
|
{
|
2015-10-30 10:54:52 +08:00
|
|
|
const size_t TIMEOUT_MILLIS = 5000;
|
2015-10-26 05:42:35 +08:00
|
|
|
}
|
2015-10-30 10:54:52 +08:00
|
|
|
|
|
|
|
class FilePointer
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
|
|
|
FilePointer () = delete;
|
|
|
|
|
|
|
|
FilePointer (const FilePointer&) = delete;
|
|
|
|
|
|
|
|
FilePointer (FILE *file_p)
|
|
|
|
: _file_p (file_p)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
~FilePointer ()
|
|
|
|
{
|
|
|
|
if (_file_p != nullptr)
|
|
|
|
{
|
|
|
|
const int close_result = fclose (_file_p);
|
|
|
|
EXPECT_EQ(0, close_result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
operator FILE* ()
|
|
|
|
{
|
|
|
|
return _file_p;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
FILE *_file_p;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
Wraps an Editline class, providing a simple way to feed
|
|
|
|
input (as if from the keyboard) and receive output from Editline.
|
|
|
|
*/
|
|
|
|
class EditlineAdapter
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
|
|
|
EditlineAdapter ();
|
|
|
|
|
|
|
|
void
|
|
|
|
CloseInput ();
|
|
|
|
|
|
|
|
bool
|
|
|
|
IsValid () const
|
|
|
|
{
|
|
|
|
return _editline_sp.get () != nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
lldb_private::Editline&
|
|
|
|
GetEditline ()
|
|
|
|
{
|
|
|
|
return *_editline_sp;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
SendLine (const std::string &line);
|
|
|
|
|
|
|
|
bool
|
|
|
|
SendLines (const std::vector<std::string> &lines);
|
|
|
|
|
|
|
|
bool
|
|
|
|
GetLine (std::string &line, bool &interrupted, size_t timeout_millis);
|
|
|
|
|
|
|
|
bool
|
|
|
|
GetLines (lldb_private::StringList &lines, bool &interrupted, size_t timeout_millis);
|
|
|
|
|
|
|
|
void
|
|
|
|
ConsumeAllOutput ();
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
static bool
|
|
|
|
IsInputComplete (
|
|
|
|
lldb_private::Editline * editline,
|
|
|
|
lldb_private::StringList & lines,
|
|
|
|
void * baton);
|
|
|
|
|
|
|
|
std::unique_ptr<lldb_private::Editline> _editline_sp;
|
|
|
|
|
|
|
|
lldb_utility::PseudoTerminal _pty;
|
|
|
|
int _pty_master_fd;
|
|
|
|
int _pty_slave_fd;
|
|
|
|
|
|
|
|
std::unique_ptr<FilePointer> _el_slave_file;
|
|
|
|
};
|
|
|
|
|
|
|
|
EditlineAdapter::EditlineAdapter () :
|
|
|
|
_editline_sp (),
|
|
|
|
_pty (),
|
|
|
|
_pty_master_fd (-1),
|
|
|
|
_pty_slave_fd (-1),
|
|
|
|
_el_slave_file ()
|
|
|
|
{
|
|
|
|
lldb_private::Error error;
|
|
|
|
|
|
|
|
// Open the first master pty available.
|
|
|
|
char error_string[256];
|
|
|
|
error_string[0] = '\0';
|
|
|
|
if (!_pty.OpenFirstAvailableMaster (O_RDWR, error_string, sizeof (error_string)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "failed to open first available master pty: '%s'\n", error_string);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grab the master fd. This is a file descriptor we will:
|
|
|
|
// (1) write to when we want to send input to editline.
|
|
|
|
// (2) read from when we want to see what editline sends back.
|
|
|
|
_pty_master_fd = _pty.GetMasterFileDescriptor();
|
|
|
|
|
|
|
|
// Open the corresponding slave pty.
|
|
|
|
if (!_pty.OpenSlave (O_RDWR, error_string, sizeof (error_string)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "failed to open slave pty: '%s'\n", error_string);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_pty_slave_fd = _pty.GetSlaveFileDescriptor();
|
|
|
|
|
|
|
|
_el_slave_file.reset (new FilePointer (fdopen (_pty_slave_fd, "rw")));
|
|
|
|
EXPECT_FALSE (nullptr == *_el_slave_file);
|
|
|
|
if (*_el_slave_file == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Create an Editline instance.
|
|
|
|
_editline_sp.reset (new lldb_private::Editline("gtest editor", *_el_slave_file, *_el_slave_file, *_el_slave_file, false));
|
|
|
|
_editline_sp->SetPrompt ("> ");
|
|
|
|
|
|
|
|
// Hookup our input complete callback.
|
|
|
|
_editline_sp->SetIsInputCompleteCallback(IsInputComplete, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
EditlineAdapter::CloseInput ()
|
|
|
|
{
|
|
|
|
if (_el_slave_file != nullptr)
|
|
|
|
_el_slave_file.reset (nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
EditlineAdapter::SendLine (const std::string &line)
|
|
|
|
{
|
|
|
|
// Ensure we're valid before proceeding.
|
|
|
|
if (!IsValid ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Write the line out to the pipe connected to editline's input.
|
|
|
|
ssize_t input_bytes_written =
|
|
|
|
::write (_pty_master_fd,
|
|
|
|
line.c_str(),
|
|
|
|
line.length() * sizeof (std::string::value_type));
|
|
|
|
|
|
|
|
const char *eoln = "\n";
|
|
|
|
const size_t eoln_length = strlen(eoln);
|
|
|
|
input_bytes_written =
|
|
|
|
::write (_pty_master_fd,
|
|
|
|
eoln,
|
|
|
|
eoln_length * sizeof (char));
|
|
|
|
|
2016-03-11 17:00:23 +08:00
|
|
|
EXPECT_NE(-1, input_bytes_written) << strerror(errno);
|
|
|
|
EXPECT_EQ (eoln_length * sizeof (char), size_t(input_bytes_written));
|
|
|
|
return eoln_length * sizeof (char) == size_t(input_bytes_written);
|
2015-10-30 10:54:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
EditlineAdapter::SendLines (const std::vector<std::string> &lines)
|
|
|
|
{
|
|
|
|
for (auto &line : lines)
|
|
|
|
{
|
|
|
|
#if EDITLINE_TEST_DUMP_OUTPUT
|
|
|
|
printf ("<stdin> sending line \"%s\"\n", line.c_str());
|
|
|
|
#endif
|
|
|
|
if (!SendLine (line))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We ignore the timeout for now.
|
|
|
|
bool
|
|
|
|
EditlineAdapter::GetLine (std::string &line, bool &interrupted, size_t /* timeout_millis */)
|
|
|
|
{
|
|
|
|
// Ensure we're valid before proceeding.
|
|
|
|
if (!IsValid ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
_editline_sp->GetLine (line, interrupted);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
EditlineAdapter::GetLines (lldb_private::StringList &lines, bool &interrupted, size_t /* timeout_millis */)
|
|
|
|
{
|
|
|
|
// Ensure we're valid before proceeding.
|
|
|
|
if (!IsValid ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
_editline_sp->GetLines (1, lines, interrupted);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
EditlineAdapter::IsInputComplete (
|
|
|
|
lldb_private::Editline * editline,
|
|
|
|
lldb_private::StringList & lines,
|
|
|
|
void * baton)
|
|
|
|
{
|
|
|
|
// We'll call ourselves complete if we've received a balanced set of braces.
|
|
|
|
int start_block_count = 0;
|
|
|
|
int brace_balance = 0;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < lines.GetSize (); ++i)
|
|
|
|
{
|
|
|
|
for (auto ch : lines[i])
|
|
|
|
{
|
|
|
|
if (ch == '{')
|
|
|
|
{
|
|
|
|
++start_block_count;
|
|
|
|
++brace_balance;
|
|
|
|
}
|
|
|
|
else if (ch == '}')
|
|
|
|
--brace_balance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (start_block_count > 0) && (brace_balance == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
EditlineAdapter::ConsumeAllOutput ()
|
|
|
|
{
|
|
|
|
FilePointer output_file (fdopen (_pty_master_fd, "r"));
|
|
|
|
|
|
|
|
int ch;
|
|
|
|
while ((ch = fgetc(output_file)) != EOF)
|
|
|
|
{
|
|
|
|
#if EDITLINE_TEST_DUMP_OUTPUT
|
|
|
|
char display_str[] = { 0, 0, 0 };
|
|
|
|
switch (ch)
|
|
|
|
{
|
|
|
|
case '\t':
|
|
|
|
display_str[0] = '\\';
|
|
|
|
display_str[1] = 't';
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
display_str[0] = '\\';
|
|
|
|
display_str[1] = 'n';
|
|
|
|
break;
|
|
|
|
case '\r':
|
|
|
|
display_str[0] = '\\';
|
|
|
|
display_str[1] = 'r';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
display_str[0] = ch;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
printf ("<stdout> 0x%02x (%03d) (%s)\n", ch, ch, display_str);
|
|
|
|
// putc(ch, stdout);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
class EditlineTestFixture : public ::testing::Test
|
2015-10-30 10:54:52 +08:00
|
|
|
{
|
2016-01-23 05:58:55 +08:00
|
|
|
private:
|
|
|
|
EditlineAdapter _el_adapter;
|
|
|
|
std::shared_ptr<std::thread> _sp_output_thread;
|
2015-10-30 10:54:52 +08:00
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
public:
|
|
|
|
void SetUp()
|
|
|
|
{
|
|
|
|
// We need a TERM set properly for editline to work as expected.
|
|
|
|
setenv("TERM", "vt100", 1);
|
|
|
|
|
|
|
|
// Validate the editline adapter.
|
|
|
|
EXPECT_TRUE(_el_adapter.IsValid());
|
|
|
|
if (!_el_adapter.IsValid())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Dump output.
|
|
|
|
_sp_output_thread.reset(new std::thread([&] { _el_adapter.ConsumeAllOutput(); }));
|
|
|
|
}
|
|
|
|
|
|
|
|
void TearDown()
|
|
|
|
{
|
|
|
|
_el_adapter.CloseInput();
|
|
|
|
if (_sp_output_thread)
|
|
|
|
_sp_output_thread->join();
|
|
|
|
}
|
2015-10-30 10:54:52 +08:00
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
EditlineAdapter &GetEditlineAdapter() { return _el_adapter; }
|
|
|
|
};
|
2015-10-30 10:54:52 +08:00
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
TEST_F(EditlineTestFixture, EditlineReceivesSingleLineText)
|
|
|
|
{
|
2015-10-30 10:54:52 +08:00
|
|
|
// Send it some text via our virtual keyboard.
|
|
|
|
const std::string input_text ("Hello, world");
|
2016-01-23 05:58:55 +08:00
|
|
|
EXPECT_TRUE(GetEditlineAdapter().SendLine(input_text));
|
2015-10-30 10:54:52 +08:00
|
|
|
|
|
|
|
// Verify editline sees what we put in.
|
|
|
|
std::string el_reported_line;
|
|
|
|
bool input_interrupted = false;
|
2016-01-23 05:58:55 +08:00
|
|
|
const bool received_line = GetEditlineAdapter().GetLine(el_reported_line, input_interrupted, TIMEOUT_MILLIS);
|
2015-10-30 10:54:52 +08:00
|
|
|
|
|
|
|
EXPECT_TRUE (received_line);
|
|
|
|
EXPECT_FALSE (input_interrupted);
|
|
|
|
EXPECT_EQ (input_text, el_reported_line);
|
|
|
|
}
|
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
TEST_F(EditlineTestFixture, EditlineReceivesMultiLineText)
|
2015-10-30 10:54:52 +08:00
|
|
|
{
|
|
|
|
// Send it some text via our virtual keyboard.
|
|
|
|
std::vector<std::string> input_lines;
|
|
|
|
input_lines.push_back ("int foo()");
|
|
|
|
input_lines.push_back ("{");
|
|
|
|
input_lines.push_back ("printf(\"Hello, world\");");
|
|
|
|
input_lines.push_back ("}");
|
|
|
|
input_lines.push_back ("");
|
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
EXPECT_TRUE(GetEditlineAdapter().SendLines(input_lines));
|
2015-10-30 10:54:52 +08:00
|
|
|
|
|
|
|
// Verify editline sees what we put in.
|
|
|
|
lldb_private::StringList el_reported_lines;
|
|
|
|
bool input_interrupted = false;
|
|
|
|
|
2016-01-23 05:58:55 +08:00
|
|
|
EXPECT_TRUE(GetEditlineAdapter().GetLines(el_reported_lines, input_interrupted, TIMEOUT_MILLIS));
|
2015-10-30 10:54:52 +08:00
|
|
|
EXPECT_FALSE (input_interrupted);
|
|
|
|
|
|
|
|
// Without any auto indentation support, our output should directly match our input.
|
|
|
|
EXPECT_EQ (input_lines.size (), el_reported_lines.GetSize ());
|
|
|
|
if (input_lines.size () == el_reported_lines.GetSize ())
|
|
|
|
{
|
2016-03-11 17:00:23 +08:00
|
|
|
for (size_t i = 0; i < input_lines.size(); ++i)
|
2015-10-30 10:54:52 +08:00
|
|
|
EXPECT_EQ (input_lines[i], el_reported_lines[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|