Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/language_server/.formatter.exs
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
3 changes: 2 additions & 1 deletion apps/language_server/lib/language_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
115 changes: 115 additions & 0 deletions apps/language_server/lib/language_server/ex_unit_test_tracer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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]
# TODO build lock?
Code.put_compiler_option(:tracers, [__MODULE__])

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

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 ->
# 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,
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
Original file line number Diff line number Diff line change
Expand Up @@ -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{}) ::
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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
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)}
end
else
{:error, :server_error, "This feature requires elixir >= 1.13"}
end
end
end
6 changes: 3 additions & 3 deletions apps/language_server/lib/language_server/source_file.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
18 changes: 13 additions & 5 deletions apps/language_server/lib/language_server/tracer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,22 @@ defmodule ElixirLS.LanguageServer.Tracer do

defp build_module_info(module, file, line) do
defs =
for {name, arity} <- Module.definitions_in(module) do
def_info = Module.get_definition(module, {name, arity})
{{name, arity}, build_def_info(def_info)}
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

attributes =
for name <- Module.attributes_in(module) do
{name, Module.get_attribute(module, name)}
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

%{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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

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

@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)
end
end
end
2 changes: 1 addition & 1 deletion apps/language_server/test/server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule ErrorTest 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
Original file line number Diff line number Diff line change
@@ -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