nealsid updated this revision to Diff 392497.
nealsid edited the summary of this revision.
nealsid added a comment.
Testing ARC, no action necessary.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D115126/new/
https://reviews.llvm.org/D115126
Files:
lldb/include/lldb/Host/Editline.h
lldb/source/Host/common/Editline.cpp
lldb/unittests/Editline/EditlineTest.cpp
Index: lldb/unittests/Editline/EditlineTest.cpp
===================================================================
--- lldb/unittests/Editline/EditlineTest.cpp
+++ lldb/unittests/Editline/EditlineTest.cpp
@@ -17,6 +17,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <chrono>
#include <memory>
#include <thread>
@@ -28,7 +29,12 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StringList.h"
+using namespace std::chrono_literals;
+
+#define ESCAPE "\x1b"
+
using namespace lldb_private;
+using namespace testing;
namespace {
const size_t TIMEOUT_MILLIS = 5000;
@@ -80,6 +86,12 @@
void ConsumeAllOutput();
+ const std::string GetEditlineOutput() {
+ std::unique_lock<std::mutex> lock(outputReadMutex);
+ outputReadCv.wait(lock);
+ return testOutputBuffer.str();
+ }
+
private:
bool IsInputComplete(lldb_private::Editline *editline,
lldb_private::StringList &lines);
@@ -91,6 +103,9 @@
int _pty_secondary_fd;
std::unique_ptr<FilePointer> _el_secondary_file;
+ std::stringstream testOutputBuffer;
+ std::mutex outputReadMutex;
+ std::condition_variable outputReadCv;
};
EditlineAdapter::EditlineAdapter()
@@ -110,11 +125,21 @@
EXPECT_THAT_ERROR(_pty.OpenSecondary(O_RDWR), llvm::Succeeded());
_pty_secondary_fd = _pty.GetSecondaryFileDescriptor();
- _el_secondary_file.reset(new FilePointer(fdopen(_pty_secondary_fd, "rw")));
+ _el_secondary_file.reset(new FilePointer(fdopen(_pty_secondary_fd, "w+")));
EXPECT_FALSE(nullptr == *_el_secondary_file);
if (*_el_secondary_file == nullptr)
return;
+ // We have to set the output stream we pass to Editline as not using
+ // buffered I/O. Otherwise we are missing editline's output when we
+ // close the stream in the keybinding test (i.e. the EOF comes
+ // before data previously written to the stream by editline). This
+ // behavior isn't as I understand the spec becuse fclose should
+ // flush the stream, but my best guess is that it's some unexpected
+ // interaction with stream I/O and ptys.
+ EXPECT_EQ(setvbuf(*_el_secondary_file, nullptr, _IONBF, 0), 0)
+ << "Could not set editline output stream to use unbuffered I/O.";
+
// Create an Editline instance.
_editline_sp.reset(new lldb_private::Editline(
"gtest editor", *_el_secondary_file, *_el_secondary_file,
@@ -207,7 +232,7 @@
void EditlineAdapter::ConsumeAllOutput() {
FilePointer output_file(fdopen(_pty_primary_fd, "r"));
- int ch;
+ char ch;
while ((ch = fgetc(output_file)) != EOF) {
#if EDITLINE_TEST_DUMP_OUTPUT
char display_str[] = {0, 0, 0};
@@ -229,15 +254,18 @@
break;
}
printf("<stdout> 0x%02x (%03d) (%s)\n", ch, ch, display_str);
-// putc(ch, stdout);
#endif
+ if (ch == '\n') {
+ outputReadCv.notify_one();
+ }
+ testOutputBuffer << ch;
}
}
class EditlineTestFixture : public ::testing::Test {
SubsystemRAII<FileSystem> subsystems;
EditlineAdapter _el_adapter;
- std::shared_ptr<std::thread> _sp_output_thread;
+ std::thread _sp_output_thread;
public:
static void SetUpTestCase() {
@@ -252,14 +280,13 @@
return;
// Dump output.
- _sp_output_thread =
- std::make_shared<std::thread>([&] { _el_adapter.ConsumeAllOutput(); });
+ _sp_output_thread = std::thread([&] { _el_adapter.ConsumeAllOutput(); });
}
void TearDown() override {
_el_adapter.CloseInput();
- if (_sp_output_thread)
- _sp_output_thread->join();
+ if (_sp_output_thread.joinable())
+ _sp_output_thread.join();
}
EditlineAdapter &GetEditlineAdapter() { return _el_adapter; }
@@ -309,4 +336,131 @@
EXPECT_THAT(reported_lines, testing::ContainerEq(input_lines));
}
+namespace lldb_private {
+
+// Parameter structure for parameterized tests.
+struct KeybindingTestValue {
+ // A number that is used to name the test, so test output can be
+ // mapped back to a specific input.
+ const std::string testNumber;
+ // Whether this keyboard shortcut is only bound in multi-line mode.
+ bool multilineOnly;
+ // The actual key sequence.
+ const std::string keySequence;
+ // The command the key sequence is supposed to execute.
+ const std::string commandName;
+ // This is initialized to the keySequence by default, but gtest has
+ // some errors when the test name as created by the overloaded
+ // operator<< has embedded newlines. This is even true when we
+ // specify a custom test name function, as we do below when we
+ // instantiate the test case. In cases where the keyboard shortcut
+ // has a newline or carriage return, this field in the struct can be
+ // set to something that is printable.
+ const std::string &printableKeySequence = keySequence;
+};
+
+std::ostream &operator<<(std::ostream &os, const KeybindingTestValue &kbtv) {
+ return os << "{" << kbtv.printableKeySequence << " => " << kbtv.commandName
+ << " (multiline only: " << kbtv.multilineOnly << ")}";
+}
+
+// The keyboard shortcuts that we're testing.
+const KeybindingTestValue keySequences[] = {
+ {"1", false, "^w", "ed-delete-prev-word"},
+ {"2", false, "\t", "lldb-complete"},
+ {"3", false, ESCAPE "[1;5C", "em-next-word"},
+ {"4", false, ESCAPE "[1;5D", "ed-prev-word"},
+ {"5", false, ESCAPE "[5C", "em-next-word"},
+ {"6", false, ESCAPE "[5D", "ed-prev-word"},
+ {"7", false, ESCAPE ESCAPE "[C", "em-next-word"},
+ {"8", false, ESCAPE ESCAPE "[D", "ed-prev-word"},
+ {"9", true, "\n", "lldb-end-or-add-line", "<CR>"},
+ {"10", true, "\r", "lldb-end-or-add-line", "<LF>"},
+ {"11", true, ESCAPE "\n", "lldb-break-line", ESCAPE "<CR>"},
+ {"12", true, ESCAPE "\r", "lldb-break-line", ESCAPE "<LF>"},
+ {"13", true, "^p", "lldb-previous-line"},
+ {"14", true, "^n", "lldb-next-line"},
+ {"15", true, "^?", "lldb-delete-previous-char"},
+ {"16", true, "^d", "lldb-delete-next-char"},
+ {"17", true, ESCAPE "[3~", "lldb-delete-next-char"},
+ {"18", true, ESCAPE "[\\^", "lldb-revert-line"},
+ {"19", true, ESCAPE "<", "lldb-buffer-start"},
+ {"20", true, ESCAPE ">", "lldb-buffer-end"},
+ {"21", true, ESCAPE "[A", "lldb-previous-line"},
+ {"22", true, ESCAPE "[B", "lldb-next-line"},
+ {"23", true, ESCAPE ESCAPE "[A", "lldb-previous-history"},
+ {"24", true, ESCAPE ESCAPE "[B", "lldb-next-history"},
+ {"25", true, ESCAPE "[1;3A", "lldb-previous-history"},
+ {"26", true, ESCAPE "[1;3B", "lldb-next-history"},
+};
+
+class EditlineKeyboardBindingTest
+ : public EditlineTestFixture,
+ public testing::WithParamInterface<KeybindingTestValue> {};
+
+// Helper method to call into libedit to have it output a keyboard
+// shortcut mapping.
+void retrieveEditlineShortcutKey(::EditLine *el,
+ const std::string &keySequence) {
+ ASSERT_EQ(el_set(el, EL_BIND, keySequence.c_str(), nullptr), 0)
+ << "Retrieving editline keybinding failed for " << keySequence;
+}
+
+// Helper function that, given a test parameter value, returns a
+// string that matches the editline output for a given shortcut key.
+const std::string editlineExpectedOutputRegex(KeybindingTestValue kbtv) {
+ std::stringstream builder;
+ builder << ".*" << kbtv.commandName << ".*";
+ return builder.str();
+}
+
+// Test cases for editline in single-line mode.
+TEST_P(EditlineKeyboardBindingTest, SingleLineEditlineKeybindings) {
+ KeybindingTestValue kbtv = GetParam();
+
+ auto &editLine = GetEditlineAdapter().GetEditline();
+
+ editLine.ConfigureEditor(false);
+
+ retrieveEditlineShortcutKey(editLine.m_editline, kbtv.keySequence);
+ const std::string output = GetEditlineAdapter().GetEditlineOutput();
+ // If the shortcut key is only in multiline mode, verify that it is
+ // not mapped to the command. It could still be mapped by default,
+ // so we just check if our command doesn't appear in the output.
+ if (kbtv.multilineOnly) {
+ ASSERT_THAT(output, Not(HasSubstr(kbtv.commandName)))
+ << "Multiline only key was bound in single-line mode.";
+ return;
+ }
+
+ // Otherwise, compare the output to make sure our command is mapped
+ // to the shortcut key.
+ ASSERT_THAT(output, ContainsRegex(editlineExpectedOutputRegex(kbtv)))
+ << "Key sequence was not bound to expected command name.";
+}
+
+// Test cases for editline in multi-line mode.
+TEST_P(EditlineKeyboardBindingTest, MultiLineEditlineKeybindings) {
+ KeybindingTestValue kbtv = GetParam();
+
+ auto &editLine = GetEditlineAdapter().GetEditline();
+
+ editLine.ConfigureEditor(true);
+
+ retrieveEditlineShortcutKey(editLine.m_editline, kbtv.keySequence);
+
+ const std::string output = GetEditlineAdapter().GetEditlineOutput();
+
+ ASSERT_THAT(output, ContainsRegex(editlineExpectedOutputRegex(kbtv)))
+ << "Key sequence was not bound to expected command name.";
+}
+
+INSTANTIATE_TEST_SUITE_P(KeyboardShortcuts, EditlineKeyboardBindingTest,
+ testing::ValuesIn(keySequences),
+ [](const TestParamInfo<KeybindingTestValue> &kvb) {
+ return kvb.param.testNumber;
+ });
+
+} // namespace lldb_private
+
#endif
Index: lldb/source/Host/common/Editline.cpp
===================================================================
--- lldb/source/Host/common/Editline.cpp
+++ lldb/source/Host/common/Editline.cpp
@@ -354,12 +354,6 @@
int Editline::GetPromptWidth() { return (int)PromptForIndex(0).length(); }
-bool Editline::IsEmacs() {
- const char *editor;
- el_get(m_editline, EL_EDITOR, &editor);
- return editor[0] == 'e';
-}
-
bool Editline::IsOnlySpaces() {
const LineInfoW *info = el_wline(m_editline);
for (const EditLineCharType *character = info->buffer;
@@ -1329,36 +1323,15 @@
el_set(m_editline, EL_BIND, ESCAPE "[\\^", "lldb-revert-line", NULL);
// Editor-specific bindings
- if (IsEmacs()) {
- el_set(m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL);
- el_set(m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL);
- el_set(m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL);
- el_set(m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL);
- el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history",
- NULL);
- el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history",
- NULL);
- el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history",
- NULL);
- el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL);
- } else {
- el_set(m_editline, EL_BIND, "^H", "lldb-delete-previous-char", NULL);
-
- el_set(m_editline, EL_BIND, "-a", ESCAPE "[A", "lldb-previous-line",
- NULL);
- el_set(m_editline, EL_BIND, "-a", ESCAPE "[B", "lldb-next-line", NULL);
- el_set(m_editline, EL_BIND, "-a", "x", "lldb-delete-next-char", NULL);
- el_set(m_editline, EL_BIND, "-a", "^H", "lldb-delete-previous-char",
- NULL);
- el_set(m_editline, EL_BIND, "-a", "^?", "lldb-delete-previous-char",
- NULL);
-
- // Escape is absorbed exiting edit mode, so re-register important
- // sequences without the prefix
- el_set(m_editline, EL_BIND, "-a", "[A", "lldb-previous-line", NULL);
- el_set(m_editline, EL_BIND, "-a", "[B", "lldb-next-line", NULL);
- el_set(m_editline, EL_BIND, "-a", "[\\^", "lldb-revert-line", NULL);
- }
+ el_set(m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL);
+ el_set(m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL);
+ el_set(m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL);
+ el_set(m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL);
+ el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history",
+ NULL);
+ el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history", NULL);
+ el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history", NULL);
+ el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL);
}
}
Index: lldb/include/lldb/Host/Editline.h
===================================================================
--- lldb/include/lldb/Host/Editline.h
+++ lldb/include/lldb/Host/Editline.h
@@ -240,11 +240,6 @@
/// all lines of the current multi-line session.
int GetPromptWidth();
- /// Returns true if the underlying EditLine session's keybindings are
- /// Emacs-based, or false if
- /// they are VI-based.
- bool IsEmacs();
-
/// Returns true if the current EditLine buffer contains nothing but spaces,
/// or is empty.
bool IsOnlySpaces();
@@ -393,6 +388,9 @@
std::size_t m_previous_autosuggestion_size = 0;
std::mutex m_output_mutex;
+
+ friend class EditlineKeyboardBindingTest_MultiLineEditlineKeybindings_Test;
+ friend class EditlineKeyboardBindingTest_SingleLineEditlineKeybindings_Test;
};
}
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits