diff --git a/lib/ruby_lsp/ruby_lsp_rails/addon.rb b/lib/ruby_lsp/ruby_lsp_rails/addon.rb index 5282bd90..3c4bb3ff 100644 --- a/lib/ruby_lsp/ruby_lsp_rails/addon.rb +++ b/lib/ruby_lsp/ruby_lsp_rails/addon.rb @@ -58,6 +58,13 @@ def rails_runner_client @addon_mutex.synchronize { @rails_runner_client } end + # Blocks until the add-on finished booting the Rails runner client, successfully or not. After this method + # returns, `rails_runner_client` is in its final state: a working client or a NullClient if booting failed + #: -> void + def join_boot_thread + @boot_thread.join + end + # @override #: (GlobalState global_state, Thread::Queue outgoing_queue) -> void def activate(global_state, outgoing_queue) @@ -77,7 +84,7 @@ def activate(global_state, outgoing_queue) # @override #: -> void def deactivate - @boot_thread.join + join_boot_thread @rails_runner_client.shutdown end diff --git a/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb b/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb index 544dbd60..9a6879be 100644 --- a/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +++ b/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb @@ -10,13 +10,15 @@ class RunnerClient class << self #: (Thread::Queue outgoing_queue, RubyLsp::GlobalState global_state) -> RunnerClient def create_client(outgoing_queue, global_state) - if File.exist?("bin/rails") + workspace_path = global_state.workspace_path + + if File.exist?(File.join(workspace_path, "bin", "rails")) new(outgoing_queue, global_state) else unless outgoing_queue.closed? outgoing_queue << RubyLsp::Notification.window_log_message( <<~MESSAGE.chomp, - Ruby LSP Rails failed to locate bin/rails in the current directory: #{Dir.pwd} + Ruby LSP Rails failed to locate bin/rails in the workspace: #{workspace_path} Server dependent features will not be available MESSAGE type: RubyLsp::Constant::MessageType::WARNING, @@ -64,6 +66,7 @@ def initialize(outgoing_queue, global_state) "#{__dir__}/server.rb", "start", server_relevant_capabilities(global_state), + { chdir: global_state.workspace_path }, ) end diff --git a/test/ruby_lsp_rails/code_lens_test.rb b/test/ruby_lsp_rails/code_lens_test.rb index 98fcc835..0ec16027 100644 --- a/test/ruby_lsp_rails/code_lens_test.rb +++ b/test/ruby_lsp_rails/code_lens_test.rb @@ -402,7 +402,7 @@ def index def generate_code_lens_for_source(source, file: "/fake.rb") with_server(source, URI(file)) do |server, uri| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message( id: 1, diff --git a/test/ruby_lsp_rails/completion_test.rb b/test/ruby_lsp_rails/completion_test.rb index 9aa4554f..dacb76f1 100644 --- a/test/ruby_lsp_rails/completion_test.rb +++ b/test/ruby_lsp_rails/completion_test.rb @@ -60,7 +60,7 @@ class CompletionTest < ActiveSupport::TestCase def generate_completions_for_source(source, position) with_server(source) do |server, uri| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message( id: 1, diff --git a/test/ruby_lsp_rails/definition_test.rb b/test/ruby_lsp_rails/definition_test.rb index ab84731f..099ad7cb 100644 --- a/test/ruby_lsp_rails/definition_test.rb +++ b/test/ruby_lsp_rails/definition_test.rb @@ -536,7 +536,7 @@ class Bar::UsersController def generate_definitions_for_source(source, position) with_server(source) do |server, uri| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message( id: 1, diff --git a/test/ruby_lsp_rails/rails_test_style_test.rb b/test/ruby_lsp_rails/rails_test_style_test.rb index ea11718d..19a91513 100644 --- a/test/ruby_lsp_rails/rails_test_style_test.rb +++ b/test/ruby_lsp_rails/rails_test_style_test.rb @@ -142,7 +142,7 @@ class SpecialCharsTest < ActiveSupport::TestCase Dir.stubs(:glob).returns(test_paths) with_server do |server| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message({ id: 1, @@ -181,7 +181,7 @@ class SpecialCharsTest < ActiveSupport::TestCase test "resolve test command group test" do with_server do |server| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message({ id: 1, @@ -230,7 +230,7 @@ class SpecialCharsTest < ActiveSupport::TestCase test "resolve test escapes file paths in groups" do with_server do |server| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message({ id: 1, @@ -264,7 +264,7 @@ class SpecialCharsTest < ActiveSupport::TestCase test "resolve test escapes single file paths" do with_server do |server| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message({ id: 1, @@ -298,7 +298,7 @@ class SpecialCharsTest < ActiveSupport::TestCase Dir.stubs(:glob).returns([test_path]) with_server do |server| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message({ id: 1, @@ -328,7 +328,7 @@ class SpecialCharsTest < ActiveSupport::TestCase test "resolve test escapes file paths for specific examples" do with_server do |server| - sleep(0.1) while RubyLsp::Addon.addons.first.instance_variable_get(:@rails_runner_client).is_a?(NullClient) + wait_for_rails_runner_client_boot server.process_message({ id: 1, diff --git a/test/ruby_lsp_rails/runner_client_test.rb b/test/ruby_lsp_rails/runner_client_test.rb index 967d436d..c52dd8e2 100644 --- a/test/ruby_lsp_rails/runner_client_test.rb +++ b/test/ruby_lsp_rails/runner_client_test.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "test_helper" +require "tmpdir" require "ruby_lsp/ruby_lsp_rails/runner_client" module RubyLsp @@ -101,13 +102,31 @@ class RunnerClientTest < ActiveSupport::TestCase log = pop_log_notification(outgoing_queue, RubyLsp::Constant::MessageType::WARNING) assert_instance_of(RubyLsp::Notification, log) - assert_match("Ruby LSP Rails failed to locate bin/rails in the current directory", log.params.message) + assert_match("Ruby LSP Rails failed to locate bin/rails in the workspace", log.params.message) ensure outgoing_queue.close FileUtils.mv("bin/rails_backup", "bin/rails") end end + test "creates a client when the current working directory is not the workspace" do + outgoing_queue = Thread::Queue.new + + client = Dir.mktmpdir do |dir| + Dir.chdir(dir) { RunnerClient.create_client(outgoing_queue, @global_state) } + end + + begin + refute_instance_of(NullClient, client) + + response = client.model("User") #: as !nil + assert(response.key?(:columns)) + ensure + client.shutdown + outgoing_queue.close + end + end + test "failing to spawn server creates a null client" do FileUtils.mv("test/dummy/config/application.rb", "test/dummy/config/application.rb.bak") diff --git a/test/test_helper.rb b/test/test_helper.rb index 784be45e..98a352da 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -20,5 +20,15 @@ class TestCase def dummy_root File.expand_path("#{__dir__}/dummy") end + + # Waits until the Rails add-on finished booting the runner client and fails the test immediately if it fell back + # to a NullClient, instead of hanging forever waiting for a real client that will never arrive + def wait_for_rails_runner_client_boot + addon = RubyLsp::Addon.addons.first #: as RubyLsp::Rails::Addon + addon.join_boot_thread + + client = addon.rails_runner_client + refute_instance_of(RubyLsp::Rails::NullClient, client, "Expected the Rails runner client to boot successfully") + end end end