diff --git a/Dockerfile.development b/Dockerfile.development index 52b1bbc..72896e4 100644 --- a/Dockerfile.development +++ b/Dockerfile.development @@ -1,7 +1,7 @@ ARG RUBY_VERSION= FROM ruby:$RUBY_VERSION -RUN apt-get update && apt-get install less -y +RUN apt-get update && apt-get install apt-transport-https netcat less -y RUN groupadd --gid 1000 ruby && useradd --uid 1000 --gid ruby --shell /bin/bash --create-home ruby RUN mkdir /app /vendor && chown -R ruby:ruby /app /vendor diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 177ac10..b8d4f02 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -14,3 +14,6 @@ services: <<: *app ruby-2-2: <<: *app + selenium-vscode: + volumes: + - ./docker-selenium-vscode/vscode_wrapper:/opt/vscode_wrapper diff --git a/docker-compose.yml b/docker-compose.yml index a46cc29..390c972 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: volumes: - vendor:/vendor - home:/home/ruby + depends_on: + - selenium-vscode ruby-2-5: <<: *app ruby-2-4: @@ -31,6 +33,11 @@ services: <<: *app-build args: RUBY_VERSION: 2.2.9 + selenium-vscode: + build: + context: docker-selenium-vscode + ports: + - 5900 volumes: home: vendor: diff --git a/docker-selenium-vscode/Dockerfile b/docker-selenium-vscode/Dockerfile new file mode 100644 index 0000000..c855eaa --- /dev/null +++ b/docker-selenium-vscode/Dockerfile @@ -0,0 +1,16 @@ +FROM selenium/standalone-chrome-debug:3.7.1 + +USER root +RUN apt-get update && apt-get install apt-transport-https -y && \ + wget https://packages.microsoft.com/keys/microsoft.asc -q -O - | gpg --dearmor > microsoft.gpg && \ + mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg && \ + sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list' && \ + apt-get update && apt-get install code netcat -y + +USER seluser + +RUN wget https://mtsmfm.gallery.vsassets.io/_apis/public/gallery/publisher/mtsmfm/extension/ruby-lsc/0.1.1/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage -O /tmp/ruby-lsc.vsix && \ + /usr/bin/code --install-extension /tmp/ruby-lsc.vsix && \ + rm /tmp/ruby-lsc.vsix + +COPY vscode_wrapper /opt/vscode_wrapper diff --git a/docker-selenium-vscode/vscode_wrapper b/docker-selenium-vscode/vscode_wrapper new file mode 100755 index 0000000..b9f0bf3 --- /dev/null +++ b/docker-selenium-vscode/vscode_wrapper @@ -0,0 +1,33 @@ +#! /usr/bin/env bash + +set -e +tmpdir="/tmp/vscode-test-$(date +'%s')" +mkdir $tmpdir +cd $tmpdir +mkdir .vscode + +args=() + +for opt in "$@" +do + case $opt in + --before-boot=* ) + before_boot=$(echo "$1" | sed -e "s/^--before-boot='//" -e "s/'$//") + echo $before_boot + ;; + 'data:,' ) + # Ignore + ;; + * ) + args+=( $1 ) + ;; + esac + shift +done + +if [ "$before_boot" ]; then + eval "$before_boot" +fi + +/usr/bin/code --skip-getting-started --disable-updates --disable-crash-reporter --wait ${args[*]} . +rm -rf $tmpdir diff --git a/language_server.gemspec b/language_server.gemspec index 61a4ef5..9dcbf80 100644 --- a/language_server.gemspec +++ b/language_server.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "awesome_print" spec.add_development_dependency "benchmark-ips" spec.add_development_dependency "bundler", "~> 1.14" + spec.add_development_dependency "capybara" spec.add_development_dependency "guard" spec.add_development_dependency "guard-minitest" spec.add_development_dependency "m" @@ -38,4 +39,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "minitest-power_assert" spec.add_development_dependency "pry-byebug" spec.add_development_dependency "rake", "~> 12.2" + spec.add_development_dependency "selenium-webdriver" end diff --git a/lib/language_server.rb b/lib/language_server.rb index e92b932..d8d5684 100644 --- a/lib/language_server.rb +++ b/lib/language_server.rb @@ -88,10 +88,14 @@ def on(method, &callback) text_document_sync: Protocol::Interface::TextDocumentSyncOptions.new( change: Protocol::Constant::TextDocumentSyncKind::FULL, ), - completion_provider: Protocol::Interface::CompletionOptions.new( - resolve_provider: true, - trigger_characters: %w[.], - ), + completion_provider: if LanguageServer.adhoc_enabled? + Protocol::Interface::CompletionOptions.new( + resolve_provider: true, + trigger_characters: %w[.], + ) + else + false + end, definition_provider: LanguageServer.adhoc_enabled?, ), ) diff --git a/test/language_server_test.rb b/test/language_server_test.rb new file mode 100644 index 0000000..021ffa6 --- /dev/null +++ b/test/language_server_test.rb @@ -0,0 +1,16 @@ +require "test_helper" +require "open3" + +class LanguageServerTest < IntegrationTest + def test_syntax_check + create_and_open_new_file "a.rb" + + input_contents "def hi" + assert_error_message_on "hi", message: "unexpected end-of-input, expecting ';' or '\\n'" + + input_contents :enter + input_contents "end" + + assert_no_error_message + end +end diff --git a/test/support/capybara.rb b/test/support/capybara.rb new file mode 100644 index 0000000..55dd09b --- /dev/null +++ b/test/support/capybara.rb @@ -0,0 +1,18 @@ +require "capybara/minitest" + +Capybara.register_driver(:vscode) do |app| + options = { browser: :remote, url: "http://selenium-vscode:4444/wd/hub" } + + options[:desired_capabilities] = Selenium::WebDriver::Remote::Capabilities.chrome( + chrome_options: { + binary: "/opt/vscode_wrapper", + args: ["before-boot='echo \"{ \\\"ruby-lsc.commandWithArgs\\\": [ \\\"nc\\\", \\\"#{Socket.gethostname}\\\", \\\"12345\\\" ] }\" > .vscode/settings.json'"], + }, + ) + + Capybara::Selenium::Driver.new(app, options) +end + +Capybara.run_server = false +Capybara.default_driver = :vscode +Capybara.default_max_wait_time = 5 diff --git a/test/support/test_server.rb b/test/support/test_server.rb new file mode 100644 index 0000000..52a7196 --- /dev/null +++ b/test/support/test_server.rb @@ -0,0 +1,49 @@ +class TestServer + attr_reader :stdin, :stdout, :stderr, :thread, :port + + def initialize(port = 12345) + @stdin, @stdout, @stderr = Open3.popen3("bin/language_server-ruby") + @port = port + @thread = start_thread + @thread.abort_on_exception = true + end + + private + + def start_thread + Thread.new do + socket = TCPServer.open(port).accept + + socket_buffer = "" + stdout_buffer = "" + + loop do + readable_ios, writable_ios = IO.select([socket, stdout], [socket, stdin]) + + readable_ios.each do |io| + c = io.readchar + + case io + when socket + socket_buffer += c + when stdout + stdout_buffer += c + end + end + + next if readable_ios.any? + + writable_ios.each do |io| + case io + when stdin + io.write(socket_buffer) + socket_buffer.clear + when socket + io.write(stdout_buffer) + stdout_buffer.clear + end + end + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 17c3ede..9b96a36 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -12,3 +12,53 @@ def strip_heredoc gsub(/^[ \t]{#{indent}}/, "") end end + +Dir.glob(File.join(__dir__, "support", "**", "*.rb")).each { |f| require f } + +class IntegrationTest < Minitest::Test + include Capybara::DSL + include Capybara::Minitest::Assertions + + def setup + @server = TestServer.new + + switch_to_window(windows.last) + + assert_selector ".tab-label", text: /\AWelcome\z/, wait: 10 + end + + def teardown + evaluate_script "electron.remote.app.quit()" + + puts @server.stderr.read unless passed? + end + + def create_and_open_new_file(filename) + find(".explorer-viewlet").click + find(".action-label.new-file").click + find(".explorer-item input").send_keys(filename) + find(".explorer-item input").send_keys(:enter) + end + + def input_contents(*contents) + find(".monaco-editor textarea").send_keys(*contents) + end + + def focus_text(text) + input_contents :escape + + find("span", text: /\A#{Regexp.escape(text)}\z/).hover + end + + def assert_error_message_on(text, message: nil) + focus_text text + + options = message ? { text: /\A#{Regexp.escape(message)}\z/ } : {} + + assert_selector ".monaco-tokenized-source", options + end + + def assert_no_error_message + assert_selector ".task-statusbar-item-label-error+.task-statusbar-item-label-counter", text: /\A0\z/ + end +end