summaryrefslogtreecommitdiff
path: root/unittests/Editline/EditlineTest.cpp
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2016-01-06 20:12:03 +0000
committerDimitry Andric <dim@FreeBSD.org>2016-01-06 20:12:03 +0000
commit9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc (patch)
treedd2a1ddf0476664c2b823409c36cbccd52662ca7 /unittests/Editline/EditlineTest.cpp
parent3bd2e91faeb9eeec1aae82c64a3253afff551cfd (diff)
Notes
Diffstat (limited to 'unittests/Editline/EditlineTest.cpp')
-rw-r--r--unittests/Editline/EditlineTest.cpp368
1 files changed, 368 insertions, 0 deletions
diff --git a/unittests/Editline/EditlineTest.cpp b/unittests/Editline/EditlineTest.cpp
new file mode 100644
index 0000000000000..d82ef4c17c773
--- /dev/null
+++ b/unittests/Editline/EditlineTest.cpp
@@ -0,0 +1,368 @@
+//===-- EditlineTest.cpp -----------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_DISABLE_LIBEDIT
+
+#define EDITLINE_TEST_DUMP_OUTPUT 0
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <memory>
+#include <thread>
+
+#include "gtest/gtest.h"
+
+#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
+{
+ const size_t TIMEOUT_MILLIS = 5000;
+}
+
+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));
+
+ EXPECT_EQ (eoln_length * sizeof (char), input_bytes_written);
+ return eoln_length * sizeof (char) == input_bytes_written;
+}
+
+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
+ }
+}
+
+TEST (EditlineTest, EditlineReceivesSingleLineText)
+{
+ setenv ("TERM", "vt100", 1);
+
+ // Create an editline.
+ EditlineAdapter el_adapter;
+ EXPECT_TRUE (el_adapter.IsValid ());
+ if (!el_adapter.IsValid ())
+ return;
+
+ // Dump output.
+ std::thread el_output_thread( [&] { el_adapter.ConsumeAllOutput (); });
+
+ // Send it some text via our virtual keyboard.
+ const std::string input_text ("Hello, world");
+ EXPECT_TRUE (el_adapter.SendLine (input_text));
+
+ // Verify editline sees what we put in.
+ std::string el_reported_line;
+ bool input_interrupted = false;
+ const bool received_line = el_adapter.GetLine (el_reported_line, input_interrupted, TIMEOUT_MILLIS);
+
+ EXPECT_TRUE (received_line);
+ EXPECT_FALSE (input_interrupted);
+ EXPECT_EQ (input_text, el_reported_line);
+
+ el_adapter.CloseInput();
+ el_output_thread.join();
+}
+
+TEST (EditlineTest, EditlineReceivesMultiLineText)
+{
+ setenv ("TERM", "vt100", 1);
+
+ // Create an editline.
+ EditlineAdapter el_adapter;
+ EXPECT_TRUE (el_adapter.IsValid ());
+ if (!el_adapter.IsValid ())
+ return;
+
+ // Stick editline output/error dumpers on separate threads.
+ std::thread el_output_thread( [&] { el_adapter.ConsumeAllOutput (); });
+
+ // 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 ("");
+
+ EXPECT_TRUE (el_adapter.SendLines (input_lines));
+
+ // Verify editline sees what we put in.
+ lldb_private::StringList el_reported_lines;
+ bool input_interrupted = false;
+
+ EXPECT_TRUE (el_adapter.GetLines (el_reported_lines, input_interrupted, TIMEOUT_MILLIS));
+ 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 ())
+ {
+ for (auto i = 0; i < input_lines.size(); ++i)
+ EXPECT_EQ (input_lines[i], el_reported_lines[i]);
+ }
+
+ el_adapter.CloseInput();
+ el_output_thread.join();
+}
+
+#endif