brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.4 KiB · 2afc336 Raw
293 lines · cpp
1//===-- EditlineTest.cpp --------------------------------------------------===//2//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Host/Config.h"10#include "lldb/Host/File.h"11#include "lldb/Host/HostInfo.h"12#include "lldb/lldb-forward.h"13#include "llvm/Testing/Support/Error.h"14 15#if LLDB_ENABLE_LIBEDIT16 17#define EDITLINE_TEST_DUMP_OUTPUT 018 19#include <stdio.h>20#include <unistd.h>21 22#include "gmock/gmock.h"23#include "gtest/gtest.h"24#include <memory>25#include <thread>26 27#include "TestingSupport/SubsystemRAII.h"28#include "lldb/Host/Editline.h"29#include "lldb/Host/FileSystem.h"30#include "lldb/Host/PseudoTerminal.h"31#include "lldb/Host/StreamFile.h"32#include "lldb/Utility/Status.h"33#include "lldb/Utility/StringList.h"34 35using namespace lldb_private;36 37namespace {38const size_t TIMEOUT_MILLIS = 5000;39}40 41/**42 Wraps an Editline class, providing a simple way to feed43 input (as if from the keyboard) and receive output from Editline.44 */45class EditlineAdapter {46public:47  EditlineAdapter();48 49  void CloseInput();50 51  bool IsValid() const { return _editline_sp != nullptr; }52 53  lldb_private::Editline &GetEditline() { return *_editline_sp; }54 55  bool SendLine(const std::string &line);56 57  bool SendLines(const std::vector<std::string> &lines);58 59  bool GetLine(std::string &line, bool &interrupted, size_t timeout_millis);60 61  bool GetLines(lldb_private::StringList &lines, bool &interrupted,62                size_t timeout_millis);63 64  void ConsumeAllOutput();65 66private:67  bool IsInputComplete(lldb_private::Editline *editline,68                       lldb_private::StringList &lines);69 70  std::recursive_mutex output_mutex;71  std::unique_ptr<lldb_private::Editline> _editline_sp;72 73  lldb::FileSP _el_primary_file;74  lldb::FileSP _el_secondary_file;75};76 77EditlineAdapter::EditlineAdapter() : _editline_sp(), _el_secondary_file() {78  lldb_private::Status error;79  PseudoTerminal pty;80 81  // Open the first primary pty available.82  EXPECT_THAT_ERROR(pty.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded());83  // Open the corresponding secondary pty.84  EXPECT_THAT_ERROR(pty.OpenSecondary(O_RDWR), llvm::Succeeded());85 86  // Grab the primary fd.  This is a file descriptor we will:87  // (1) write to when we want to send input to editline.88  // (2) read from when we want to see what editline sends back.89  _el_primary_file.reset(90      new NativeFile(pty.ReleasePrimaryFileDescriptor(),91                     lldb_private::NativeFile::eOpenOptionReadWrite, true));92 93  _el_secondary_file.reset(94      new NativeFile(pty.ReleaseSecondaryFileDescriptor(),95                     lldb_private::NativeFile::eOpenOptionReadWrite, true));96 97  lldb::LockableStreamFileSP output_stream_sp =98      std::make_shared<LockableStreamFile>(_el_secondary_file, output_mutex);99  lldb::LockableStreamFileSP error_stream_sp =100      std::make_shared<LockableStreamFile>(_el_secondary_file, output_mutex);101 102  // Create an Editline instance.103  _editline_sp.reset(new lldb_private::Editline(104      "gtest editor", _el_secondary_file->GetStream(), output_stream_sp,105      error_stream_sp,106      /*color=*/false));107  _editline_sp->SetPrompt("> ");108 109  // Hookup our input complete callback.110  auto input_complete_cb = [this](Editline *editline, StringList &lines) {111    return this->IsInputComplete(editline, lines);112  };113  _editline_sp->SetIsInputCompleteCallback(input_complete_cb);114}115 116void EditlineAdapter::CloseInput() {117  if (_el_secondary_file != nullptr)118    _el_secondary_file->Close();119}120 121bool EditlineAdapter::SendLine(const std::string &line) {122  // Ensure we're valid before proceeding.123  if (!IsValid())124    return false;125 126  std::string out = line + "\n";127 128  // Write the line out to the pipe connected to editline's input.129  size_t num_bytes = out.length() * sizeof(std::string::value_type);130  EXPECT_THAT_ERROR(_el_primary_file->Write(out.c_str(), num_bytes).takeError(),131                    llvm::Succeeded());132  EXPECT_EQ(num_bytes, out.length() * sizeof(std::string::value_type));133  return true;134}135 136bool EditlineAdapter::SendLines(const std::vector<std::string> &lines) {137  for (auto &line : lines) {138#if EDITLINE_TEST_DUMP_OUTPUT139    printf("<stdin> sending line \"%s\"\n", line.c_str());140#endif141    if (!SendLine(line))142      return false;143  }144  return true;145}146 147// We ignore the timeout for now.148bool EditlineAdapter::GetLine(std::string &line, bool &interrupted,149                              size_t /* timeout_millis */) {150  // Ensure we're valid before proceeding.151  if (!IsValid())152    return false;153 154  _editline_sp->GetLine(line, interrupted);155  return true;156}157 158bool EditlineAdapter::GetLines(lldb_private::StringList &lines,159                               bool &interrupted, size_t /* timeout_millis */) {160  // Ensure we're valid before proceeding.161  if (!IsValid())162    return false;163 164  _editline_sp->GetLines(1, lines, interrupted);165  return true;166}167 168bool EditlineAdapter::IsInputComplete(lldb_private::Editline *editline,169                                      lldb_private::StringList &lines) {170  // We'll call ourselves complete if we've received a balanced set of braces.171  int start_block_count = 0;172  int brace_balance = 0;173 174  for (const std::string &line : lines) {175    for (auto ch : line) {176      if (ch == '{') {177        ++start_block_count;178        ++brace_balance;179      } else if (ch == '}')180        --brace_balance;181    }182  }183 184  return (start_block_count > 0) && (brace_balance == 0);185}186 187void EditlineAdapter::ConsumeAllOutput() {188  FILE *output_file = _el_primary_file->GetStream();189 190  int ch;191  while ((ch = fgetc(output_file)) != EOF) {192#if EDITLINE_TEST_DUMP_OUTPUT193    char display_str[] = {0, 0, 0};194    switch (ch) {195    case '\t':196      display_str[0] = '\\';197      display_str[1] = 't';198      break;199    case '\n':200      display_str[0] = '\\';201      display_str[1] = 'n';202      break;203    case '\r':204      display_str[0] = '\\';205      display_str[1] = 'r';206      break;207    default:208      display_str[0] = ch;209      break;210    }211    printf("<stdout> 0x%02x (%03d) (%s)\n", ch, ch, display_str);212// putc(ch, stdout);213#endif214  }215}216 217class EditlineTestFixture : public ::testing::Test {218  SubsystemRAII<FileSystem, HostInfo> subsystems;219  EditlineAdapter _el_adapter;220  std::shared_ptr<std::thread> _sp_output_thread;221 222public:223  static void SetUpTestCase() {224    // We need a TERM set properly for editline to work as expected.225    setenv("TERM", "vt100", 1);226  }227 228  void SetUp() override {229    // Validate the editline adapter.230    EXPECT_TRUE(_el_adapter.IsValid());231    if (!_el_adapter.IsValid())232      return;233 234    // Dump output.235    _sp_output_thread =236        std::make_shared<std::thread>([&] { _el_adapter.ConsumeAllOutput(); });237  }238 239  void TearDown() override {240    _el_adapter.CloseInput();241    if (_sp_output_thread)242      _sp_output_thread->join();243  }244 245  EditlineAdapter &GetEditlineAdapter() { return _el_adapter; }246};247 248TEST_F(EditlineTestFixture, EditlineReceivesSingleLineText) {249  // Send it some text via our virtual keyboard.250  const std::string input_text("Hello, world");251  EXPECT_TRUE(GetEditlineAdapter().SendLine(input_text));252 253  // Verify editline sees what we put in.254  std::string el_reported_line;255  bool input_interrupted = false;256  const bool received_line = GetEditlineAdapter().GetLine(257      el_reported_line, input_interrupted, TIMEOUT_MILLIS);258 259  EXPECT_TRUE(received_line);260  EXPECT_FALSE(input_interrupted);261  EXPECT_EQ(input_text, el_reported_line);262}263 264TEST_F(EditlineTestFixture, EditlineReceivesMultiLineText) {265  // Send it some text via our virtual keyboard.266  std::vector<std::string> input_lines;267  input_lines.push_back("int foo()");268  input_lines.push_back("{");269  input_lines.push_back("printf(\"Hello, world\");");270  input_lines.push_back("}");271  input_lines.push_back("");272 273  EXPECT_TRUE(GetEditlineAdapter().SendLines(input_lines));274 275  // Verify editline sees what we put in.276  lldb_private::StringList el_reported_lines;277  bool input_interrupted = false;278 279  EXPECT_TRUE(GetEditlineAdapter().GetLines(el_reported_lines,280                                            input_interrupted, TIMEOUT_MILLIS));281  EXPECT_FALSE(input_interrupted);282 283  // Without any auto indentation support, our output should directly match our284  // input.285  std::vector<std::string> reported_lines;286  for (const std::string &line : el_reported_lines)287    reported_lines.push_back(line);288 289  EXPECT_THAT(reported_lines, testing::ContainerEq(input_lines));290}291 292#endif293