diff --git a/apps/language_server/test/dialyzer_test.exs b/apps/language_server/test/dialyzer_test.exs index 49df44d99..b8b71825d 100644 --- a/apps/language_server/test/dialyzer_test.exs +++ b/apps/language_server/test/dialyzer_test.exs @@ -22,9 +22,26 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do end setup do - server = start_server() + {:ok, server} = Server.start_link() + start_server(server) + Process.unlink(server) {:ok, _tracer} = start_supervised(Tracer) + on_exit(fn -> + if Process.alive?(server) do + state = :sys.get_state(server) + refute state.build_running? + + Process.monitor(server) + Process.exit(server, :terminate) + + receive do + {:DOWN, _, _, ^server, _} -> + :ok + end + end + end) + {:ok, %{server: server}} end @@ -87,8 +104,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do }), 40000 - # Stop while we're still capturing logs to avoid log leakage - GenServer.stop(server) + wait_until_compiled(server) end) end) end @@ -140,8 +156,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do }), 3_000 - # Stop while we're still capturing logs to avoid log leakage - GenServer.stop(server) + wait_until_compiled(server) end) end) end @@ -188,6 +203,8 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do Type: :error """ + + wait_until_compiled(server) end) end) end @@ -225,6 +242,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do assert error_message1 == "Function fun/0 has no local return." assert error_message2 == "The pattern can never match the type :error." + wait_until_compiled(server) end) end) end @@ -263,6 +281,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do assert error_message1 == "Function 'fun'/0 has no local return" # Note: Don't assert on error_messaage 2 because the message is not stable across OTP versions + wait_until_compiled(server) end) end) end @@ -300,6 +319,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do assert error_message1 == "Function check_error/0 has no local return." assert error_message2 == "The pattern can never match the type :error." + wait_until_compiled(server) end) end) end @@ -317,9 +337,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do File.rm("lib/a.ex") Server.receive_packet(server, did_change_watched_files([%{"uri" => file_a, "type" => 3}])) assert_receive publish_diagnostics_notif(^file_a, []), 20000 - - # Stop while we're still capturing logs to avoid log leakage - GenServer.stop(server) + wait_until_compiled(server) end) end) end @@ -388,6 +406,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do # we should not receive Protocol has already been consolidated warnings here refute_receive notification("textDocument/publishDiagnostics", _), 3000 + wait_until_compiled(server) end) end @@ -416,6 +435,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do resp = assert_receive(%{"id" => 3}, 5000) assert response(3, []) == resp + wait_until_compiled(server) end) end) end @@ -501,6 +521,7 @@ defmodule ElixirLS.LanguageServer.DialyzerTest do JsonRpc.receive_packet(response(id, %{"applied" => true})) assert_receive(%{"id" => 4, "result" => nil}, 5000) + wait_until_compiled(server) end) end) end diff --git a/apps/language_server/test/providers/execute_command/mix_clean_test.exs b/apps/language_server/test/providers/execute_command/mix_clean_test.exs index da4ac9ab1..43d25a6b0 100644 --- a/apps/language_server/test/providers/execute_command/mix_clean_test.exs +++ b/apps/language_server/test/providers/execute_command/mix_clean_test.exs @@ -6,7 +6,24 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.MixCleanTest do setup do {:ok, _} = start_supervised(Tracer) - server = start_server() + {:ok, server} = Server.start_link() + start_server(server) + Process.unlink(server) + + on_exit(fn -> + if Process.alive?(server) do + state = :sys.get_state(server) + refute state.build_running? + + Process.monitor(server) + Process.exit(server, :terminate) + + receive do + {:DOWN, _, _, ^server, _} -> + :ok + end + end + end) {:ok, %{server: server}} end diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 498d9acd4..e47667077 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -14,162 +14,207 @@ defmodule ElixirLS.LanguageServer.ServerTest do end) end + setup context do + if context[:skip_server] do + :ok + else + {:ok, server} = Server.start_link() + start_server(server) + Process.monitor(server) + Process.unlink(server) + {:ok, tracer} = start_supervised(Tracer) + + on_exit(fn -> + if Process.alive?(server) do + state = :sys.get_state(server) + refute state.build_running? + + Process.monitor(server) + Process.exit(server, :terminate) + + receive do + {:DOWN, _, _, ^server, _} -> + :ok + end + end + end) + + {:ok, %{server: server, tracer: tracer}} + end + end + describe "initialize" do test "returns error -32002 ServerNotInitialized when not initialized", %{server: server} do - uri = "file:///file.ex" - Server.receive_packet(server, completion_req(1, uri, 2, 25)) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + Server.receive_packet(server, completion_req(1, uri, 2, 25)) - assert_receive( - %{ - "id" => 1, - "error" => %{ - "code" => -32002 - } - }, - 1000 - ) + assert_receive( + %{ + "id" => 1, + "error" => %{ + "code" => -32002 + } + }, + 1000 + ) + + wait_until_compiled(server) + end) end test "initializes", %{server: server} do - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + wait_until_compiled(server) + end) end test "gets configuration after initialized notification if client supports it", %{ server: server } do - Server.receive_packet( - server, - initialize_req(1, root_uri(), %{ - "workspace" => %{ - "configuration" => true - } - }) - ) - - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, notification("initialized")) - uri = root_uri() + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet( + server, + initialize_req(1, root_uri(), %{ + "workspace" => %{ + "configuration" => true + } + }) + ) - assert_receive( - %{ - "id" => 1, - "method" => "workspace/configuration", - "params" => %{"items" => [%{"scopeUri" => ^uri, "section" => "elixirLS"}]} - }, - 1000 - ) + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + Server.receive_packet(server, notification("initialized")) + uri = root_uri() - JsonRpc.receive_packet( - response(1, [ + assert_receive( %{ - "mixEnv" => "dev", - "autoBuild" => false, - "dialyzerEnabled" => false - } - ]) - ) + "id" => 1, + "method" => "workspace/configuration", + "params" => %{"items" => [%{"scopeUri" => ^uri, "section" => "elixirLS"}]} + }, + 1000 + ) + + JsonRpc.receive_packet( + response(1, [ + %{ + "mixEnv" => "dev", + "autoBuild" => false, + "dialyzerEnabled" => false + } + ]) + ) - assert :sys.get_state(server).mix_env == "dev" + assert :sys.get_state(server).mix_env == "dev" + wait_until_compiled(server) + end) end test "gets configuration after workspace/didChangeConfiguration notification if client supports it", %{ server: server } do - Server.receive_packet( - server, - initialize_req(1, root_uri(), %{ - "workspace" => %{ - "configuration" => true - } - }) - ) + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet( + server, + initialize_req(1, root_uri(), %{ + "workspace" => %{ + "configuration" => true + } + }) + ) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, notification("initialized")) - uri = root_uri() + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + Server.receive_packet(server, notification("initialized")) + uri = root_uri() - assert_receive( - %{ - "id" => id, - "method" => "workspace/configuration", - "params" => %{"items" => [%{"scopeUri" => ^uri, "section" => "elixirLS"}]} - }, - 1000 - ) + assert_receive( + %{ + "id" => id, + "method" => "workspace/configuration", + "params" => %{"items" => [%{"scopeUri" => ^uri, "section" => "elixirLS"}]} + }, + 1000 + ) - config = %{ - "mixEnv" => "dev", - "autoBuild" => false, - "dialyzerEnabled" => false - } + config = %{ + "mixEnv" => "dev", + "autoBuild" => false, + "dialyzerEnabled" => false + } - JsonRpc.receive_packet(response(id, [config])) + JsonRpc.receive_packet(response(id, [config])) - assert_receive( - %{ - "method" => "window/logMessage", - "params" => %{ - "message" => "Received client configuration via workspace/configuration" <> _ - } - }, - 1000 - ) + assert_receive( + %{ + "method" => "window/logMessage", + "params" => %{ + "message" => "Received client configuration via workspace/configuration" <> _ + } + }, + 1000 + ) - Server.receive_packet( - server, - did_change_configuration(nil) - ) + Server.receive_packet( + server, + did_change_configuration(nil) + ) - assert_receive( - %{ - "id" => id, - "method" => "workspace/configuration" - }, - 3000 - ) + assert_receive( + %{ + "id" => id, + "method" => "workspace/configuration" + }, + 3000 + ) - JsonRpc.receive_packet(response(id, [config])) + JsonRpc.receive_packet(response(id, [config])) + wait_until_compiled(server) + end) end test "handles deprecated push based configuration", %{ server: server } do - Server.receive_packet( - server, - initialize_req(1, root_uri(), %{ - "workspace" => %{ - "configuration" => false - } - }) - ) + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet( + server, + initialize_req(1, root_uri(), %{ + "workspace" => %{ + "configuration" => false + } + }) + ) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, notification("initialized")) - uri = root_uri() + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + Server.receive_packet(server, notification("initialized")) + uri = root_uri() - refute_receive( - %{ - "id" => 1, - "method" => "workspace/configuration", - "params" => %{"items" => [%{"scopeUri" => ^uri, "section" => "elixirLS"}]} - }, - 1000 - ) + refute_receive( + %{ + "id" => 1, + "method" => "workspace/configuration", + "params" => %{"items" => [%{"scopeUri" => ^uri, "section" => "elixirLS"}]} + }, + 1000 + ) - config = %{ - "mixEnv" => "dev", - "autoBuild" => false, - "dialyzerEnabled" => false - } + config = %{ + "mixEnv" => "dev", + "autoBuild" => false, + "dialyzerEnabled" => false + } - Server.receive_packet( - server, - did_change_configuration(%{"elixirLS" => config}) - ) + Server.receive_packet( + server, + did_change_configuration(%{"elixirLS" => config}) + ) - assert :sys.get_state(server).mix_env == "dev" + assert :sys.get_state(server).mix_env == "dev" + wait_until_compiled(server) + end) end test "falls back do default configuration", %{ @@ -209,471 +254,553 @@ defmodule ElixirLS.LanguageServer.ServerTest do ) assert :sys.get_state(server).mix_env == "test" + wait_until_compiled(server) end) end - test "Execute commands should include the server instance id", %{server: server} do - # If a command does not include the server instance id then it will cause - # vscode-elixir-ls to fail to start up on multi-root workspaces. - # Example: https://github.com/elixir-lsp/elixir-ls/pull/505 + test "execute commands should include the server instance id", %{server: server} do + in_fixture(__DIR__, "clean", fn -> + # If a command does not include the server instance id then it will cause + # vscode-elixir-ls to fail to start up on multi-root workspaces. + # Example: https://github.com/elixir-lsp/elixir-ls/pull/505 - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive(%{"id" => 1, "result" => result}, 1000) + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + assert_receive(%{"id" => 1, "result" => result}, 1000) - commands = get_in(result, ["capabilities", "executeCommandProvider", "commands"]) - server_instance_id = :sys.get_state(server).server_instance_id + commands = get_in(result, ["capabilities", "executeCommandProvider", "commands"]) + server_instance_id = :sys.get_state(server).server_instance_id - Enum.each(commands, fn command -> - assert String.contains?(command, server_instance_id) - end) + Enum.each(commands, fn command -> + assert String.contains?(command, server_instance_id) + end) - refute Enum.empty?(commands) + refute Enum.empty?(commands) + wait_until_compiled(server) + end) end test "returns -32600 InvalidRequest when already initialized", %{server: server} do - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive( - %{ - "id" => 1, - "error" => %{ - "code" => -32600 - } - }, - 1000 - ) + assert_receive( + %{ + "id" => 1, + "error" => %{ + "code" => -32600 + } + }, + 1000 + ) + + wait_until_compiled(server) + end) end test "skips notifications when not initialized", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do use GenServer end ) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - assert :sys.get_state(server).source_files == %{} + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + assert :sys.get_state(server).source_files == %{} + wait_until_compiled(server) + end) end end describe "exit" do test "exit notifications when not initialized", %{server: server} do - Process.monitor(server) - Server.receive_packet(server, notification("exit")) - assert_receive({:DOWN, _, :process, ^server, {:exit_code, 1}}) + in_fixture(__DIR__, "clean", fn -> + Process.monitor(server) + wait_until_compiled(server) + Server.receive_packet(server, notification("exit")) + assert_receive({:DOWN, _, :process, ^server, {:exit_code, 1}}) + end) end test "exit notifications after shutdown", %{server: server} do - Process.monitor(server) - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, request(2, "shutdown", %{})) - assert_receive(%{"id" => 2, "result" => nil}, 1000) - Server.receive_packet(server, notification("exit")) - assert_receive({:DOWN, _, :process, ^server, {:exit_code, 0}}) + in_fixture(__DIR__, "clean", fn -> + Process.monitor(server) + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + wait_until_compiled(server) + Server.receive_packet(server, request(2, "shutdown", %{})) + assert_receive(%{"id" => 2, "result" => nil}, 1000) + Server.receive_packet(server, notification("exit")) + assert_receive({:DOWN, _, :process, ^server, {:exit_code, 0}}) + end) end test "returns -32600 InvalidRequest when shutting down", %{server: server} do - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, request(2, "shutdown", %{})) - assert_receive(%{"id" => 2, "result" => nil}, 1000) + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + wait_until_compiled(server) + Server.receive_packet(server, request(2, "shutdown", %{})) + assert_receive(%{"id" => 2, "result" => nil}, 1000) - Server.receive_packet(server, hover_req(1, "file:///file.ex", 2, 17)) + Server.receive_packet(server, hover_req(1, "file:///file.ex", 2, 17)) - assert_receive( - %{ - "id" => 1, - "error" => %{ - "code" => -32600 - } - }, - 1000 - ) + assert_receive( + %{ + "id" => 1, + "error" => %{ + "code" => -32600 + } + }, + 1000 + ) + end) end test "skips notifications when not shutting down", %{server: server} do - Server.receive_packet(server, initialize_req(1, root_uri(), %{})) - assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) - Server.receive_packet(server, request(2, "shutdown", %{})) - assert_receive(%{"id" => 2, "result" => nil}, 1000) + in_fixture(__DIR__, "clean", fn -> + Server.receive_packet(server, initialize_req(1, root_uri(), %{})) + assert_receive(%{"id" => 1, "result" => %{"capabilities" => %{}}}, 1000) + wait_until_compiled(server) + Server.receive_packet(server, request(2, "shutdown", %{})) + assert_receive(%{"id" => 2, "result" => nil}, 1000) - uri = "file:///file.ex" - code = ~S( + uri = "file:///file.ex" + code = ~S( defmodule MyModule do use GenServer end ) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - assert :sys.get_state(server).source_files == %{} + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + assert :sys.get_state(server).source_files == %{} + end) end end describe "not matched messages" do test "not supported $/ notifications are skipped", %{server: server} do - fake_initialize(server) - Server.receive_packet(server, notification("$/not_supported")) - :sys.get_state(server) - refute_receive(%{"method" => "window/logMessage"}) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + Server.receive_packet(server, notification("$/not_supported")) + :sys.get_state(server) + refute_receive(%{"method" => "window/logMessage"}) + wait_until_compiled(server) + end) end test "not matched notifications log warning", %{server: server} do - fake_initialize(server) - Server.receive_packet(server, notification("not_matched")) - :sys.get_state(server) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + Server.receive_packet(server, notification("not_matched")) + :sys.get_state(server) - assert_receive(%{ - "method" => "window/logMessage", - "params" => %{"message" => "Received unmatched notification" <> _, "type" => 2} - }) + assert_receive(%{ + "method" => "window/logMessage", + "params" => %{"message" => "Received unmatched notification" <> _, "type" => 2} + }) + + wait_until_compiled(server) + end) end test "not supported $/ requests return -32601 MethodNotFound", %{server: server} do - fake_initialize(server) - Server.receive_packet(server, request(1, "$/not_supported")) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + Server.receive_packet(server, request(1, "$/not_supported")) - assert_receive( - %{ - "id" => 1, - "error" => %{ - "code" => -32601 - } - }, - 1000 - ) + assert_receive( + %{ + "id" => 1, + "error" => %{ + "code" => -32601 + } + }, + 1000 + ) - refute_receive(%{"method" => "window/logMessage"}) + refute_receive(%{"method" => "window/logMessage"}) + wait_until_compiled(server) + end) end test "not matched requests return -32600 InvalidRequest and log warning", %{server: server} do - fake_initialize(server) - Server.receive_packet(server, request(1, "not_matched")) - - assert_receive( - %{ - "id" => 1, - "error" => %{ - "code" => -32600 - } - }, - 1000 - ) - - assert_receive(%{ - "method" => "window/logMessage", - "params" => %{"message" => "Unmatched request" <> _, "type" => 2} - }) - end + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + Server.receive_packet(server, request(1, "not_matched")) - test "not matched executeCommand requests return -32600 InvalidRequest and log warning", %{ - server: server - } do - fake_initialize(server) - Server.receive_packet(server, execute_command_req(1, "not_matched", ["a", "bc"])) + assert_receive( + %{ + "id" => 1, + "error" => %{ + "code" => -32600 + } + }, + 1000 + ) - assert_receive( - %{ - "id" => 1, - "error" => %{ - "code" => -32600 - } - }, - 1000 - ) + assert_receive(%{ + "method" => "window/logMessage", + "params" => %{"message" => "Unmatched request" <> _, "type" => 2} + }) - assert_receive(%{ - "method" => "window/logMessage", - "params" => %{"message" => "Unmatched request" <> _, "type" => 2} - }) + wait_until_compiled(server) + end) end - end - setup context do - if context[:skip_server] do - :ok - else - server = start_server() - {:ok, tracer} = start_supervised(Tracer) + test "not matched executeCommand requests return -32600 InvalidRequest and log warning", %{ + server: server + } do + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + Server.receive_packet(server, execute_command_req(1, "not_matched", ["a", "bc"])) - {:ok, %{server: server, tracer: tracer}} + assert_receive( + %{ + "id" => 1, + "error" => %{ + "code" => -32600 + } + }, + 1000 + ) + + assert_receive(%{ + "method" => "window/logMessage", + "params" => %{"message" => "Unmatched request" <> _, "type" => 2} + }) + + wait_until_compiled(server) + end) end end describe "text synchronization" do test "textDocument/didOpen", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - state = :sys.get_state(server) + state = :sys.get_state(server) - assert %SourceFile{dirty?: false, text: ^code, version: 1} = - Server.get_source_file(state, uri) + assert %SourceFile{dirty?: false, text: ^code, version: 1} = + Server.get_source_file(state, uri) - assert_receive notification("textDocument/publishDiagnostics", %{ - "uri" => ^uri, - "diagnostics" => [] - }), - 1000 + assert_receive notification("textDocument/publishDiagnostics", %{ + "uri" => ^uri, + "diagnostics" => [] + }), + 1000 + + wait_until_compiled(server) + end) end test "textDocument/didOpen already open", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - assert_receive %{ - "method" => "window/logMessage", - "params" => %{ - "message" => - "Received textDocument/didOpen for file that is already open" <> _, - "type" => 2 - } - }, - 1000 + assert_receive %{ + "method" => "window/logMessage", + "params" => %{ + "message" => + "Received textDocument/didOpen for file that is already open" <> _, + "type" => 2 + } + }, + 1000 + + wait_until_compiled(server) + end) end test "textDocument/didClose", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, did_close(uri)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, did_close(uri)) - state = :sys.get_state(server) - assert_raise Server.InvalidParamError, fn -> Server.get_source_file(state, uri) end + state = :sys.get_state(server) + assert_raise Server.InvalidParamError, fn -> Server.get_source_file(state, uri) end + wait_until_compiled(server) + end) end test "textDocument/didClose not open", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_close(uri)) - - assert_receive %{ - "method" => "window/logMessage", - "params" => %{ - "message" => - "Received textDocument/didClose for file that is not open" <> _, - "type" => 2 - } - }, - 1000 + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_close(uri)) + + assert_receive %{ + "method" => "window/logMessage", + "params" => %{ + "message" => + "Received textDocument/didClose for file that is not open" <> _, + "type" => 2 + } + }, + 1000 + + wait_until_compiled(server) + end) end test "textDocument/didChange", %{server: server} do - uri = "file:///file.ex" + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" - content_changes = [ - %{ - "range" => %{ - "end" => %{"character" => 2, "line" => 1}, - "start" => %{"character" => 0, "line" => 2} - }, - "rangeLength" => 1, - "text" => "" - } - ] + content_changes = [ + %{ + "range" => %{ + "end" => %{"character" => 2, "line" => 1}, + "start" => %{"character" => 0, "line" => 2} + }, + "rangeLength" => 1, + "text" => "" + } + ] - code = ~S( + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, did_change(uri, 1, content_changes)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, did_change(uri, 1, content_changes)) - state = :sys.get_state(server) - assert %SourceFile{dirty?: true, version: 2} = Server.get_source_file(state, uri) + state = :sys.get_state(server) + assert %SourceFile{dirty?: true, version: 2} = Server.get_source_file(state, uri) + wait_until_compiled(server) + end) end test "textDocument/didChange not open", %{server: server} do - uri = "file:///file.ex" + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" - content_changes = [ - %{ - "range" => %{ - "end" => %{"character" => 2, "line" => 1}, - "start" => %{"character" => 0, "line" => 2} - }, - "rangeLength" => 1, - "text" => "" - } - ] + content_changes = [ + %{ + "range" => %{ + "end" => %{"character" => 2, "line" => 1}, + "start" => %{"character" => 0, "line" => 2} + }, + "rangeLength" => 1, + "text" => "" + } + ] - fake_initialize(server) - Server.receive_packet(server, did_change(uri, 1, content_changes)) - - assert_receive %{ - "method" => "window/logMessage", - "params" => %{ - "message" => - "Received textDocument/didChange for file that is not open" <> _, - "type" => 2 - } - }, - 1000 + fake_initialize(server) + Server.receive_packet(server, did_change(uri, 1, content_changes)) + + assert_receive %{ + "method" => "window/logMessage", + "params" => %{ + "message" => + "Received textDocument/didChange for file that is not open" <> _, + "type" => 2 + } + }, + 1000 - state = :sys.get_state(server) - refute Map.has_key?(state.source_files, uri) + state = :sys.get_state(server) + refute Map.has_key?(state.source_files, uri) + wait_until_compiled(server) + end) end test "textDocument/didSave", %{server: server} do - uri = "file:///file.ex" + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" - content_changes = [ - %{ - "range" => %{ - "end" => %{"character" => 2, "line" => 1}, - "start" => %{"character" => 0, "line" => 2} - }, - "rangeLength" => 1, - "text" => "" - } - ] + content_changes = [ + %{ + "range" => %{ + "end" => %{"character" => 2, "line" => 1}, + "start" => %{"character" => 0, "line" => 2} + }, + "rangeLength" => 1, + "text" => "" + } + ] - code = ~S( + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, did_change(uri, 1, content_changes)) - Server.receive_packet(server, did_save(uri)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, did_change(uri, 1, content_changes)) + Server.receive_packet(server, did_save(uri)) - state = :sys.get_state(server) - assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end test "textDocument/didSave not open", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_save(uri)) - - assert_receive %{ - "method" => "window/logMessage", - "params" => %{ - "message" => - "Received textDocument/didSave for file that is not open" <> _, - "type" => 2 - } - }, - 1000 + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_save(uri)) - state = :sys.get_state(server) - refute Map.has_key?(state.source_files, uri) + assert_receive %{ + "method" => "window/logMessage", + "params" => %{ + "message" => + "Received textDocument/didSave for file that is not open" <> _, + "type" => 2 + } + }, + 1000 + + state = :sys.get_state(server) + refute Map.has_key?(state.source_files, uri) + wait_until_compiled(server) + end) end end describe "workspace/didChangeWatchedFiles" do test "not watched file changed outside", %{server: server} do - uri = "file:///file.txt" - fake_initialize(server) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.txt" + fake_initialize(server) - for change_type <- 1..3 do - Server.receive_packet( - server, - did_change_watched_files([%{"uri" => uri, "type" => change_type}]) - ) + for change_type <- 1..3 do + Server.receive_packet( + server, + did_change_watched_files([%{"uri" => uri, "type" => change_type}]) + ) - state = :sys.get_state(server) - refute state.needs_build? - end + state = :sys.get_state(server) + refute state.needs_build? + end + + wait_until_compiled(server) + end) end test "watched file created outside", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end test "watched file updated outside", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end test "watched file deleted outside", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end test "watched open file created in editor", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? - assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + wait_until_compiled(server) + end) end # this case compiles 2 times but cannot be easily fixed without breaking other cases test "watched open file created in editor, didSave sent", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, did_save(uri)) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, did_save(uri)) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) - state = :sys.get_state(server) - assert state.needs_build? - assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + state = :sys.get_state(server) + assert state.needs_build? + assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + wait_until_compiled(server) + end) end test "watched open file saved in editor", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, did_save(uri)) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, did_save(uri)) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? - assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + wait_until_compiled(server) + end) end test "watched open file deleted in editor", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, did_close(uri)) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, did_close(uri)) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end @tag :fixture @@ -708,6 +835,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do state = :sys.get_state(server) assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: false} = Server.get_source_file(state, uri) + wait_until_compiled(server) end) end @@ -740,339 +868,400 @@ defmodule ElixirLS.LanguageServer.ServerTest do state = :sys.get_state(server) assert state.needs_build? || state.build_running? assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) - end) - end - - test "watched open file created outside, read error", %{server: server} do - uri = "file:///file.ex" - - content_changes = [ - %{ - "range" => %{ - "end" => %{"character" => 2, "line" => 1}, - "start" => %{"character" => 0, "line" => 2} - }, - "rangeLength" => 1, - "text" => "" - } - ] - - code = ~S( - defmodule MyModule do - use GenServer - end - ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, did_change(uri, 1, content_changes)) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) + wait_until_compiled(server) + end) + end + + test "watched open file created outside, read error", %{server: server} do + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + + content_changes = [ + %{ + "range" => %{ + "end" => %{"character" => 2, "line" => 1}, + "start" => %{"character" => 0, "line" => 2} + }, + "rangeLength" => 1, + "text" => "" + } + ] + + code = ~S( + defmodule MyModule do + use GenServer + end + ) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, did_change(uri, 1, content_changes)) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 1}])) + + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? - assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) + assert_receive %{ + "method" => "window/logMessage", + "params" => %{"message" => "Unable to read file" <> _, "type" => 2} + }, + 1000 - assert_receive %{ - "method" => "window/logMessage", - "params" => %{"message" => "Unable to read file" <> _, "type" => 2} - }, - 1000 + wait_until_compiled(server) + end) end test "watched open file updated outside, read error", %{server: server} do - uri = "file:///file.ex" + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" - content_changes = [ - %{ - "range" => %{ - "end" => %{"character" => 2, "line" => 1}, - "start" => %{"character" => 0, "line" => 2} - }, - "rangeLength" => 1, - "text" => "" - } - ] + content_changes = [ + %{ + "range" => %{ + "end" => %{"character" => 2, "line" => 1}, + "start" => %{"character" => 0, "line" => 2} + }, + "rangeLength" => 1, + "text" => "" + } + ] - code = ~S( + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, did_change(uri, 1, content_changes)) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, did_change(uri, 1, content_changes)) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) - state = :sys.get_state(server) - assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert %SourceFile{dirty?: true} = Server.get_source_file(state, uri) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end test "watched open file deleted outside", %{server: server} do - uri = "file:///file.ex" - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 3}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end # https://github.com/elixir-lsp/elixir-ls/pull/569 @tag :additional_extension test "watched file updated outside, non-default extension", %{server: server} do - uri = "file:///file.veex" - fake_initialize(server) + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.veex" + fake_initialize(server) - # Simulate settings related to this test - :sys.replace_state(server, fn state -> - %{state | settings: %{"additionalWatchedExtensions" => [".veex"]}} - end) + # Simulate settings related to this test + :sys.replace_state(server, fn state -> + %{state | settings: %{"additionalWatchedExtensions" => [".veex"]}} + end) - # Check if *.veex file triggers build - Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) + # Check if *.veex file triggers build + Server.receive_packet(server, did_change_watched_files([%{"uri" => uri, "type" => 2}])) - state = :sys.get_state(server) - assert state.needs_build? || state.build_running? + state = :sys.get_state(server) + assert state.needs_build? || state.build_running? + wait_until_compiled(server) + end) end test "gracefully skip not supported URI scheme", %{server: server} do - uri = "git://github.com/user/repo.git" - fake_initialize(server) + in_fixture(__DIR__, "clean", fn -> + uri = "git://github.com/user/repo.git" + fake_initialize(server) - Server.receive_packet( - server, - did_change_watched_files([%{"uri" => uri, "type" => 2}]) - ) + Server.receive_packet( + server, + did_change_watched_files([%{"uri" => uri, "type" => 2}]) + ) - :sys.get_state(server) + wait_until_compiled(server) + end) end end test "hover", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do use GenServer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, hover_req(1, uri, 2, 17)) - - resp = assert_receive(%{"id" => 1}, 1000) - - assert response(1, %{ - "contents" => %{ - "kind" => "markdown", - "value" => "> GenServer" <> _ - }, - "range" => %{ - "start" => %{"line" => 2, "character" => 12}, - "end" => %{"line" => 2, "character" => 21} - } - }) = resp + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, hover_req(1, uri, 2, 17)) + + resp = assert_receive(%{"id" => 1}, 1000) + + assert response(1, %{ + "contents" => %{ + "kind" => "markdown", + "value" => "> GenServer" <> _ + }, + "range" => %{ + "start" => %{"line" => 2, "character" => 12}, + "end" => %{"line" => 2, "character" => 21} + } + }) = resp + + wait_until_compiled(server) + end) end test "auto complete", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do def my_fn, do: GenSer end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, completion_req(1, uri, 2, 25)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, completion_req(1, uri, 2, 25)) - resp = assert_receive(%{"id" => 1}, 10000) + resp = assert_receive(%{"id" => 1}, 10000) - assert response(1, %{ - "isIncomplete" => true, - "items" => [ - %{ - "detail" => "behaviour", - "documentation" => _, - "kind" => 8, - "label" => "GenServer" - } - | _ - ] - }) = resp + assert response(1, %{ + "isIncomplete" => true, + "items" => [ + %{ + "detail" => "behaviour", + "documentation" => _, + "kind" => 8, + "label" => "GenServer" + } + | _ + ] + }) = resp + + wait_until_compiled(server) + end) end describe "textDocument/definition" do test "definition found", %{server: server} do - uri = "file:///file.ex" - code = ~S( + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( defmodule MyModule do @behaviour ElixirLS.LanguageServer.Fixtures.ExampleBehaviour end ) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, definition_req(1, uri, 2, 58)) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, definition_req(1, uri, 2, 58)) - uri = - ElixirLS.LanguageServer.Fixtures.ExampleBehaviour.module_info()[:compile][:source] - |> to_string - |> SourceFile.Path.to_uri() + uri = + ElixirLS.LanguageServer.Fixtures.ExampleBehaviour.module_info()[:compile][:source] + |> to_string + |> SourceFile.Path.to_uri() - assert_receive( - response(1, %{ - "range" => %{ - "end" => %{"character" => 0, "line" => 0}, - "start" => %{"character" => 0, "line" => 0} - }, - "uri" => ^uri - }), - 3000 - ) + assert_receive( + response(1, %{ + "range" => %{ + "end" => %{"character" => 0, "line" => 0}, + "start" => %{"character" => 0, "line" => 0} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + end) end test "definition not found", %{server: server} do - fake_initialize(server) - uri = "file:///file.ex" - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, definition_req(1, uri, 0, 43)) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + uri = "file:///file.ex" + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, definition_req(1, uri, 0, 43)) - assert_receive( - response(1, nil), - 3000 - ) + assert_receive( + response(1, nil), + 3000 + ) + + wait_until_compiled(server) + end) end end describe "textDocument/implementation" do test "implementations found", %{server: server} do - file_path = FixtureHelpers.get_path("example_behaviour.ex") - text = File.read!(file_path) - uri = SourceFile.Path.to_uri(file_path) - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, text)) + in_fixture(__DIR__, "clean", fn -> + file_path = FixtureHelpers.get_path("example_behaviour.ex") + text = File.read!(file_path) + uri = SourceFile.Path.to_uri(file_path) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, text)) - # force load as currently only loaded or loadable modules that are a part - # of an application are found - Code.ensure_loaded?(ElixirLS.LanguageServer.Fixtures.ExampleBehaviourImpl) + # force load as currently only loaded or loadable modules that are a part + # of an application are found + Code.ensure_loaded?(ElixirLS.LanguageServer.Fixtures.ExampleBehaviourImpl) - Server.receive_packet(server, implementation_req(1, uri, 0, 43)) + Server.receive_packet(server, implementation_req(1, uri, 0, 43)) - assert_receive( - response(1, [ - %{ - "range" => %{ - "end" => %{"character" => _, "line" => _}, - "start" => %{"character" => _, "line" => _} - }, - "uri" => ^uri - } - ]), - 15000 - ) + assert_receive( + response(1, [ + %{ + "range" => %{ + "end" => %{"character" => _, "line" => _}, + "start" => %{"character" => _, "line" => _} + }, + "uri" => ^uri + } + ]), + 15000 + ) + + wait_until_compiled(server) + end) end test "implementations not found", %{server: server} do - fake_initialize(server) - uri = "file:///file.ex" - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, implementation_req(1, uri, 0, 43)) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + uri = "file:///file.ex" + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, implementation_req(1, uri, 0, 43)) - assert_receive( - response(1, []), - 15000 - ) + assert_receive( + response(1, []), + 15000 + ) + + wait_until_compiled(server) + end) end end describe "requests cancellation" do test "known request", %{server: server} do - fake_initialize(server) - uri = "file:///file.ex" - Server.receive_packet(server, did_open(uri, "elixir", 1, "")) - Server.receive_packet(server, hover_req(1, uri, 1, 1)) - Server.receive_packet(server, cancel_request(1)) - - state = :sys.get_state(server) - refute Map.has_key?(state.requests, 1) - - assert_receive %{ - "error" => %{"code" => -32800, "message" => "Request cancelled"}, - "id" => 1, - "jsonrpc" => "2.0" - } + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + uri = "file:///file.ex" + Server.receive_packet(server, did_open(uri, "elixir", 1, "")) + Server.receive_packet(server, hover_req(1, uri, 1, 1)) + Server.receive_packet(server, cancel_request(1)) + + state = :sys.get_state(server) + refute Map.has_key?(state.requests, 1) + + assert_receive %{ + "error" => %{"code" => -32800, "message" => "Request cancelled"}, + "id" => 1, + "jsonrpc" => "2.0" + } + + wait_until_compiled(server) + end) end test "unknown request", %{server: server} do - fake_initialize(server) - Process.monitor(server) - Server.receive_packet(server, cancel_request(1)) - - assert_receive %{ - "method" => "window/logMessage", - "params" => %{ - "message" => "Received $/cancelRequest for unknown request" <> _, - "type" => 2 - } - }, - 1000 + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + Process.monitor(server) + Server.receive_packet(server, cancel_request(1)) + + assert_receive %{ + "method" => "window/logMessage", + "params" => %{ + "message" => "Received $/cancelRequest for unknown request" <> _, + "type" => 2 + } + }, + 1000 - refute_receive {:DOWN, _, _, _, _} + refute_receive {:DOWN, _, _, _, _} + wait_until_compiled(server) + end) end end describe "requests shutdown" do test "without params", %{server: server} do - fake_initialize(server) - Server.receive_packet(server, request(1, "shutdown")) - assert %{received_shutdown?: true} = :sys.get_state(server) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + wait_until_compiled(server) + Server.receive_packet(server, request(1, "shutdown")) + assert %{received_shutdown?: true} = :sys.get_state(server) + end) end test "with params", %{server: server} do - fake_initialize(server) - Server.receive_packet(server, request(1, "shutdown", nil)) - assert %{received_shutdown?: true} = :sys.get_state(server) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + wait_until_compiled(server) + Server.receive_packet(server, request(1, "shutdown", nil)) + assert %{received_shutdown?: true} = :sys.get_state(server) + end) end end test "uri request when the source file is not open returns -32602", %{server: server} do - fake_initialize(server) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) - Server.receive_packet(server, document_symbol_req(1, "file:///file.ex")) + Server.receive_packet(server, document_symbol_req(1, "file:///file.ex")) - assert_receive( - %{ - "id" => 1, - "error" => %{"code" => -32602, "message" => "invalid URI: \"file:///file.ex\""} - }, - 1000 - ) + assert_receive( + %{ + "id" => 1, + "error" => %{"code" => -32602, "message" => "invalid URI: \"file:///file.ex\""} + }, + 1000 + ) + + wait_until_compiled(server) + end) end test "uri async request when the source file is not open returns -32602", %{server: server} do - fake_initialize(server) + in_fixture(__DIR__, "clean", fn -> + fake_initialize(server) + + Server.receive_packet( + server, + execute_command_req(1, "spec:1", [ + %{ + "uri" => "file:///file.ex", + "mod" => "Mod", + "fun" => "fun", + "arity" => 1, + "spec" => "", + "line" => 1 + } + ]) + ) - Server.receive_packet( - server, - execute_command_req(1, "spec:1", [ + assert_receive( %{ - "uri" => "file:///file.ex", - "mod" => "Mod", - "fun" => "fun", - "arity" => 1, - "spec" => "", - "line" => 1 - } - ]) - ) + "id" => 1, + "error" => %{"code" => -32602, "message" => "invalid URI: \"file:///file.ex\""} + }, + 1000 + ) - assert_receive( - %{ - "id" => 1, - "error" => %{"code" => -32602, "message" => "invalid URI: \"file:///file.ex\""} - }, - 1000 - ) + wait_until_compiled(server) + end) end @tag :fixture @@ -1124,64 +1313,69 @@ defmodule ElixirLS.LanguageServer.ServerTest do # File is already formatted assert response(3, []) == resp + wait_until_compiled(server) end) end test "signature help", %{server: server} do - uri = "file:///file.ex" - code = ~S[ + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S[ defmodule MyModule do def my_fn do IO.inspect() end end ] - fake_initialize(server) - Server.receive_packet(server, did_open(uri, "elixir", 1, code)) - Server.receive_packet(server, signature_help_req(1, uri, 3, 19)) - - resp = assert_receive(%{"id" => 1}, 1000) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, signature_help_req(1, uri, 3, 19)) + + resp = assert_receive(%{"id" => 1}, 1000) + + assert response(1, %{ + "activeParameter" => 0, + "activeSignature" => 0, + "signatures" => [ + %{ + "documentation" => %{ + "kind" => "markdown", + "value" => + """ + Inspects and writes the given `item` to the device. + + ``` + @spec inspect\ + """ <> _ + }, + "label" => "inspect(item, opts \\\\ [])", + "parameters" => [%{"label" => "item"}, %{"label" => "opts \\\\ []"}] + }, + %{ + "documentation" => %{ + "kind" => "markdown", + "value" => """ + Inspects `item` according to the given options using the IO `device`. - assert response(1, %{ - "activeParameter" => 0, - "activeSignature" => 0, - "signatures" => [ - %{ - "documentation" => %{ - "kind" => "markdown", - "value" => + ``` + @spec inspect(device, item, keyword) :: + item + when item: var + ``` """ - Inspects and writes the given `item` to the device. + }, + "label" => "inspect(device, item, opts)", + "parameters" => [ + %{"label" => "device"}, + %{"label" => "item"}, + %{"label" => "opts"} + ] + } + ] + }) = resp - ``` - @spec inspect\ - """ <> _ - }, - "label" => "inspect(item, opts \\\\ [])", - "parameters" => [%{"label" => "item"}, %{"label" => "opts \\\\ []"}] - }, - %{ - "documentation" => %{ - "kind" => "markdown", - "value" => """ - Inspects `item` according to the given options using the IO `device`. - - ``` - @spec inspect(device, item, keyword) :: - item - when item: var - ``` - """ - }, - "label" => "inspect(device, item, opts)", - "parameters" => [ - %{"label" => "device"}, - %{"label" => "item"}, - %{"label" => "opts"} - ] - } - ] - }) = resp + wait_until_compiled(server) + end) end @tag :fixture @@ -1226,6 +1420,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do }), 1000 end + + wait_until_compiled(server) end) end @@ -1247,6 +1443,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do ] }), 1000 + + wait_until_compiled(server) end) end @@ -1268,6 +1466,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do ] }), 2000 + + wait_until_compiled(server) end) end @@ -1298,6 +1498,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do "uri" => ^reference_uri } ]) = resp + + wait_until_compiled(server) end) after Code.put_compiler_option(:tracers, []) @@ -1330,6 +1532,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do "uri" => ^reference_uri } ]) = resp + + wait_until_compiled(server) end) after Code.put_compiler_option(:tracers, []) @@ -1445,6 +1649,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do } } ]) = resp + + wait_until_compiled(server) end) end @@ -1507,6 +1713,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do } } ]) = resp + + wait_until_compiled(server) end) end @@ -1531,6 +1739,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do resp = assert_receive(%{"id" => 4}, 5000) assert response(4, []) = resp + wait_until_compiled(server) end) end @@ -1592,6 +1801,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do } } ]) = resp + + wait_until_compiled(server) end) end @@ -1659,6 +1870,8 @@ defmodule ElixirLS.LanguageServer.ServerTest do } } ]) = resp + + wait_until_compiled(server) end) end diff --git a/apps/language_server/test/support/server_test_helpers.ex b/apps/language_server/test/support/server_test_helpers.ex index 62cfae06e..5a6664616 100644 --- a/apps/language_server/test/support/server_test_helpers.ex +++ b/apps/language_server/test/support/server_test_helpers.ex @@ -8,12 +8,11 @@ defmodule ElixirLS.LanguageServer.Test.ServerTestHelpers do alias ElixirLS.Utils.PacketCapture use ElixirLS.LanguageServer.Protocol - def start_server do + def start_server(server) do packet_capture = start_supervised!({PacketCapture, self()}) replace_logger(packet_capture) - server = start_supervised!({Server, nil}) Process.group_leader(server, packet_capture) json_rpc = start_supervised!({JsonRpc, name: JsonRpc}) @@ -108,7 +107,7 @@ defmodule ElixirLS.LanguageServer.Test.ServerTestHelpers do def fake_initialize(server) do :sys.replace_state(server, fn state -> - %{state | server_instance_id: "123", project_dir: "/fake_dir"} + %{state | server_instance_id: "123", project_dir: File.cwd!()} end) end