From 40ddbc593f77f5deb888f44090fa5184419092ed Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 29 Sep 2022 08:39:16 +0200 Subject: [PATCH 1/7] add custom command returning tests in file --- apps/language_server/lib/language_server.ex | 3 +- .../language_server/ex_unit_test_tracer.ex | 90 +++++++++++++++++++ .../providers/execute_command.ex | 3 +- .../get_ex_unit_tests_in_file.ex | 16 ++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 apps/language_server/lib/language_server/ex_unit_test_tracer.ex create mode 100644 apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex diff --git a/apps/language_server/lib/language_server.ex b/apps/language_server/lib/language_server.ex index 36dfd96c1..0e7dc7f18 100644 --- a/apps/language_server/lib/language_server.ex +++ b/apps/language_server/lib/language_server.ex @@ -10,7 +10,8 @@ defmodule ElixirLS.LanguageServer do {ElixirLS.LanguageServer.Server, ElixirLS.LanguageServer.Server}, {ElixirLS.LanguageServer.JsonRpc, name: ElixirLS.LanguageServer.JsonRpc}, {ElixirLS.LanguageServer.Providers.WorkspaceSymbols, []}, - {ElixirLS.LanguageServer.Tracer, []} + {ElixirLS.LanguageServer.Tracer, []}, + {ElixirLS.LanguageServer.ExUnitTestTracer, []} ] opts = [strategy: :one_for_one, name: ElixirLS.LanguageServer.Supervisor, max_restarts: 0] diff --git a/apps/language_server/lib/language_server/ex_unit_test_tracer.ex b/apps/language_server/lib/language_server/ex_unit_test_tracer.ex new file mode 100644 index 000000000..6d0190e4a --- /dev/null +++ b/apps/language_server/lib/language_server/ex_unit_test_tracer.ex @@ -0,0 +1,90 @@ +defmodule ElixirLS.LanguageServer.ExUnitTestTracer do + use GenServer + + @tables ~w(tests)a + + for table <- @tables do + defp table_name(unquote(table)) do + :"#{__MODULE__}:#{unquote(table)}" + end + end + + def start_link(args) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + def get_tests(path) do + GenServer.call(__MODULE__, {:get_tests, path}, :infinity) + end + + @impl true + def init(_args) do + for table <- @tables do + table_name = table_name(table) + + :ets.new(table_name, [ + :named_table, + :public, + read_concurrency: true, + write_concurrency: true + ]) + end + + ExUnit.start(autorun: false) + + {:ok, %{}} + end + + @impl true + def handle_call({:get_tests, path}, _from, state) do + :ets.delete_all_objects(table_name(:tests)) + tracers = Code.compiler_options()[:tracers] + Code.put_compiler_option(:tracers, tracers ++ [__MODULE__]) + # TODO parallel compiler and diagnostics? + _ = Code.compile_file(path) + + Code.put_compiler_option(:tracers, tracers) + result = :ets.tab2list(table_name(:tests)) + |> Enum.map(fn {{_file, module, line}, describes} -> + %{ + module: inspect(module), + line: line, + describes: describes + } + end) + + {:reply, result, state} + end + + def trace({:on_module, _, _}, %Macro.Env{} = env) do + test_info = Module.get_attribute(env.module, :ex_unit_tests) + + if test_info != nil do + describe_infos =test_info + |> Enum.group_by(fn %ExUnit.Test{tags: tags} -> {tags.describe, tags.describe_line} end) + |> Enum.map(fn {{describe, describe_line}, tests} -> + tests = tests + |> Enum.map(fn %ExUnit.Test{tags: tags} = test -> + %{ + name: test.name, + type: tags.test_type, + line: tags.line - 1, + } + end) + + %{ + describe: describe, + line: if(describe_line, do: describe_line - 1), + tests: tests + } + end) + + :ets.insert(table_name(:tests), {{env.file, env.module, env.line - 1}, describe_infos}) + end + :ok + end + + def trace(_, %Macro.Env{} = _env) do + :ok + end +end diff --git a/apps/language_server/lib/language_server/providers/execute_command.ex b/apps/language_server/lib/language_server/providers/execute_command.ex index 427521244..414759acc 100644 --- a/apps/language_server/lib/language_server/providers/execute_command.ex +++ b/apps/language_server/lib/language_server/providers/execute_command.ex @@ -10,7 +10,8 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand do "expandMacro" => ExecuteCommand.ExpandMacro, "manipulatePipes" => ExecuteCommand.ManipulatePipes, "restart" => ExecuteCommand.Restart, - "mixClean" => ExecuteCommand.MixClean + "mixClean" => ExecuteCommand.MixClean, + "getExUnitTestsInFile" => ExecuteCommand.GetExUnitTestsInFile } @callback execute([any], %ElixirLS.LanguageServer.Server{}) :: diff --git a/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex b/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex new file mode 100644 index 000000000..3c2100ea3 --- /dev/null +++ b/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex @@ -0,0 +1,16 @@ +defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFile do + alias ElixirLS.LanguageServer.{SourceFile, ExUnitTestTracer} + @behaviour ElixirLS.LanguageServer.Providers.ExecuteCommand + + @impl ElixirLS.LanguageServer.Providers.ExecuteCommand + def execute([uri], _state) do + path = SourceFile.Path.from_uri(uri) + # with {:ok, _} = Code.compile_file(path) do + tests = ExUnitTestTracer.get_tests(path) + {:ok, tests} + # else + # {:error, reason} -> + # {:error, :server_error, inspect(reason)} + # end + end +end From 2f601d62fc9e8995bf860211324b0460f559741c Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 22 Oct 2022 13:44:01 +0200 Subject: [PATCH 2/7] add tests --- apps/language_server/.formatter.exs | 5 +- .../language_server/ex_unit_test_tracer.ex | 68 +++++++++++++------ .../get_ex_unit_tests_in_file.ex | 12 ++-- .../test/fixtures/project_with_tests/mix.exs | 9 +++ .../project_with_tests/test/error_test.exs | 21 ++++++ .../project_with_tests/test/fixture_test.exs | 21 ++++++ .../get_ex_unit_tests_in_file_test.exs | 53 +++++++++++++++ 7 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 apps/language_server/test/fixtures/project_with_tests/mix.exs create mode 100644 apps/language_server/test/fixtures/project_with_tests/test/error_test.exs create mode 100644 apps/language_server/test/fixtures/project_with_tests/test/fixture_test.exs create mode 100644 apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs diff --git a/apps/language_server/.formatter.exs b/apps/language_server/.formatter.exs index 4629fcb03..cfcc89ae3 100644 --- a/apps/language_server/.formatter.exs +++ b/apps/language_server/.formatter.exs @@ -1,4 +1,7 @@ -impossible_to_format = ["test/fixtures/token_missing_error/lib/has_error.ex"] +impossible_to_format = [ + "test/fixtures/token_missing_error/lib/has_error.ex", + "test/fixtures/project_with_tests/test/error_test.exs" +] [ inputs: diff --git a/apps/language_server/lib/language_server/ex_unit_test_tracer.ex b/apps/language_server/lib/language_server/ex_unit_test_tracer.ex index 6d0190e4a..49af37ae0 100644 --- a/apps/language_server/lib/language_server/ex_unit_test_tracer.ex +++ b/apps/language_server/lib/language_server/ex_unit_test_tracer.ex @@ -40,18 +40,29 @@ defmodule ElixirLS.LanguageServer.ExUnitTestTracer do :ets.delete_all_objects(table_name(:tests)) tracers = Code.compiler_options()[:tracers] Code.put_compiler_option(:tracers, tracers ++ [__MODULE__]) - # TODO parallel compiler and diagnostics? - _ = Code.compile_file(path) - - Code.put_compiler_option(:tracers, tracers) - result = :ets.tab2list(table_name(:tests)) - |> Enum.map(fn {{_file, module, line}, describes} -> - %{ - module: inspect(module), - line: line, - describes: describes - } - end) + + result = + try do + # TODO parallel compiler and diagnostics? + _ = Code.compile_file(path) + + result = + :ets.tab2list(table_name(:tests)) + |> Enum.map(fn {{_file, module, line}, describes} -> + %{ + module: inspect(module), + line: line, + describes: describes + } + end) + + {:ok, result} + rescue + e -> + {:error, e} + after + Code.put_compiler_option(:tracers, tracers) + end {:reply, result, state} end @@ -60,17 +71,29 @@ defmodule ElixirLS.LanguageServer.ExUnitTestTracer do test_info = Module.get_attribute(env.module, :ex_unit_tests) if test_info != nil do - describe_infos =test_info + describe_infos = + test_info |> Enum.group_by(fn %ExUnit.Test{tags: tags} -> {tags.describe, tags.describe_line} end) |> Enum.map(fn {{describe, describe_line}, tests} -> - tests = tests - |> Enum.map(fn %ExUnit.Test{tags: tags} = test -> - %{ - name: test.name, - type: tags.test_type, - line: tags.line - 1, - } - end) + tests = + tests + |> Enum.map(fn %ExUnit.Test{tags: tags} = test -> + # drop test prefix + "test " <> test_name = Atom.to_string(test.name) + + test_name = + if describe != nil do + test_name |> String.replace_prefix(describe <> " ", "") + else + test_name + end + + %{ + name: test_name, + type: tags.test_type, + line: tags.line - 1 + } + end) %{ describe: describe, @@ -79,8 +102,9 @@ defmodule ElixirLS.LanguageServer.ExUnitTestTracer do } end) - :ets.insert(table_name(:tests), {{env.file, env.module, env.line - 1}, describe_infos}) + :ets.insert(table_name(:tests), {{env.file, env.module, env.line - 1}, describe_infos}) end + :ok end diff --git a/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex b/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex index 3c2100ea3..37acc2314 100644 --- a/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex +++ b/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex @@ -5,12 +5,10 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFile @impl ElixirLS.LanguageServer.Providers.ExecuteCommand def execute([uri], _state) do path = SourceFile.Path.from_uri(uri) - # with {:ok, _} = Code.compile_file(path) do - tests = ExUnitTestTracer.get_tests(path) - {:ok, tests} - # else - # {:error, reason} -> - # {:error, :server_error, inspect(reason)} - # end + + case ExUnitTestTracer.get_tests(path) do + {:ok, tests} -> {:ok, tests} + {:error, reason} -> {:error, :server_error, inspect(reason)} + end end end diff --git a/apps/language_server/test/fixtures/project_with_tests/mix.exs b/apps/language_server/test/fixtures/project_with_tests/mix.exs new file mode 100644 index 000000000..71a23daa8 --- /dev/null +++ b/apps/language_server/test/fixtures/project_with_tests/mix.exs @@ -0,0 +1,9 @@ +defmodule ProjectWithTests.MixProject do + use Mix.Project + + def project do + [app: :project_with_tests, version: "0.1.0"] + end + + def application, do: [] +end diff --git a/apps/language_server/test/fixtures/project_with_tests/test/error_test.exs b/apps/language_server/test/fixtures/project_with_tests/test/error_test.exs new file mode 100644 index 000000000..12be9dae8 --- /dev/null +++ b/apps/language_server/test/fixtures/project_with_tests/test/error_test.exs @@ -0,0 +1,21 @@ +defmodule FixtureTest do + use ExUnit.Case + + defmodule ModuleWithoutTests do + end + + test "fixture test" do + assert true + end + + describe "describe with test" do + test "fixture test" do + assert true + end + end + + describe "describe without test" do + end + + test "this will be a test in future" do +end diff --git a/apps/language_server/test/fixtures/project_with_tests/test/fixture_test.exs b/apps/language_server/test/fixtures/project_with_tests/test/fixture_test.exs new file mode 100644 index 000000000..dd215cb29 --- /dev/null +++ b/apps/language_server/test/fixtures/project_with_tests/test/fixture_test.exs @@ -0,0 +1,21 @@ +defmodule FixtureTest do + use ExUnit.Case + + defmodule ModuleWithoutTests do + end + + test "fixture test" do + assert true + end + + describe "describe with test" do + test "fixture test" do + assert true + end + end + + describe "describe without test" do + end + + test "this will be a test in future" +end diff --git a/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs b/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs new file mode 100644 index 000000000..6e0cf3fa3 --- /dev/null +++ b/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs @@ -0,0 +1,53 @@ +defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFileTest do + alias ElixirLS.LanguageServer.{ExUnitTestTracer, SourceFile} + alias ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFile + use ElixirLS.Utils.MixTest.Case, async: false + + setup do + {:ok, _} = start_supervised(ExUnitTestTracer) + + {:ok, %{}} + end + + @tag fixture: true + test "return tests" do + in_fixture(Path.join(__DIR__, "../.."), "project_with_tests", fn -> + uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/fixture_test.exs")) + + assert {:ok, + [ + %{ + describes: [ + %{ + describe: nil, + line: nil, + tests: [ + %{line: 19, name: "this will be a test in future", type: :test}, + %{line: 6, name: "fixture test", type: :test} + ] + }, + %{ + describe: "describe with test", + line: 10, + tests: [ + %{line: 11, name: "fixture test", type: :test} + ] + } + ], + line: 0, + module: "FixtureTest" + } + ]} = GetExUnitTestsInFile.execute([uri], nil) + end) + end + + @tag fixture: true + test "return error when file fails to compile" do + in_fixture(Path.join(__DIR__, "../.."), "project_with_tests", fn -> + uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/error_test.exs")) + + assert {:error, :server_error, "%TokenMissingError" <> _} = + GetExUnitTestsInFile.execute([uri], nil) + end) + end +end From c60f4e040e057cdf3d671c9e640a0a6d4d692be2 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 22 Oct 2022 18:30:02 +0200 Subject: [PATCH 3/7] move outside of tests to prevent mix from running tests in fixture --- .../execute_command/get_ex_unit_tests_in_file_test.exs | 4 ++-- .../fixtures/project_with_tests/mix.exs | 0 .../fixtures/project_with_tests/test/error_test.exs | 2 +- .../fixtures/project_with_tests/test/fixture_test.exs | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename apps/language_server/{test => test_fixtures}/fixtures/project_with_tests/mix.exs (100%) rename apps/language_server/{test => test_fixtures}/fixtures/project_with_tests/test/error_test.exs (92%) rename apps/language_server/{test => test_fixtures}/fixtures/project_with_tests/test/fixture_test.exs (100%) diff --git a/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs b/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs index 6e0cf3fa3..21e5e2501 100644 --- a/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs +++ b/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs @@ -11,7 +11,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFileT @tag fixture: true test "return tests" do - in_fixture(Path.join(__DIR__, "../.."), "project_with_tests", fn -> + in_fixture(Path.join(__DIR__, "../../../test_fixtures"), "project_with_tests", fn -> uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/fixture_test.exs")) assert {:ok, @@ -43,7 +43,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFileT @tag fixture: true test "return error when file fails to compile" do - in_fixture(Path.join(__DIR__, "../.."), "project_with_tests", fn -> + in_fixture(Path.join(__DIR__, "../../../test_fixtures"), "project_with_tests", fn -> uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/error_test.exs")) assert {:error, :server_error, "%TokenMissingError" <> _} = diff --git a/apps/language_server/test/fixtures/project_with_tests/mix.exs b/apps/language_server/test_fixtures/fixtures/project_with_tests/mix.exs similarity index 100% rename from apps/language_server/test/fixtures/project_with_tests/mix.exs rename to apps/language_server/test_fixtures/fixtures/project_with_tests/mix.exs diff --git a/apps/language_server/test/fixtures/project_with_tests/test/error_test.exs b/apps/language_server/test_fixtures/fixtures/project_with_tests/test/error_test.exs similarity index 92% rename from apps/language_server/test/fixtures/project_with_tests/test/error_test.exs rename to apps/language_server/test_fixtures/fixtures/project_with_tests/test/error_test.exs index 12be9dae8..2ef75b8c6 100644 --- a/apps/language_server/test/fixtures/project_with_tests/test/error_test.exs +++ b/apps/language_server/test_fixtures/fixtures/project_with_tests/test/error_test.exs @@ -1,4 +1,4 @@ -defmodule FixtureTest do +defmodule ErrorTest do use ExUnit.Case defmodule ModuleWithoutTests do diff --git a/apps/language_server/test/fixtures/project_with_tests/test/fixture_test.exs b/apps/language_server/test_fixtures/fixtures/project_with_tests/test/fixture_test.exs similarity index 100% rename from apps/language_server/test/fixtures/project_with_tests/test/fixture_test.exs rename to apps/language_server/test_fixtures/fixtures/project_with_tests/test/fixture_test.exs From 7b7d02ab1328f59ed4e5f7dd3384f3f34751aecb Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 22 Oct 2022 18:30:38 +0200 Subject: [PATCH 4/7] disable other tracers --- .../language_server/lib/language_server/ex_unit_test_tracer.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/language_server/lib/language_server/ex_unit_test_tracer.ex b/apps/language_server/lib/language_server/ex_unit_test_tracer.ex index 49af37ae0..9cfc75ec8 100644 --- a/apps/language_server/lib/language_server/ex_unit_test_tracer.ex +++ b/apps/language_server/lib/language_server/ex_unit_test_tracer.ex @@ -39,7 +39,8 @@ defmodule ElixirLS.LanguageServer.ExUnitTestTracer do def handle_call({:get_tests, path}, _from, state) do :ets.delete_all_objects(table_name(:tests)) tracers = Code.compiler_options()[:tracers] - Code.put_compiler_option(:tracers, tracers ++ [__MODULE__]) + # TODO build lock? + Code.put_compiler_option(:tracers, [__MODULE__]) result = try do From 5447a278301c9c5c635c3f61c148b85da5d991bc Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 22 Oct 2022 18:30:49 +0200 Subject: [PATCH 5/7] increase timeout --- apps/language_server/test/server_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index bb49655f7..ca8b3f453 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -726,7 +726,7 @@ defmodule ElixirLS.LanguageServer.ServerTest do 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}, 1000) + resp = assert_receive(%{"id" => 1}, 5000) assert response(1, %{ "isIncomplete" => true, From 6dc2eb959b23ba2fe3bf9b08f1d924512f0d866a Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 22 Oct 2022 21:23:42 +0200 Subject: [PATCH 6/7] improve compatibility with elixir 1.11 and 1.12 --- .../lib/language_server/source_file.ex | 6 +++--- apps/language_server/lib/language_server/tracer.ex | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/language_server/lib/language_server/source_file.ex b/apps/language_server/lib/language_server/source_file.ex index e79feab77..ce7cec7e5 100644 --- a/apps/language_server/lib/language_server/source_file.ex +++ b/apps/language_server/lib/language_server/source_file.ex @@ -240,10 +240,10 @@ defmodule ElixirLS.LanguageServer.SourceFile do try do true = Code.ensure_loaded?(Mix.Tasks.Format) - if function_exported?(Mix.Tasks.Format, :formatter_for_file, 1) do - {:ok, Mix.Tasks.Format.formatter_for_file(path)} + if Version.match?(System.version(), ">= 1.13.0") do + {:ok, apply(Mix.Tasks.Format, :formatter_for_file, [path])} else - {:ok, {nil, Mix.Tasks.Format.formatter_opts_for_file(path)}} + {:ok, {nil, apply(Mix.Tasks.Format, :formatter_opts_for_file, [path])}} end rescue e -> diff --git a/apps/language_server/lib/language_server/tracer.ex b/apps/language_server/lib/language_server/tracer.ex index 3b7d7023c..0d4065c30 100644 --- a/apps/language_server/lib/language_server/tracer.ex +++ b/apps/language_server/lib/language_server/tracer.ex @@ -205,16 +205,22 @@ defmodule ElixirLS.LanguageServer.Tracer do end defp build_module_info(module, file, line) do - defs = + defs = if Version.match?(System.version(), ">= 1.12.0") do for {name, arity} <- Module.definitions_in(module) do - def_info = Module.get_definition(module, {name, arity}) + def_info = apply(Module, :get_definition, [module, {name, arity}]) {{name, arity}, build_def_info(def_info)} end + else + [] + end - attributes = - for name <- Module.attributes_in(module) do + attributes = if Version.match?(System.version(), ">= 1.13.0") do + for name <- apply(Module, :attributes_in, [module]) do {name, Module.get_attribute(module, name)} end + else + [] + end %{ defs: defs, From 0df887f99cb31776ad408424288b464ce55e67cd Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 22 Oct 2022 23:32:43 +0200 Subject: [PATCH 7/7] on_module is only available since 1.13 --- .../get_ex_unit_tests_in_file.ex | 12 ++- .../lib/language_server/tracer.ex | 28 +++---- .../get_ex_unit_tests_in_file_test.exs | 76 ++++++++++--------- 3 files changed, 62 insertions(+), 54 deletions(-) diff --git a/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex b/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex index 37acc2314..dfc167e7b 100644 --- a/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex +++ b/apps/language_server/lib/language_server/providers/execute_command/get_ex_unit_tests_in_file.ex @@ -4,11 +4,15 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFile @impl ElixirLS.LanguageServer.Providers.ExecuteCommand def execute([uri], _state) do - path = SourceFile.Path.from_uri(uri) + if Version.match?(System.version(), ">= 1.13.0") do + path = SourceFile.Path.from_uri(uri) - case ExUnitTestTracer.get_tests(path) do - {:ok, tests} -> {:ok, tests} - {:error, reason} -> {:error, :server_error, inspect(reason)} + case ExUnitTestTracer.get_tests(path) do + {:ok, tests} -> {:ok, tests} + {:error, reason} -> {:error, :server_error, inspect(reason)} + end + else + {:error, :server_error, "This feature requires elixir >= 1.13"} end end end diff --git a/apps/language_server/lib/language_server/tracer.ex b/apps/language_server/lib/language_server/tracer.ex index 0d4065c30..ebdb8c156 100644 --- a/apps/language_server/lib/language_server/tracer.ex +++ b/apps/language_server/lib/language_server/tracer.ex @@ -205,22 +205,24 @@ defmodule ElixirLS.LanguageServer.Tracer do end defp build_module_info(module, file, line) do - defs = if Version.match?(System.version(), ">= 1.12.0") do - for {name, arity} <- Module.definitions_in(module) do - def_info = apply(Module, :get_definition, [module, {name, arity}]) - {{name, arity}, build_def_info(def_info)} + defs = + if Version.match?(System.version(), ">= 1.12.0") do + for {name, arity} <- Module.definitions_in(module) do + def_info = apply(Module, :get_definition, [module, {name, arity}]) + {{name, arity}, build_def_info(def_info)} + end + else + [] end - else - [] - end - attributes = if Version.match?(System.version(), ">= 1.13.0") do - for name <- apply(Module, :attributes_in, [module]) do - {name, Module.get_attribute(module, name)} + attributes = + if Version.match?(System.version(), ">= 1.13.0") do + for name <- apply(Module, :attributes_in, [module]) do + {name, Module.get_attribute(module, name)} + end + else + [] end - else - [] - end %{ defs: defs, diff --git a/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs b/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs index 21e5e2501..688cc9f6a 100644 --- a/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs +++ b/apps/language_server/test/providers/execute_command/get_ex_unit_tests_in_file_test.exs @@ -9,45 +9,47 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.GetExUnitTestsInFileT {:ok, %{}} end - @tag fixture: true - test "return tests" do - in_fixture(Path.join(__DIR__, "../../../test_fixtures"), "project_with_tests", fn -> - uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/fixture_test.exs")) + if Version.match?(System.version(), ">= 1.13.0") do + @tag fixture: true + test "return tests" do + in_fixture(Path.join(__DIR__, "../../../test_fixtures"), "project_with_tests", fn -> + uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/fixture_test.exs")) - assert {:ok, - [ - %{ - describes: [ - %{ - describe: nil, - line: nil, - tests: [ - %{line: 19, name: "this will be a test in future", type: :test}, - %{line: 6, name: "fixture test", type: :test} - ] - }, - %{ - describe: "describe with test", - line: 10, - tests: [ - %{line: 11, name: "fixture test", type: :test} - ] - } - ], - line: 0, - module: "FixtureTest" - } - ]} = GetExUnitTestsInFile.execute([uri], nil) - end) - end + assert {:ok, + [ + %{ + describes: [ + %{ + describe: nil, + line: nil, + tests: [ + %{line: 19, name: "this will be a test in future", type: :test}, + %{line: 6, name: "fixture test", type: :test} + ] + }, + %{ + describe: "describe with test", + line: 10, + tests: [ + %{line: 11, name: "fixture test", type: :test} + ] + } + ], + line: 0, + module: "FixtureTest" + } + ]} = GetExUnitTestsInFile.execute([uri], nil) + end) + end - @tag fixture: true - test "return error when file fails to compile" do - in_fixture(Path.join(__DIR__, "../../../test_fixtures"), "project_with_tests", fn -> - uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/error_test.exs")) + @tag fixture: true + test "return error when file fails to compile" do + in_fixture(Path.join(__DIR__, "../../../test_fixtures"), "project_with_tests", fn -> + uri = SourceFile.Path.to_uri(Path.join(File.cwd!(), "test/error_test.exs")) - assert {:error, :server_error, "%TokenMissingError" <> _} = - GetExUnitTestsInFile.execute([uri], nil) - end) + assert {:error, :server_error, "%TokenMissingError" <> _} = + GetExUnitTestsInFile.execute([uri], nil) + end) + end end end